Skip to content

Claude reviewer tool processes can outlive the review run (orphaned process at 100% CPU for 4 days) #613

Description

@zzwong

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)

  1. 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.
  2. After 120s, Claude Code moves the command to a background task.
  3. The reviewer notices and runs ...; pkill -f 'python3 -'.
  4. 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.
  5. The interpreter is reparented to PID 1. Claude Code considers the task finished, and the review completes normally.
  6. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions