fix(windows): route npm/npx through node + cli.js to survive uv_spawn#5
Open
oldschoola wants to merge 1 commit into
Open
fix(windows): route npm/npx through node + cli.js to survive uv_spawn#5oldschoola wants to merge 1 commit into
oldschoola wants to merge 1 commit into
Conversation
platform.exec ultimately calls libuv's uv_spawn, which on Windows:
1. does not consult PATHEXT, so spawning 'npm' fails with
'ENOENT: uv_spawn npm' because the on-disk file is npm.cmd, and
2. since Node >=18.20.2 (CVE-2024-27980) hard-rejects spawning .cmd
and .bat shims without shell: true, which ExecOptions does not
expose.
Wrapping in cmd.exe /d /s /c needs windowsVerbatimArguments to be
safe, which we also don't control. Instead, resolve npm and npx to
the same 'node <cli.js> ...args' invocation their .cmd shims use
internally. node.exe is a plain binary that libuv spawns cleanly.
Threaded through every existing call site:
- src/bootstrap.ts background version check
- src/commands/plan.ts visual-companion install
(the user-visible failure on
/supi:plan)
- src/commands/doctor.ts npm presence + ping
- src/commands/update.ts npm view / install,
bun + npm fallback,
deps installer lambda
- src/harness/anti_slop/fallow.ts npx --no-install fallow
Other binaries (bun, git, node, gh, rustup, go, pip, uv) ship as
.exe and pass through untouched. POSIX always passes through.
Tests cover the rewrite, the pass-through, the wrapExecForCli
adapter, and the fallback when npm-cli.js is missing. Existing
update / fallow tests normalize captured calls so they stay stable
on both platforms.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
On Windows,
/supi:plan(when the user picks "Terminal + Visual companion") fails with:/supi:doctorand/supi:updateare affected by the same root cause but fail more quietly (silent "npm not found" in doctor, broken update flow on first install).Root cause
platform.execultimately calls libuv'suv_spawn. On Windows that's a double bug for npm-shipped CLIs:PATHEXT, so spawning"npm"fails because the on-disk file isnpm.cmd..cmd/.batshims withoutshell: true(CVE-2024-27980).ExecOptionsdoes not exposeshell.Wrapping in
cmd.exe /d /s /cis the canonical workaround, but only safe when the spawner setswindowsVerbatimArguments: true— Node's default CRT escaping double-quotes the command line and cmd's/sonly strips one pair, leaving""command""for cmd.exe to choke on. Tested locally: this does break for paths with spaces. We don't control the spawner, so we sidestep the whole problem.Fix
Resolve
npmandnpxto the exactnode <cli.js> ...argsinvocation their.cmdshims use internally.node.exeis a plain binary that libuv spawns without ceremony, and the CVE check doesn't trip. Repro:A small
execClihelper (src/utils/exec-cli.ts) wraps each existing call site:src/commands/plan.tsnpm install --production(visual companion deps — the user-visible failure)src/bootstrap.tsnpm view supipowers versionsrc/commands/doctor.tsnpm --version,npm pingsrc/commands/update.tsnpm view,npm install --prefix,bun install, npm fallback, plus the deps-installer lambda viawrapExecForClisrc/harness/anti_slop/fallow-adapter.tsnpx --no-install fallow ...and the resolved-invocation runnerOther binaries (
bun,git,node,gh,rustup,go,pip,uv, …) ship as.exeon Windows and pass through untouched. POSIX always passes through.Tests
tests/utils/exec-cli.test.ts— new. Covers the rewrite, pass-through,wrapExecForCli, fallback whennpm-cli.jsis missing, and POSIX behaviour. Uses a synthesised fake Node tree on aPATHoverride so the Windows branch is deterministic.tests/commands/update.test.ts,tests/harness/anti_slop/fallow-adapter.test.ts— normalize capturedplatform.execcalls so existing mocks keep matching on the logical command (helper:tests/helpers/exec-calls.ts).Full
bun testhas 11 pre-existing failures onmainon this Windows box, all intests/context-mode//tests/release//tests/mempalace//tests/qa/and all about WSL/Hyper-V not being installed, dev-server timing, or release dry-run output. None touch the spawn path.Risk
process.platform === "win32"and the bare command name; a stale cache survives the process. There's a_resetExecCliCacheForTestingescape hatch.npmandnpx. Anything else falls through, so existing callers that spawngit,node,gh,bun,rustup, etc. are unchanged.nodeor<command>-cli.jscannot be located, so POSIX-only setups and exotic installs degrade to today's behaviour.Closes the symptom reported as
Extension "command:supi:plan" error: ENOENT: no such file or directory, uv_spawn 'npm'.