codex-companion cancel fails on Windows + Git Bash due to MSYS path conversion
TL;DR
On Windows with Git Bash (MSYS) as the default shell, /codex:cancel <task-id> consistently fails because the taskkill /PID <pid> /T /F invocation gets corrupted by MSYS path conversion — /PID is mangled into C:/Program Files/Git/PID and passed to taskkill as an invalid argument. The zombie task is never killed, the worker stays alive, the task slot is never released, and the user is permanently locked out of running new codex tasks until they manually taskkill from a real cmd.exe.
In our case one zombie task (task-mp9b4g4b-rt35lr) blocked all subsequent codex-rescue work for over 1 hour with no recovery path.
Environment
| | |
|---|---|
| OS | Windows 10/11 (MINGW64_NT-10.0-26200) — Chinese locale |
| codex-cli | 0.128.0 |
| codex-companion plugin | openai-codex/codex/1.0.4 (under Claude Code plugin cache) |
| Shell | Git Bash GNU bash 5.2.37(1)-release (x86_64-pc-msys) |
| Node | (whatever Claude Code ships) — emits DEP0190 warning about shell:true |
| Claude Code | with codex:codex-rescue agent integration |
Steps to Reproduce
- From Claude Code, invoke the
codex:codex-rescueagent with a long-running task that usesnpm run buildor any other Windows subprocess output capture.
- The agent may hang on reading stdout from that subprocess (a separate bug — first observed in a session where codex was implementing a multi-file refactor and froze at
npm run buildoutput read). The companion reports the task asrunningbut no progress is made for 30+ minutes.
- User runs
/codex:cancel <task-id>from Claude Code.
- Behind the scenes, the companion runs:
````
node ".../codex-companion.mjs" cancel "task-mp9b4g4b-rt35lr"
- The companion attempts to kill the worker process via
process.kill(pid)(Node-internaltaskkillon Windows), which spawns:
````
taskkill /PID 43280 /T /F
- Bug: When spawned with
shell: trueunder Git Bash, MSYS converts the/PIDargument toC:/Program Files/Git/PID, so the actual invocation becomes:
````
taskkill "C:/Program Files/Git/PID" 43280 /T /F
taskkillexits with code 1. The stderr message (Chinese Windows locale) is:
错误: 无效参数/选项 - 'C:/Program Files/Git/PID'。请键入 "TASKKILL /?" 以了解用法。
— English equivalent: ERROR: Invalid argument/option - 'C:/Program Files/Git/PID'. Type "TASKKILL /?" for usage.
Actual error output (verbatim)
Error: Shell command failed for pattern "!`node "C:/Users/Lenovo/.claude/plugins/cache/openai-codex/codex/1.0.4/scripts/codex-companion.mjs" cancel "task-mp9b4g4b-rt35lr"`": [stderr]
(node:8320) [DEP0190] DeprecationWarning: Passing args to a child process with shell option true can lead to security vulnerabilities, as the arguments are not escaped, only concatenated.
(Use `node --trace-deprecation ...` to show where the warning was created)
taskkill /PID 43280 /T /F: exit=1: 错误: 无效参数/选项 - 'C:/Program Files/Git/PID'。
请键入 "TASKKILL /?" 以了解用法。
(The Chinese lines translate to: ERROR: Invalid argument/option - 'C:/Program Files/Git/PID'. Type "TASKKILL /?" for usage.)
The DEP0190 warning is itself a smoking gun: the companion is using child_process.spawn with shell: true, which on MSYS triggers the path conversion.
Expected behavior
taskkillactually kills PID 43280 and its child processes.- Task
task-mp9b4g4b-rt35lrtransitions tocancelledstate. - Worktree
.claude/worktrees/<task-name>is cleaned up. - Task slot is released; subsequent codex-rescue invocations succeed.
Observed impact
- Zombie task remains in
runningstate forever. - All subsequent
Agent({ subagent_type: "codex:codex-rescue", ... })calls return a Chinese-locale message:
> 任务 task-mp9b4g4b-rt35lr 仍在运行中,无法续接。请先用 /codex:status 查看当前状态,等任务完成或停止后再续接。
> (English: Task task-mp9b4g4b-rt35lr is still running and cannot be continued. Use /codex:status to check the current state and wait for the task to complete or stop before continuing.)
- The user has no working recovery path except killing Node processes manually from
cmd.exeor restarting Claude Code. - Worktree directory leaks:
.claude/worktrees/magical-allen-6b883cremained after 24+ hours.
Root cause hypothesis
Looking at the codex-companion.mjs bundle around the spawn call (~line 643):
const child = spawn(process.execPath, [scriptPath, "task-worker", "--cwd", cwd, "--job-id", jobId], {
...
});
The script spawns child workers. The cancel path likely uses process.kill(pid) or child.kill(), which on Windows defers internally to taskkill /T /F /PID <pid>. If that internal call (or any direct spawn("taskkill", [...], { shell: true })) inherits Git Bash via process.env.ComSpec or runs under shell, MSYS path conversion mangles /PID → C:/Program Files/Git/PID.
The DEP0190 warning indicates the companion is explicitly using shell: true somewhere when spawning, which is itself a security risk (per the deprecation notice) and the direct cause of the path mangling.
Suggested fixes (any of these would resolve)
Fix A (preferred): Don't use shell: true for taskkill
// Instead of:
spawn("taskkill", ["/PID", pid, "/T", "/F"], { shell: true });
// Use:
spawn("taskkill", ["/PID", String(pid), "/T", "/F"], { shell: false });
taskkill is a native Windows command on PATH; no shell is needed. This also clears the DEP0190 warning.
Fix B: Force MSYS to skip path conversion
If shell: true must be retained for other reasons:
spawn("taskkill", ["//PID", String(pid), "//T", "//F"], {
shell: true,
env: { ...process.env, MSYS_NO_PATHCONV: "1" }
});
The double-slash //PID is the MSYS escape convention to prevent path conversion. The env var is belt-and-suspenders.
Fix C: Use cmd.exe explicitly on Windows
const shell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
spawn(shell, ["/c", `taskkill /PID ${pid} /T /F`], { shell: false });
This sidesteps Git Bash entirely.
Fix D: Use a cross-platform process killer library
E.g. tree-kill handles Windows + macOS + Linux uniformly without manual taskkill invocations.
Workaround for affected users (until fixed)
- Open
cmd.exe(not Git Bash) as Administrator. - Find the stuck Node PID:
````
tasklist | findstr node
- Kill it manually:
````
taskkill /PID <pid> /T /F
- Remove the stale worktree manually:
````
rmdir /S /Q "<repo>\.claude\worktrees\<task-name>"
- Restart the Claude Code session to clear in-memory task state.
Additional context / related issues
- The
DEP0190warning is not just informational — it precisely identifies that the underlyingspawn({ shell: true })is the cause. Worth addressing project-wide if other commands use the same pattern. - This bug is likely invisible on macOS / Linux because there is no MSYS path conversion. Test coverage should include Windows + Git Bash explicitly.
- Possibly related: any
/codex:<command>that ultimately invokestaskkillor other slash-prefixed Windows commands throughshell: truemay exhibit the same issue.
Repro test (suggested for codex-companion CI)
// Should pass on Windows when run from a Git Bash environment
test("cancel kills worker via taskkill without path conversion", () => {
// Simulate Git Bash env: process.env.MSYSTEM = "MINGW64"
// Spawn worker, then cancel
// Assert: worker process exits, no "Invalid argument" stderr
});
---
Reporter context: Encountered while using the codex:codex-rescue subagent in Claude Code for a multi-step Python refactor task. The agent's npm run build step hung on output read after about 20 minutes of normal work. /codex:cancel then failed as described, blocking 1+ hour of subsequent work with no recovery path other than restarting Claude Code.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗