[BUG] [Windows] TaskStop / background-task kill leaves Git Bash child processes running (orphaned grep, find, du, sleep for hours)

Status Open
Reported on v2.1.296
Maintainer reply None cached
Activity 1 comment · opened Oct 10, 2026

Environment

  • Claude Code 2.1.296 (native claude.exe), Windows 11 Pro 10.0.26200
  • Shell for the Bash tool: Git for Windows 2.50.1 (C:\Program Files\Git\bin\bash.exe, GNU bash 5.2.37 x86_64-pc-msys, MSYS runtime 3.6.3)
  • Reproduced inside a subagent and, with the same command, in the main thread; all traced cases in the wild were subagents.
  • Related: #85200 (same symptom, data loss), #84464, #27273. This report adds the mechanism and a 5-second reproduction.

Summary

When a background Bash task is stopped — by the TaskStop tool, and by the harness itself when it terminates a
task — Claude Code kills the two bash.exe processes of the call and reports Successfully stopped task: <id>
(the output file ends with [killed]). The programs that shell had started (grep.exe, find.exe, du.exe,
sleep.exe, head.exe … from Git\usr\bin) are not killed. They keep running with no parent and no owner
until they finish on their own, which for a recursive search can be hours.

The same sequence with the PowerShell tool and a native child (ping) kills the child correctly.

Why it happens (observed, not read from source)

A Git Bash (MSYS2) program is started through a short-lived intermediate process, so the Windows parent pid of
sleep.exe / grep.exe is already dead one second after start — for a perfectly healthy pipeline too. A kill that
walks the Windows parent/child tree from bash.exe therefore never reaches them. Only the MSYS process table
(ps -ef inside Git Bash) shows the real parent, the shell of the call.

Minimal reproduction

  1. Bash tool, foreground, timeout: 5000:

``bash
echo start-A; sleep 611 | grep -v zzz-repro-A | head -3; echo end-A
``

Result: Command did not complete within its 5s timeout and was moved to the background (ID: bwal3npo5).

  1. Process list at this point (pid, Windows parent, start, command):

``
8728 ppid= 44704(claude.exe) 11:06:26 bash.exe "C:\Program Files\Git\bin\bash.exe" -c "<snapshot> eval '...
45532 ppid= 8728(bash.exe) 11:06:26 bash.exe "C:\Program Files\Git\bin\..\usr\bin\bash.exe" -c "<snapshot> e...
42800 ppid= 1764(DEAD) 11:06:26 sleep.exe "C:\Program Files\Git\usr\bin\sleep.exe" 611
28240 ppid= 22980(DEAD) 11:06:26 grep.exe "C:\Program Files\Git\usr\bin\grep.exe" -v zzz-repro-A
44276 ppid= 26640(DEAD) 11:06:26 head.exe "C:\Program Files\Git\usr\bin\head.exe" -3
``

In the MSYS table (ps -ef) all three have the call's shell as their parent.

  1. TaskStop with that id → Successfully stopped task: bwal3npo5; the output file reads start-A / [killed].
  1. Process list after the stop: both bash.exe are gone; sleep.exe 42800, grep.exe 28240 and head.exe 44276

are still running.

Variant: the same with run_in_background: true and sleep 612 → after TaskStop only sleep.exe is left alive.

Control (works as expected): PowerShell tool, timeout: 5000, ping -n 613 127.0.0.1 | Select-String 'zzz' →
moved to the background → TaskStop → both pwsh.exe and PING.EXE are gone.

Expected

Stopping a task terminates every process the task's shell started (as it already does on Windows for native
children of PowerShell), or the tool result says that children were left running.

Actual

Successfully stopped task is reported while the children keep running without a parent.

Impact (one machine, 16 hours, three projects)

Four of five long searches traced in process snapshots were such orphans; every one began as a default-timeout
(120 s) foreground Bash call that was moved to the background and then stopped with TaskStop:

| Command | Agent action | Left running after "Successfully stopped" |
|---|---|---|
| grep -rn "…" --include=*.md --include=*.py --include=*.json -l . \| grep -v node_modules \| head -30 | TaskStop 4 s after the timeout | 13 min, 1 992 MB working set, until killed by hand |
| find / -path "*…*" -name "….json" \| head -3 | TaskStop 5 s after the timeout | ≥ 3.7 h, 3.29 million handles (it walks /proc/registry) |
| … ; du -sh . \| tail -1; … | TaskStop 11 min later | 15 min; then wrote its result into the output file after [killed] |
| … && du -sh temp \| tail -1 && … | TaskStop 8 min later | 1.5 h, long after the agent itself had finished |

The agent is told the task is stopped, so it never looks again; the memory and handles stay taken.

Suggested fix

On Windows, when the shell is Git Bash / MSYS2, kill by the MSYS process group or parent chain rather than by the
Windows parent pid: e.g. before terminating the shell, ask it for its descendants (ps -l: PID PPID PGID WINPID),
or signal them from inside (kill -TERM -- -$$ / a trap in the wrapper), or put the task's shell into a Windows
Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE so that every process created under it dies with it.

Not checked

  • Whether Esc / closing the session leaves the same children behind.
  • Whether the task kill at a subagent's final response behaves the same way (it writes the same [killed] mark).

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗