Skip to content

Bug: agent-runner spawn() calls likely fail on Windows for npm-installed CLI tools (.cmd shim / CreateProcess limitation) #153

Description

@priyamkarn

Summary

libs/agent-runner/{codex,opencode,openhands}.ts all spawn their respective
agent CLIs (codex, opencode, presumably the openhands binary) using Node's
child_process.spawn() without shell: true, e.g.:

const child = spawn("opencode", args, {
  cwd: input.cwd,
  env: input.env,
  stdio: ["ignore", "pipe", "inherit"],
});

This likely fails on Windows whenever the target CLI is installed the standard
way -- via npm install -g, which creates a .cmd shim rather than a native
.exe.

Why

spawn() without shell: true calls Windows' CreateProcess directly.
CreateProcess cannot execute .cmd/.bat files -- they aren't in native
executable (PE) format and require cmd.exe /c <file> as an intermediary to
run at all. This is an OS-level restriction, not a Node bug or version quirk.

How I found this

While building a test for a separate opencode fix (argv secret exposure), I
tried spawning a fake opencode.cmd stand-in the exact same way the real
runner does. It failed immediately with:

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

reproduced on a real Windows machine (Node v24.16.0), not a POSIX simulation.
Since the real production spawn call uses the identical pattern
(spawn("opencode", args, { ...no shell:true... })), this points to the same
failure happening for real if opencode is npm-installed on Windows.

Impact

If confirmed, the background auto-memory-update hook (libs/hooks/worker.ts)
would silently fail on Windows for any platform whose CLI is npm-installed --
likely all three (codex, opencode, openhands installers all follow the
same spawn pattern). This is a portability/functional bug, not a security
issue -- separate root cause from #, even though
found in the same file.

Suggested fix

The standard solution for "spawn needs to work with .cmd/.bat shims on
Windows, cross-platform, without shell-injection risk" is the cross-spawn
npm package. It detects .cmd/.bat targets and handles the
cmd.exe /c wrapping (with correct argument escaping) automatically,
avoiding the injection risk that comes with a blanket shell: true.

Would apply to all three agent runners identically, since they share the
same spawn pattern.

To verify before fixing

  • Confirm on a real Windows machine that a real, npm-installed opencode
    (or codex) actually reproduces this when triggered via
    greplica install + a real coding session (not just my synthetic
    .cmd stand-in).
  • Check whether any of the three CLIs happen to ship a native .exe
    instead of an npm .cmd shim (would make this moot for that specific
    tool).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions