fix(windows): handle spaces in Node.js path for plugin install on Windows#37592
fix(windows): handle spaces in Node.js path for plugin install on Windows#37592manusjs wants to merge 1 commit intoopenclaw:mainfrom
Conversation
…dows
Two bugs when Node.js is installed in a path with spaces (e.g. the default
C:\Program Files\nodejs\):
Bug 1 (primary): resolveNpmArgvForWindows resolves npm/npx to
[node.exe, npm-cli.js, ...] where both paths may contain spaces.
These were passed directly to spawn() without cmd.exe wrapping,
causing Node.js argument escaping to garble the command line.
Fix: detect when any resolved argv entry contains a space and
route through cmd.exe + buildCmdExeCommandLine (which uses
escapeForCmdExe) in both runCommandWithTimeout and runExec.
Bug 2 (secondary): when resolveNpmArgvForWindows returns null (Node
installed via nvm-windows, volta, fnm — npm-cli.js not at the
standard nodeDir path), resolveCommand('npm') returned bare 'npm'
causing spawn EINVAL on Node 18.20.2+ (CVE-2024-27980 mitigation
rejects .cmd without shell=true).
Fix: add 'npm' and 'npx' to the cmdCommands list in resolveCommand
so they resolve to npm.cmd/npx.cmd and get the cmd.exe wrapper path
as a fallback, identical to how pnpm/yarn are handled.
Fixes openclaw#37563
Greptile SummaryThis PR fixes two related Windows bugs that caused
Confidence Score: 4/5
Last reviewed commit: 0c2289f |
| const finalArgv = process.platform === "win32" ? (resolveNpmArgvForWindows(argv) ?? argv) : argv; | ||
| const resolvedCommand = finalArgv !== argv ? (finalArgv[0] ?? "") : resolveCommand(argv[0] ?? ""); | ||
| const useCmdWrapper = isWindowsBatchCommand(resolvedCommand); | ||
| const wasNpmResolved = process.platform === "win32" && finalArgv !== argv; |
There was a problem hiding this comment.
Inconsistent wasNpmResolved tracking across functions
runCommandWithTimeout detects whether npm was resolved via reference-inequality (finalArgv !== argv), relying on the fact that resolveNpmArgvForWindows returns a fresh array or null. runExec (line 121-127) uses an explicit wasNpmResolved = true boolean instead, which is clearer and more resilient to future changes in resolveNpmArgvForWindows.
Consider storing the result of resolveNpmArgvForWindows explicitly to keep the two functions consistent:
const resolvedArgv = process.platform === "win32" ? resolveNpmArgvForWindows(argv) : null;
const wasNpmResolved = resolvedArgv !== null;
const finalArgv = resolvedArgv ?? argv;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/process/exec.ts
Line: 232
Comment:
**Inconsistent `wasNpmResolved` tracking across functions**
`runCommandWithTimeout` detects whether npm was resolved via reference-inequality (`finalArgv !== argv`), relying on the fact that `resolveNpmArgvForWindows` returns a fresh array or `null`. `runExec` (line 121-127) uses an explicit `wasNpmResolved = true` boolean instead, which is clearer and more resilient to future changes in `resolveNpmArgvForWindows`.
Consider storing the result of `resolveNpmArgvForWindows` explicitly to keep the two functions consistent:
```
const resolvedArgv = process.platform === "win32" ? resolveNpmArgvForWindows(argv) : null;
const wasNpmResolved = resolvedArgv !== null;
const finalArgv = resolvedArgv ?? argv;
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| const useCmdWrapper = | ||
| isWindowsBatchCommand(execCommand) || | ||
| (wasNpmResolved && [execCommand, ...execArgs].some((a) => a.includes(" "))); |
There was a problem hiding this comment.
No unit tests for the new space-in-path and .cmd fallback code paths
exec.windows.test.ts covers the pre-existing .cmd wrapping for pnpm/yarn, but neither of the two new branches introduced here are exercised:
wasNpmResolved && [execCommand, ...execArgs].some((a) => a.includes(" "))inrunExec- The equivalent branch in
runCommandWithTimeout(line 236-238) - The
npm/npx→.cmdfallback path viaresolveCommand
Both paths are mockable without a real Windows machine: process.platform can be spied on, fs.existsSync can be mocked to return false (triggering the fallback) or true with a path that includes a space (triggering the wrapper). Given that the bugs were subtle enough to slip through previously, covering these paths would give future maintainers a safety net.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/process/exec.ts
Line: 136-138
Comment:
**No unit tests for the new space-in-path and `.cmd` fallback code paths**
`exec.windows.test.ts` covers the pre-existing `.cmd` wrapping for `pnpm`/`yarn`, but neither of the two new branches introduced here are exercised:
- `wasNpmResolved && [execCommand, ...execArgs].some((a) => a.includes(" "))` in `runExec`
- The equivalent branch in `runCommandWithTimeout` (line 236-238)
- The `npm`/`npx` → `.cmd` fallback path via `resolveCommand`
Both paths are mockable without a real Windows machine: `process.platform` can be spied on, `fs.existsSync` can be mocked to return `false` (triggering the fallback) or `true` with a path that includes a space (triggering the wrapper). Given that the bugs were subtle enough to slip through previously, covering these paths would give future maintainers a safety net.
How can I resolve this? If you propose a fix, please make it concise.
What
Fix two bugs that break
openclaw plugins installon Windows when Node.js is installed in a path containing spaces (e.g. the defaultC:\Program Files\nodejs\).Fixes #37563
Bug 1 (primary) — space-containing resolved paths not wrapped in cmd.exe
resolveNpmArgvForWindowsresolvesnpm packto:These were passed directly to
spawn()without the cmd.exe wrapper, causing Node's CreateProcessW escaping to garble paths with spaces:Fix: After
resolveNpmArgvForWindowssucceeds, check if any resolved argv entry contains a space. If so, route throughcmd.exe + buildCmdExeCommandLine(which usesescapeForCmdExe), same as the existing .cmd/.bat wrapper path. Applied in bothrunCommandWithTimeoutandrunExec.Bug 2 (secondary) — EINVAL when npm-cli.js not found at standard path
When Node is installed via nvm-windows, volta, or fnm,
npm-cli.jsis not atnodeDir/node_modules/npm/bin/.resolveNpmArgvForWindowsreturnsnull, andresolveCommand('npm')returned bare'npm'(npm was removed fromcmdCommandsin a prior fix), causing spawn EINVAL on Node 18.20.2+.Fix: Add
'npm'and'npx'back to thecmdCommandslist inresolveCommandso they resolve tonpm.cmd/npx.cmdas a fallback — identical to howpnpm/yarnare handled. Updated the comment to reflect this.Testing
All 55 existing process tests pass. Platform-specific behavior (Windows spawn semantics) can only be fully verified on a Windows machine, but the logic paths are well-covered by the existing exec test suite.