From 865f7faba90887b49903eeee1a61b54445244883 Mon Sep 17 00:00:00 2001 From: Mohammad Malik <80257039+mohammad-malik@users.noreply.github.com> Date: Sat, 5 Sep 2026 03:22:53 +0500 Subject: [PATCH] Do not run taskkill through a shell on Windows `runCommand` prefers `process.env.SHELL` on Windows, so when Claude Code runs under Git Bash the process kill became `bash -c "taskkill /PID ..."`. MSYS path conversion then rewrote the `/PID` switch into `C:/Program Files/Git/PID`, and taskkill rejected it before touching the process: ERROR: Invalid argument/option - 'C:/Program Files/Git/PID'. Every `/codex:cancel` failed that way, as did the internal kills, so a job could not be stopped from a Git Bash session at all. taskkill takes `/`-switches and an argument array, so it never needed a shell. Passing `shell: false` at this one call site fixes it without changing how any other command runs: `runCommand` still prefers the user's shell everywhere else, where the quoting it provides is wanted. Verified on Windows 11 with SHELL=C:\Program Files\Git\bin\bash.exe, against a real process that had a child of its own so the `/T` tree kill was exercised: before the change taskkill never ran, after it the tree exits and taskkill returns 0. The existing Windows test now also asserts the absence of a shell, since the arguments alone were identical in both the working and broken cases. Co-Authored-By: Claude Opus 5 --- plugins/codex/scripts/lib/process.mjs | 8 +++++++- tests/process.test.mjs | 9 ++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/plugins/codex/scripts/lib/process.mjs b/plugins/codex/scripts/lib/process.mjs index dd8fc3751..ab913b2f4 100644 --- a/plugins/codex/scripts/lib/process.mjs +++ b/plugins/codex/scripts/lib/process.mjs @@ -66,7 +66,13 @@ export function terminateProcessTree(pid, options = {}) { if (platform === "win32") { const result = runCommandImpl("taskkill", ["/PID", String(pid), "/T", "/F"], { cwd: options.cwd, - env: options.env + env: options.env, + // runCommand prefers process.env.SHELL on Windows, so under Git Bash this + // ran as `bash -c "taskkill /PID ..."` and MSYS path conversion rewrote + // the /PID switch into C:/Program Files/Git/PID, failing every kill with + // "ERROR: Invalid argument/option". taskkill takes /-switches and an + // argument array, so it does not need a shell. + shell: false }); if (!result.error && result.status === 0) { diff --git a/tests/process.test.mjs b/tests/process.test.mjs index 80e0715b0..f59bd56d4 100644 --- a/tests/process.test.mjs +++ b/tests/process.test.mjs @@ -7,8 +7,8 @@ test("terminateProcessTree uses taskkill on Windows", () => { let captured = null; const outcome = terminateProcessTree(1234, { platform: "win32", - runCommandImpl(command, args) { - captured = { command, args }; + runCommandImpl(command, args, options) { + captured = { command, args, shell: options?.shell }; return { command, args, @@ -26,7 +26,10 @@ test("terminateProcessTree uses taskkill on Windows", () => { assert.deepEqual(captured, { command: "taskkill", - args: ["/PID", "1234", "/T", "/F"] + args: ["/PID", "1234", "/T", "/F"], + // Not through a shell: under Git Bash, MSYS path conversion turns the /PID + // switch into C:/Program Files/Git/PID and taskkill rejects it. + shell: false }); assert.equal(outcome.delivered, true); assert.equal(outcome.method, "taskkill");