Skip to content

PackageManager: npm argv not quoted under shell:true on Windows — spaced cwd breaks session create #4623

@sarukas

Description

@sarukas

Upstream bug report — earendil-works/pi-mono

Package: @earendil-works/pi-coding-agent@0.74.0
File: packages/coding-agent/src/core/package-manager.ts
(compiled: dist/core/package-manager.js)
Platform: Windows
Severity: High — aborts session creation for any working directory
whose absolute path contains a space (very common, e.g.
C:\Users\<name>\Documents\Marketing Advisor).

Summary

PackageManager spawns npm/npx/pnpm/yarn with Node's
child_process.spawn(command, args, { shell: true }) on Windows
(shouldUseWindowsShell() returns true because these resolve to
.cmd shims). Per the Node.js docs, when shell is true the
args array is not quoted/escaped
— the command line is handed to
cmd.exe verbatim, which word-splits on whitespace.

Consequently npm install <spec> --prefix <dir> where <dir> has a
space (e.g. the project <cwd>/.pi/npm) is split: npm receives a
truncated --prefix and the remainder as a separate relative arg.

Reproduction

  1. On Windows, start a session whose working directory path contains a
    space, e.g. C:\Users\me\Documents\Marketing Advisor.
  2. PI auto-installs a project extension:
    npm install @dhruv2mars/pi-queue --prefix C:\Users\me\Documents\Marketing Advisor\.pi\npm
  3. Observed failure:
npm error code ENOENT
npm error path C:\Users\me\Documents\<launchCwd>\Advisor\.pi\npm\package.json
npm error enoent Could not read package.json: ENOENT: no such file or directory,
  open 'C:\Users\me\Documents\<launchCwd>\Advisor\.pi\npm\package.json'
Error: npm install @dhruv2mars/pi-queue --prefix C:\Users\me\Documents\Marketing Advisor\.pi\npm
  failed with code 4294963238
    at ChildProcess.<anonymous> (.../core/package-manager.js)

cmd.exe split --prefix C:\…\Marketing Advisor\.pi\npm into
--prefix C:\…\Marketing + Advisor\.pi\npm; npm resolved the latter
relative to the spawn cwd → ENOENT. Session creation aborts.

Root cause

dist/core/package-manager.js (v0.74.0), three spawn sites:

  • spawnCommand()spawn(command, args, { shell: shouldUseWindowsShell(command) })
  • spawnCaptureCommand() — same
  • runCommandSync()spawnSync(command, args, { shell: shouldUseWindowsShell(command) })

args is passed unquoted while shell is true.

Suggested fix

When (and only when) the shell is used, quote args that contain
whitespace before handing them to spawn/spawnSync (passthrough
otherwise so the non-shell argv path is unchanged):

function shellQuote(useShell: boolean, args: string[]): string[] {
  if (!useShell) return args;
  return args.map((a) => {
    const s = String(a);
    return /\s/.test(s) ? `"${s.replace(/"/g, '\\"')}"` : s;
  });
}

Apply at the three spawn sites. (A more complete escape of cmd.exe
metacharacters — &|<>^()%! — is advisable, but whitespace quoting
alone resolves the reported ENOENT class.) Alternatively, avoid
shell:true by resolving the npm/npx executable to its absolute path
and spawning it directly without a shell.

Workaround (downstream, ally2)

Patched locally via patch-package against
@earendil-works/pi-coding-agent with the shellQuote helper above.
Tracked as ally2 open topic T-077.

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions