Environment
- OS: Windows 11
- Node: v22.x
- codex: 0.128.0 (npm global install)
- Plugin: openai-codex 1.0.1
Symptom
On Windows, invoking codex:rescue (or any path that triggers CodexAppServerClient.connect) fails with:
Error: spawn codex ENOENT
/codex:setup reports ready: true because detection takes a different code path that already uses shell: true, masking the issue.
Root cause
scripts/lib/app-server.mjs:188 calls:
this.proc = spawn("codex", ["app-server"], {
cwd: this.cwd,
env: this.options.env,
stdio: ["pipe", "pipe", "pipe"]
});
On Windows, child_process.spawn does not consult PATHEXT, so it cannot resolve a .cmd shim. The npm global install of @openai/codex only ships:
codex (sh shim, no extension)
codex.cmd
codex.ps1
There is no codex.exe, so spawn falls through with ENOENT.
By contrast, scripts/lib/process.mjs:5 already uses shell: process.platform === "win32" for the same reason — that is why binaryAvailable("codex", ...) works during /codex:setup.
Reproduction
On Windows with codex installed via npm i -g @openai/codex:
// fails with ENOENT
require('child_process').spawn('codex', ['--version']).on('error', console.error);
// works
require('child_process').spawn('codex', ['--version'], { shell: true }).on('error', console.error);
Diagnostic note
Stderr from a failed task-worker often shows a DEP0190 deprecation warning and the ENOENT side by side. They come from different spawn points:
DEP0190 originates from process.mjs (uses shell: true + args array — Node 22+ warns on this combination, but the spawn itself succeeds).
ENOENT originates from app-server.mjs:188 (no shell: true, fails to resolve the .cmd shim).
Easy to misread as "shell:true is already set but spawn still fails."
Suggested fix
Two options:
-
Match the existing pattern in process.mjs — add shell: process.platform === "win32" to the spawn options at app-server.mjs:188. One-line change. Caveat: on Windows this wraps the child in cmd.exe, so this.proc.kill() may leave the actual codex process as an orphan.
-
Resolve the executable's full path before spawning — try codex, codex.cmd, codex.exe against PATHEXT (or use a small helper / cross-spawn) and spawn the resolved path directly. No shell wrapper, kill targets the codex process directly. Cleaner long-term fix.
Option 2 is preferable; option 1 is a one-line patch that unblocks Windows users immediately.
Environment
Symptom
On Windows, invoking
codex:rescue(or any path that triggersCodexAppServerClient.connect) fails with:/codex:setupreportsready: truebecause detection takes a different code path that already usesshell: true, masking the issue.Root cause
scripts/lib/app-server.mjs:188calls:On Windows,
child_process.spawndoes not consultPATHEXT, so it cannot resolve a.cmdshim. The npm global install of@openai/codexonly ships:There is no
codex.exe, so spawn falls through with ENOENT.By contrast,
scripts/lib/process.mjs:5already usesshell: process.platform === "win32"for the same reason — that is whybinaryAvailable("codex", ...)works during/codex:setup.Reproduction
On Windows with codex installed via
npm i -g @openai/codex:Diagnostic note
Stderr from a failed task-worker often shows a
DEP0190deprecation warning and the ENOENT side by side. They come from different spawn points:DEP0190originates fromprocess.mjs(usesshell: true+ args array — Node 22+ warns on this combination, but the spawn itself succeeds).ENOENToriginates fromapp-server.mjs:188(noshell: true, fails to resolve the.cmdshim).Easy to misread as "shell:true is already set but spawn still fails."
Suggested fix
Two options:
Match the existing pattern in
process.mjs— addshell: process.platform === "win32"to the spawn options atapp-server.mjs:188. One-line change. Caveat: on Windows this wraps the child incmd.exe, sothis.proc.kill()may leave the actual codex process as an orphan.Resolve the executable's full path before spawning — try
codex,codex.cmd,codex.exeagainstPATHEXT(or use a small helper /cross-spawn) and spawn the resolved path directly. No shell wrapper, kill targets the codex process directly. Cleaner long-term fix.Option 2 is preferable; option 1 is a one-line patch that unblocks Windows users immediately.