Skip to content

Fix Windows ENOENT/uv_spawn when spawning Agent CLIs - #5

Open
Kelvinz-89757 wants to merge 1 commit into
deerwork-ai:mainfrom
Kelvinz-89757:fix/windows-executable-resolution
Open

Fix Windows ENOENT/uv_spawn when spawning Agent CLIs#5
Kelvinz-89757 wants to merge 1 commit into
deerwork-ai:mainfrom
Kelvinz-89757:fix/windows-executable-resolution

Conversation

@Kelvinz-89757

@Kelvinz-89757 Kelvinz-89757 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #4.

Bun.spawn performs no PATHEXT-style resolution — it executes the literal
command string it is given. npm installs a cross-platform executable (like
claude) as a set of sibling files sharing one base name: an extension-less
POSIX shell script, a .cmd batch wrapper, and sometimes a .ps1 wrapper.
Both CodexAgent and ClaudeAgent hand Bun.spawn the bare, extension-less
command name (this.#config.command, defaulting to "claude"/"codex"),
which native Windows process creation cannot execute directly, raising
ENOENT: no such file or directory, uv_spawn '<command>' — exactly the
failure reported in #4, and root-caused there via Bun.spawn/uv_spawn not
following the same PATHEXT resolution a shell (or Node's own
child_process.spawn, which special-cases .cmd/.bat) would.

  • Adds resolveExecutable() (src/agents/resolve-executable.ts): on
    win32, tries the configured command with .exe, .cmd, .bat
    extensions (in that order) via Bun.which, falling back to the bare name;
    on every other platform, and for any already-absolute path, behavior is
    unchanged (passthrough to Bun.which).
  • Both CodexAgent and ClaudeAgent now resolve the command once per run()
    call and spawn the resolved path instead of the bare configured name. The
    existing CodexCliNotFoundError/ClaudeCliNotFoundError now fire off the
    same resolution call, so behavior when the CLI truly isn't installed is
    unchanged.

Why both agents

Both harnesses share the identical Bun.spawn(command) / Bun.which(command)
pattern, so this isn't Claude-specific — Codex is exposed to the same bug on
Windows, it just doesn't appear to have been reported yet.

Important caveat — please test on real Windows before merging

I implemented and verified this on macOS, reasoning from Bun's documented
Bun.spawn/Bun.which behavior (Bun.spawn's options expose
windowsVerbatimArguments/windowsHide but no shell-resolution option) and
from the diagnosis already done in #4. I do not have a Windows machine to
reproduce the original failure or confirm this fix resolves it end-to-end.

The new resolveExecutable() unit tests exercise the win32 branch's
resolution logic deterministically (by injecting platform and a mock
which, so they run correctly on any host OS), but they cannot exercise the
actual Bun.spawn call on Windows.

@guohui666 — you already have a working repro environment and did the
original diagnostic legwork in #4 (thank you, it made root-causing this
straightforward). It would be great if you could pull this branch and confirm
deer-workflow create --agent claude "..." now works without the
mklink/PATH workarounds. Happy to iterate if anything's off.

Test plan

  • bun run typecheck
  • bun run lint
  • bun run format:check
  • bun test (all passing, including 8 new resolveExecutable unit tests
    covering: non-Windows passthrough, Windows .exe/.cmd preference
    order, fallback to the bare name, null when nothing resolves, and
    absolute-path passthrough for both Windows- and POSIX-style paths)
  • Manual confirmation on a real Windows machine (see caveat above —
    requesting help from the issue reporter or another Windows user)

Bun.spawn performs no PATHEXT-style resolution and executes the literal
command string it is given. npm installs a cross-platform executable as
sibling files sharing one base name: an extension-less POSIX shell script,
a `.cmd` batch wrapper, and sometimes a `.ps1` wrapper. Both CodexAgent and
ClaudeAgent handed Bun.spawn the bare, extension-less command name, which
native Windows process creation cannot execute directly, raising
`ENOENT: no such file or directory, uv_spawn`.

Both agents now resolve the configured command through resolveExecutable(),
which prefers a `.exe`/`.cmd`/`.bat` sibling on win32 before falling back to
the bare name, and pass the resolved path to Bun.spawn instead.

Fixes deerwork-ai#4.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

no such file or directory, uv_spawn 'claude'

1 participant