Skip to content

fix(vscode): run npm.cmd through the shell on Windows (spawn EINVAL breaks Sharp install and ReadImage) - #399

Merged
qorzj merged 1 commit into
lessweb:mainfrom
OkoDev:fix/vscode-sharp-npm-cmd-win32
Nov 4, 2026
Merged

qorzj merged 1 commit into
lessweb:mainfrom
OkoDev:fix/vscode-sharp-npm-cmd-win32

Conversation

@OkoDev

@OkoDev OkoDev commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #398.

Problem

On Windows the Sharp loader cannot bootstrap on a cold cache. installSharp() spawned npm.cmd without shell: true, and Node >= 18.20 / 20.12 refuses to spawn .cmd / .bat in that case (CVE-2024-27980 hardening) — it throws EINVAL synchronously. The throw happens inside the new Promise executor, so child.on("error") never fires and the caller receives a bare spawn EINVAL:

  • ReadImage → Unable to read image: spawn EINVAL
  • an image attached to a prompt → Request failed: spawn EINVAL

This is reached whenever the Sharp cache for the required version is missing, i.e. right after an extension update bumps sharpVersion.

findCliAnchors() had the same bug in execFileSync("npm.cmd", ["root", "-g"]), where it was swallowed by the surrounding catch {}, so the global-npm anchor never resolved on Windows.

Fix

  • New buildNpmInvocation(args, platform): on Windows the invocation becomes npm.cmd "<arg>" "<arg>" ... with shell: true. Arguments are folded into the command string because shell mode concatenates them unescaped (Node DEP0190), and quoting keeps paths with spaces — such as the VS Code storage root — intact. Everywhere else the behaviour is unchanged (npm + argv, shell: false).
  • Used by both installSharp() and the npm root -g lookup in findCliAnchors().
  • spawnNpm() converts synchronous spawn failures into Failed to start npm: ..., so a future sync throw is reported the same way as the async error event instead of leaking a raw error code.

Tests

packages/vscode-ide-companion: 88/88 pass (node src/tests/run-tests.mjs), including 3 new tests covering the Windows invocation (shell flag, quoting, and a prefix path containing a space) and the unchanged posix argv form.

Also clean: tsc -p ./ --noEmit, eslint, prettier --check on the changed files.

Manual verification (Windows 11, VS Code 1.144.1, cold cache)

Driving createSharpLoader() against a fresh storage root, so the real installSharp() path runs:

  • with the current code: spawn EINVAL
  • with this PR: sharp 0.35.5 installed and loaded in 3.1 s, progress 10% → 100%
  • npm root -g anchor now resolves to C:\Users\<user>\AppData\Roaming\npm\node_modules instead of throwing

…l stops failing with EINVAL

Node >= 18.20 / 20.12 refuses to spawn .cmd/.bat files without a shell
(CVE-2024-27980) and throws EINVAL synchronously, which bypasses
child.on("error") and surfaces as a bare 'spawn EINVAL'. ReadImage failed
with 'Unable to read image: spawn EINVAL' and image prompts died with
'Request failed: spawn EINVAL' whenever the Sharp cache was cold, e.g.
after an extension update bumped sharpVersion.

Build the npm invocation once (buildNpmInvocation) and use it for both the
lazy install and the 'npm root -g' anchor lookup, which previously threw
and was swallowed. Failures raised synchronously by spawn are now reported
as 'Failed to start npm: ...' instead of a raw error code.

Fixes lessweb#398
@qorzj
qorzj merged commit a66979d into lessweb:main Nov 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Windows: ReadImage / image prompts fail with "spawn EINVAL" (sharp-loader spawns npm.cmd without shell: true)

2 participants