Summary
A Python process started by a Claude reviewer's Bash tool kept running for about 4 days after its review run ended. It was using a full CPU core and had been reparented to launchd (PPID 1). Its working directory was the reviewer's workbench checkout, which had already been deleted. Neither cr's process-group kill nor its claude bg job cleanup could reach it.
Timeline (from the reviewer's Claude session transcript)
- The reviewer runs
python3 - <<'EOF' ... EOF against package-lock.json in its workbench repo. The script has an infinite loop: base.rsplit('/node_modules/', 1)[0] never changes a top-level node_modules/<pkg> path.
- After 120s, Claude Code moves the command to a background task.
- The reviewer notices and runs
...; pkill -f 'python3 -'.
- Claude Code reports the background task as
failed with exit code 144, but only the wrapper shell died. On macOS python3 execs .../Python.app/Contents/MacOS/Python -, so the case-sensitive python3 - pattern never matched the real interpreter.
- The interpreter is reparented to PID 1. Claude Code considers the task finished, and the review completes normally.
- The workbench is deleted, but the process keeps running until it is killed by hand about 4 days later.
Why cr's existing safeguards miss it
llm.LaunchProcess uses Setpgid and kills -pgid, but only when the context is cancelled. Claude Code's Bash tool shells lead their own process groups, as the existing subprocessWaitDelay comment says. Locally, the tool shell's PGID is its own PID, not the claude PGID.
cleanupClaudeBGJob runs claude bg stop only when resultErr != nil. Even then, it would only stop jobs Claude Code still tracks, and this one was already marked failed.
- The data-lifecycle orphan sweep and the workbench deletion after a successful review only remove directories, never processes.
Suggested fix (defense in depth)
Before deleting a reviewer workbench, or at the end of a reviewer task, find and kill any process whose cwd is inside that workbench. A simple way is lsof -t +D <workbench> on darwin/linux or /proc/*/cwd on linux. The processes cr launches are the only ones expected to run there, so this is safe to do and catches everything that escapes the process group, whatever launched it.
A related upstream Claude Code issue is that a background task is marked finished while its descendants are still alive. cr can't rely on that being fixed.
Environment
- cr 0.10.302 (fb9759a)
- Claude Code 2.1.269 / 2.1.270, background reviewer mode
- macOS 15 (Darwin 24.6), Python 3.11
Summary
A Python process started by a Claude reviewer's Bash tool kept running for about 4 days after its review run ended. It was using a full CPU core and had been reparented to launchd (PPID 1). Its working directory was the reviewer's workbench checkout, which had already been deleted. Neither cr's process-group kill nor its
claude bgjob cleanup could reach it.Timeline (from the reviewer's Claude session transcript)
python3 - <<'EOF' ... EOFagainstpackage-lock.jsonin its workbench repo. The script has an infinite loop:base.rsplit('/node_modules/', 1)[0]never changes a top-levelnode_modules/<pkg>path....; pkill -f 'python3 -'.failedwith exit code 144, but only the wrapper shell died. On macOSpython3execs.../Python.app/Contents/MacOS/Python -, so the case-sensitivepython3 -pattern never matched the real interpreter.Why cr's existing safeguards miss it
llm.LaunchProcessusesSetpgidand kills-pgid, but only when the context is cancelled. Claude Code's Bash tool shells lead their own process groups, as the existingsubprocessWaitDelaycomment says. Locally, the tool shell's PGID is its own PID, not theclaudePGID.cleanupClaudeBGJobrunsclaude bg stoponly whenresultErr != nil. Even then, it would only stop jobs Claude Code still tracks, and this one was already markedfailed.Suggested fix (defense in depth)
Before deleting a reviewer workbench, or at the end of a reviewer task, find and kill any process whose cwd is inside that workbench. A simple way is
lsof -t +D <workbench>on darwin/linux or/proc/*/cwdon linux. The processes cr launches are the only ones expected to run there, so this is safe to do and catches everything that escapes the process group, whatever launched it.A related upstream Claude Code issue is that a background task is marked finished while its descendants are still alive. cr can't rely on that being fixed.
Environment