taskkill /PID flag mangled by Git Bash MSYS path translation on Windows

Open 💬 2 comments Opened Jun 8, 2026 by kefyusuf

Summary

terminateProcessTree() fails silently on Windows when the Node.js process is launched from Git Bash (e.g. via the Claude Code CLI or any MSYS2-based terminal). The cancel companion command returns exit 1 and running processes are never stopped.

Root cause

In scripts/lib/process.mjs, runCommand() sets:

shell: process.platform === "win32" ? (process.env.SHELL || true) : false,

In a Git Bash session, process.env.SHELL is /usr/bin/bash (or similar). spawnSync then spawns that shell as the interpreter. Git Bash applies MSYS automatic path translation to all arguments that look like Unix paths: /PID → C:/Program Files/Git/PID, /T → C:/Program Files/Git/T, /F → C:/Program Files/Git/F.

So the actual command that reaches taskkill.exe is:

taskkill "C:/Program Files/Git/PID" 65784 "C:/Program Files/Git/T" "C:/Program Files/Git/F"

which exits 1 with ERROR: Invalid argument/option - 'C:/Program Files/Git/PID'.

Reproduction

  1. Open Git Bash on Windows.
  2. Launch Claude Code (or any tool that uses codex-companion.mjs).
  3. Start a long-running codex task and try to cancel it.
  4. cancel <job-id> returns an error; the process keeps running.

Confirmed on: Windows 11 Pro 10.0.26200, Git for Windows 2.x, Node.js 20+.

Error output (observed)

taskkill /PID 65784 /T /F: exit=1: ERROR: Invalid argument/option - 'C:/Program Files/Git/PID'

Workaround

Set MSYS_NO_PATHCONV=1 before invoking the companion:

MSYS_NO_PATHCONV=1 node scripts/codex-companion.mjs cancel <job-id>

Suggested fix

For the taskkill invocation specifically, either:

Option A — force cmd.exe instead of process.env.SHELL for Win32 so MSYS translation never runs:

// In runCommand():
shell: process.platform === "win32" ? "cmd.exe" : false,

Option B — inject MSYS_NO_PATHCONV=1 into the env when calling terminateProcessTree():

const result = runCommandImpl("taskkill", ["/PID", String(pid), "/T", "/F"], {
  cwd: options.cwd,
  env: { MSYS_NO_PATHCONV: "1", ...(options.env ?? process.env) },
});

Option C — use shell: false for taskkill directly (it is a native Windows executable and does not need a shell wrapper). This would require passing a custom runCommandImpl or adding a noShell option to runCommand.

Option A is the lowest-risk single-line change; cmd.exe is always available on Win32 and does not apply path mangling.

View original on GitHub ↗

2 Comments

AI-DeveloPac · 10 days ago

Confirmed on a current Windows setup.

Environment:

  • ChatGPT/Codex Windows app: 26.810.7004.0
  • Bundled Codex CLI: 0.148.0-alpha.9
  • Windows 11 build 26100
  • Git for Windows: 2.54.0.windows.1
  • Shell: C:\Program Files\Git\bin\bash.exe

I reproduced this with a temporary PING.EXE process created solely for the test. From Git Bash:

taskkill.exe /PID <temporary-pid> /T /F
Error: Argumento u opción no válido - "C:/Program Files/Git/PID".

So /PID is still being rewritten by MSYS path conversion exactly as described in this issue. The user-visible effect is that cancellation fails and the long-running job/process remains alive.

I also verified that prefixing the command with MSYS_NO_PATHCONV=1 removes the argument-mangling error. In the ChatGPT Windows sandbox the corrected command then reaches taskkill.exe but returns Access denied; direct taskkill.exe from PowerShell returns the same access-denied result there, so that second result appears to be the sandbox boundary rather than MSYS conversion. The temporary processes were cleaned up with PowerShell Stop-Process.

This suggests the companion should avoid process.env.SHELL for native Windows process termination and invoke taskkill.exe with shell: false (or force cmd.exe), while the app-side runner should use its native process-tree termination path rather than relying on a sandboxed taskkill.

AI-DeveloPac · 10 days ago

One more Windows localization edge case should be covered by the upstream fix and its regression tests.

On Spanish Windows, invoking taskkill.exe for a nonexistent PID produces:

ERROR: no se encontró el proceso "999999".
exit code: 128

The current looksLikeMissingProcessMessage() matcher in the Claude Code Codex plugin only recognizes English phrases:

/not found|no running instance|cannot find|does not exist|no such process/i

As a result, once the Git Bash argument-mangling problem is fixed, idempotent cancellation can still throw on localized Windows installations when the process has already exited.

A tempting workaround is to accept every taskkill exit code 128 as “already stopped”, but that is broader than necessary and can hide a different localized failure. A safer minimal fix is to extend the message matcher, for example:

/not found|no running instance|cannot find|does not exist|no such process|no se encontr[oó] el proceso/i

I verified this locally with two regression cases:

  1. status 128 + ERROR: no se encontró el proceso "1234". returns an already-stopped outcome;
  2. status 128 + ERROR: Acceso denegado. still throws.

Please include a localized missing-process test with the upstream shell: false / native taskkill fix so this behavior survives plugin updates.