Skip to content

fix: quote Windows arguments that contain spaces - #1412

Open
ClayChipps wants to merge 1 commit into
oclif:mainfrom
ClayChipps:fix/quote-windows-args
Open

ClayChipps wants to merge 1 commit into
oclif:mainfrom
ClayChipps:fix/quote-windows-args

Conversation

@ClayChipps

@ClayChipps ClayChipps commented Oct 20, 2026 •

Copy link
Copy Markdown

What does this PR do?

On Windows, spawn() passes arguments with windowsVerbatimArguments: true and does no quoting, so an argument containing whitespace reaches npm as several arguments.

This shows up with JIT plugins whose oclif.jitPlugins range contains a space, e.g. ">=0.4.0 <1.0.0". The JIT install runs:

npm install @scope/pkg@>=0.4.0 <1.0.0

npm receives @scope/pkg@>=0.4.0 and <1.0.0 as two specs. npm-package-arg parses <1.0.0 as a range with no name, and npm resolves it to the unrelated, deprecated package undefined. That package gets installed into the CLI's data dir and saved in its package.json. The real plugin still installs, so the problem is easy to miss. plugins update does the same thing every time, because it passes the saved range (or the jitPlugins range) to npm the same way.

This is the same root cause as #1387. #1388 fixed it for a space in process.execPath by quoting the Node path, but the arguments are still passed unquoted.

The fix

quoteWindowsArg quotes an argument that contains whitespace or ", using the CommandLineToArgvW rules. That is what libuv does when arguments are not verbatim: wrap in quotes, escape embedded quotes, and double backslashes that precede a quote. Arguments without whitespace or quotes pass through unchanged. windowsVerbatimArguments stays on, so the existing guarantee that shell metacharacters stay literal (should not interpret shell metacharacters in arguments) is unaffected.

Testing

  • New test should pass an argument containing spaces as one argument round-trips a spaced range, an embedded quote, and a path with spaces and trailing backslashes through spawn. On Windows it fails without the fix (["@scope/pkg@>=1.0.0","<2.0.0","say","hi",…]) and passes with it. On other platforms it passes either way, as expected.
  • New unit test for quoteWindowsArg's quoting rules.
  • test/spawn.test.ts, test/plugins.test.ts and test/commands/** pass on Windows (Node 24). tsc -p test and eslint src test are clean.

What issues does this PR fix or reference?

Related: #1387, #1388

🤖 Generated with Claude Code

spawn() uses windowsVerbatimArguments, so Node joins the arguments with
spaces and does no quoting. An argument containing whitespace reached npm
as several: installing `pkg@>=1.0.0 <2.0.0` also installed `<2.0.0`,
which npm-package-arg reads as a nameless range and resolves to the
unrelated package `undefined`. JIT plugins with a spaced range in
oclif.jitPlugins hit this on every install and `plugins update`.

Quote such arguments the way CommandLineToArgvW expects, which is what
libuv does when arguments are not verbatim. Arguments without whitespace
or quotes are passed through unchanged, so the verbatim behaviour that
keeps shell metacharacters literal is untouched.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant