Skip to content

Fix: agent-runner spawn() calls fail on Windows for npm-installed CLI tools (.cmd shim) - #154

Closed
priyamkarn wants to merge 3 commits into
Autoloops:mainfrom
priyamkarn:fix/agent-runner-windows-cmd-spawn
Closed

priyamkarn wants to merge 3 commits into
Autoloops:mainfrom
priyamkarn:fix/agent-runner-windows-cmd-spawn

Conversation

@priyamkarn

@priyamkarn priyamkarn commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

#153

Bug

libs/agent-runner/{codex,opencode,openhands}.ts all spawn their respective CLIs
using node:child_process's spawn() without shell:true. On Windows, CLI tools
installed via npm install -g are shimmed as .cmd files, not native .exe.
Windows' CreateProcess (which spawn() calls fairly directly without shell:true)
cannot execute .cmd/.bat files at all -- they require cmd.exe /c as an
intermediary.

How this was found

While testing a fake opencode.cmd stand-in via the exact same spawn pattern
the production code uses, this reproduced immediately on a real Windows
machine:

Error: spawn EINVAL
    at ChildProcess.spawn (node:internal/child_process:441:11)

Since production code uses the identical spawn("", args, {no shell:true})
pattern for all three CLIs, this points to the same failure in real use
whenever any of them is npm-installed on Windows -- meaning the background
auto-memory-update hook likely fails silently on Windows today.

Fix

Switched all three runners to cross-spawn (the standard, widely-used solution
for this -- same package npm uses internally). It detects .cmd/.bat targets
and transparently wraps them through cmd.exe with correct argument escaping,
avoiding both the original bug and the shell-injection risk of a blanket
shell:true.

Tests

scripts/check-agent-runner-cross-spawn.js checks, for all three runners:

  1. Source imports spawn from cross-spawn, not node:child_process.
  2. The compiled dist output also references cross-spawn (catches a build
    misconfiguration the source check alone would miss).
  3. End-to-end: the opencode runner's injectable spawn seam produces the
    correct argv when exercised with a fake spawn implementation.

priyamkarn and others added 2 commits July 13, 2026 07:16
…LI tools

codex.ts, opencode.ts, and openhands.ts all spawned their respective CLIs via node:child_process's spawn() without shell:true. On Windows, npm-installed global CLI tools are shimmed as .cmd files, which Windows' CreateProcess cannot execute directly -- it requires cmd.exe as an intermediary. Reproduced this directly (spawn EINVAL) while testing a fake opencode.cmd stand-in with the exact same spawn pattern the production code uses.

Switches all three runners to cross-spawn, the standard solution for this (same package npm itself uses internally) -- it detects .cmd/.bat targets and wraps them through cmd.exe with correct argument escaping, without needing shell:true and its shell-injection risk.
@priyamkarn priyamkarn closed this Jul 29, 2026
@priyamkarn priyamkarn reopened this Jul 29, 2026
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.

1 participant