Bash tool background/timeout-promoted child processes on Windows never signal completion even after finishing and writing valid output
Environment:
- Claude Code CLI 2.1.259
- Windows 11 Pro 10.0.26200
- Bash tool backend: Git Bash (POSIX sh)
- Reproduced with long-running Python/CUDA subprocesses (PyTorch model loading, RTX 4090)
Summary:
When the Bash tool runs a long Python/CUDA subprocess in the background — either via explicit run_in_background: true, or via automatic promotion after the foreground command exceeds its timeout — the child process can finish all of its real work (print its final summary, write a complete and valid output file) and then never exit. It sits alive indefinitely at flat CPU (no thread activity), and Claude Code's own background-task completion notification never fires. Force-killing the process by PID immediately causes the tracked background task to report "completed, exit code 0" — meaning the harness truly was just blocked waiting on process exit.
Reproduction (3 separate instances in one session):
./scripts/benchmark/run_benchmark.sh ... --config-name base_63q ... | tail -60run in the foreground, norun_in_backgroundflag. Exceeded the 600s default timeout and was auto-promoted to background (taskbqb3ts3bx). Underlyingpython.exe(PID 33996, started 13:19:41) wrote a complete, valid JSON result by 13:24 (~5 min runtime), then sat at flat CPU (202.81 CPU-s, 0 delta over an 8s sample) for 25+ more minutes with no notification, until manually killed at ~13:49 — notification fired immediately after.- Same script,
--config-name base_133q, started explicitly withrun_in_background: true, stdout/stderr redirected to a file (> log 2>&1, no pipe). Taskb7ymnxpai. PID 32796 (started 13:33:54) wrote complete valid output by 13:44 (~10 min runtime), then sat at flat CPU (414.81 CPU-s, 0 delta over 8s) for 5+ minutes with no notification, until manually killed. - Same script,
--config-name base_fsim --f-via-similar,run_in_background: true, redirected to file. Taskbrxwm2lgh. PID 29276 (started 13:49:23) actively used CPU while genuinely working (confirmed by rising CPU across repeated samples), printed its final "Overall: PASS" summary and wrote complete output at 13:53, then immediately went flat-CPU (0 delta over 5s) with no notification, until manually killed — notification fired immediately after.
Common factors:
- All three are long-running Python/CUDA subprocesses (PyTorch + CUDA model loading/inference).
- Reproduces regardless of whether backgrounding was automatic (timeout promotion) or explicit (
run_in_background: true). - Reproduces regardless of whether stdout was piped (
| tail) or redirected to a file. - In every case, killing the orphaned PID by hand is what unblocks the notification — the harness's own tracking, not the script, is what's stuck.
Impact: Without noticing external signals (GPU utilization, log tail content, output-file existence), there is no way to tell a backgrounded task actually finished. The agent/user must manually detect completion out-of-band and kill the process by PID, defeating the purpose of the background-task notification system for this class of workload.
Root cause: Not confirmed. Leads worth checking: a lingering non-daemon thread in the PyTorch/CUDA teardown path on Windows that never joins; the harness holding an stdio handle/pipe to the child open without ever detecting EOF; or the harness's Windows process-exit detection not correctly tracking a Git-Bash-spawned, backgrounded/detached child process tree.