Backgrounded (&) shell children survive a Bash-tool timeout and leak as PID-1 orphans, related to #45717
Title
Backgrounded (&) shell children survive a Bash-tool timeout and leak as PID-1 orphans, related to #45717
Description
Related to #45717 (closed "not planned"), which describes SIGTERM on Bash timeout over-killing (taking down the whole Claude Code process instead of just the child). This report is the inverse symptom of what looks like the same root cause: process-group scoping around Bash tool timeouts is not correct, and in this case it under-kills, leaking orphaned processes indefinitely.
Repro
- In a Bash tool call, background several CPU-bound children via shell job control, then rely on
kill $BUSY(BUSY=$(jobs -p)) at the end of the same script to clean up:
``sh``
for c in $(seq 8); do (while :; do :; done) & done
BUSY=$(jobs -p)
npm test ...
kill $BUSY 2>/dev/null
- Let the surrounding command run long enough to hit
BASH_MAX_TIMEOUT_MS(default 600000ms / 10 min), so Claude Code kills the command before it reaches thekill $BUSYline. - Observe: the
zsh -c ...process for that tool call is killed (matches the "Exit code 143 — Command timed out after 10m 0s" result shown to the model), but the already-forkedwhile :; do :; donechildren are not — they get reparented to PID 1 and continue running indefinitely, pinning CPU cores.
Impact observed
A single Claude Code background session (spawned via the Claude Desktop app, not an interactive terminal) repeated this pattern several times over ~2 hours of iterative flaky-test debugging. By the time it was caught, ~30+ orphaned while :; do :; done processes were running, driving the 1-minute load average to ~700+ on an otherwise idle machine and pinning all cores (audible fan noise was the first symptom noticed).
Expected behavior
When the Bash tool times out a command, it should kill the command's entire process group (e.g. spawn via setsid/new process group and signal the group, per the fix proposed in #45717), not just the immediate PID — so that any &-backgrounded children die along with their parent shell.
Workaround in use
Avoiding shell-level & backgrounding inside synchronous Bash tool calls entirely, in favor of the tool's own run_in_background: true, which is tracked and cleaned up by Claude Code's own task lifecycle regardless of timeout behavior.
Environment
- Claude Code 2.1.219
- macOS (Darwin), session spawned via Claude Desktop app background task (not an interactive terminal)