fix(scripts): claw.js spawn 'claude' fails with ENOENT on Windows#1471
fix(scripts): claw.js spawn 'claude' fails with ENOENT on Windows#1471mike-batrakov wants to merge 1 commit intoaffaan-m:mainfrom
Conversation
Fixes affaan-m#1469. On Windows the `claude` binary installed via `npm i -g @anthropic-ai/claude-code` is `claude.cmd`, and Node's spawn() cannot resolve .cmd wrappers via PATH without shell: true. The call failed with `spawn claude ENOENT` and claw.js returned an error string to the caller. Mirrors the fix pattern applied in PR affaan-m#1456 for the MCP health-check hook. 'claude' is a hardcoded literal (not user input), so enabling shell on Windows only is safe.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughA conditional flag enabling shell execution is added to the Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR adds Confidence Score: 5/5Safe to merge — single-line targeted fix with a clear precedent in the codebase and no behavioral change on non-Windows platforms. The change is minimal (one option added to an existing spawnSync call), mirrors an already-reviewed pattern from mcp-health-check.js, and only activates on Windows. The only finding is a P2 observation about shell metacharacters in user-supplied args, which carries negligible practical risk for a local developer REPL where the user controls their own input. No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant U as User (claw REPL)
participant M as main()
participant A as askClaude()
participant S as spawnSync
participant W as Windows cmd.exe
participant C as claude CLI
U->>M: types message
M->>A: askClaude(eccContext, history, message, model)
A->>A: buildPrompt(systemPrompt, history, userMessage)
Note over A: shell = process.platform === 'win32'
A->>S: spawnSync('claude', args, {shell: win32?})
alt Windows (shell: true)
S->>W: cmd.exe /d /s /c "claude --model ... -p ..."
W->>C: resolves claude.cmd via PATH
else macOS/Linux (shell: false)
S->>C: direct spawn via PATH
end
C-->>S: stdout response
S-->>A: result
A-->>M: response string
M->>U: prints response
Reviews (1): Last reviewed commit: "fix(scripts): resolve claude.cmd on Wind..." | Re-trigger Greptile |
| const result = spawnSync('claude', args, { | ||
| encoding: 'utf8', | ||
| stdio: ['pipe', 'pipe', 'pipe'], | ||
| env: { ...process.env, CLAUDECODE: '' }, | ||
| timeout: 300000, | ||
| shell: process.platform === 'win32', | ||
| }); |
There was a problem hiding this comment.
Shell metacharacters in
-p prompt on Windows
With shell: true, Node.js joins the command and all args with spaces and passes the result to cmd.exe /d /s /c "...". The fullPrompt (which contains live user input) is included unescaped in that command string, so a Windows user whose message contains " or & <command> could trigger unintended cmd.exe interpretation.
In practice the risk is self-inflicted since the person typing is also the one running the REPL, but it's worth noting that the "safe because claude is a hardcoded literal" reasoning in the comment applies only to the command name, not to the args. A future-proof alternative is to resolve the .cmd path explicitly rather than relying on the shell:
const command = process.platform === 'win32'
? (process.env.CLAUDE_CMD_PATH || 'claude.cmd')
: 'claude';
const result = spawnSync(command, args, {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, CLAUDECODE: '' },
timeout: 300000,
shell: process.platform === 'win32', // still needed to execute .cmd
});Or, to avoid the shell entirely and protect args, use where.exe / which to pre-resolve the absolute path to claude.cmd before spawning. No action required for this PR — just worth keeping in mind if prompt content ever comes from a less-trusted source.
There was a problem hiding this comment.
Valid observation on attack surface. One note: the snippet in this comment still sets shell: process.platform === 'win32', so switching to process.env.CLAUDE_CMD_PATH || 'claude.cmd' by itself doesn't remove the cmd.exe parse of joined args — the metacharacter risk in fullPrompt would remain.
The only way to actually eliminate shell parsing of args when executing a .cmd wrapper is to bypass the wrapper entirely — i.e., invoke the underlying node script that claude.cmd dispatches to (e.g. spawnSync(process.execPath, [<path-to-claude.js>, ...args])). That's a broader refactor than this ENOENT fix.
Agreed this is a self-inflicted concern in a REPL context (the user typing is the user running it). Leaving as-is for this PR and filing a follow-up for proper arg hardening — will add it to TECH_DEBT.
There was a problem hiding this comment.
Pull request overview
Fixes Windows spawnSync('claude', ...) failures in scripts/claw.js by enabling shell execution on win32 so claude.cmd can be resolved from PATH.
Changes:
- Add Windows-specific
shell: truebehavior to thespawnSync('claude', ...)invocation inaskClaude(). - Add inline rationale comments explaining the Windows
.cmd/ENOENTbehavior.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Fixes #1469 —
scripts/claw.jscallsspawnSync('claude', args, { ... })with noshelloption. On Windows the installedclaudebinary isclaude.cmd, which Node'sspawn()cannot resolve via PATH withoutshell: true. The call fails withspawn claude ENOENTandaskClaude()returns an error string.Fix
Mirror the pattern applied in #1456 for the MCP health-check hook: pass
shell: process.platform === 'win32'tospawnSync.'claude'is a hardcoded literal (not user input), so enabling shell on Windows only is safe — no metacharacter validation needed.Scope
Audited every
spawn/spawnSync/execFilecall underscripts/for the same Windows ENOENT pattern. This is the only remaining instance; other sites either explicitly handle.cmd(post-edit-format.js,stop-format-typecheck.js) or use absolute paths (process.execPath, resolved formatter bins).Related
scripts/hooks/mcp-health-check.jsscripts/claw.jsTest plan
node scripts/claw.js <session> "hello"reaches theclaudeCLI instead of erroring with ENOENTwin32)Summary by cubic
Fixes the Windows ENOENT error in
scripts/claw.jsby enabling shell forspawnSync('claude', ...), allowing PATH to resolveclaude.cmd. Windows runs theclaudeCLI correctly; macOS/Linux behavior is unchanged.Written for commit e09af12. Summary will update on new commits.
Summary by CodeRabbit
Bug Fixes