Hourly local-agent-mode routines leak `claude-code` CLI processes — previous run not terminated when next fire spawns
Hourly local-agent-mode routines leak claude-code CLI processes — previous run not terminated when next fire spawns
Summary
The Claude desktop app's "local agent mode" — used to execute scheduled Claude Routines locally — spawns a fresh claude-code CLI subprocess on every routine fire, but does not terminate the previous fire's process after that fire's agent turn completes. With an hourly cadence, this produces one orphaned, idle-but-alive claude-code process per fire, indefinitely. Each orphan consumes 5–7% CPU on its own (not combined), holds open MCP server children, and keeps its --output-format stream-json runtime resident.
Quitting the Claude desktop app kills them all (since they're its descendants), confirming the parent relationship — and the only mitigation available to the user.
Environment
- macOS Tahoe (Apple Silicon)
- Claude desktop app, current as of 2026-05-04
claude-codeCLI bundled at~/Library/Application Support/Claude/claude-code/2.1.121/claude.app/Contents/MacOS/claude- Multiple registered hourly Claude Routines running in local-agent-mode
Reproduction
- Register two or more hourly local-agent-mode routines (Claude Routines,
cadence: hourly). - Leave the Claude desktop app running.
- After several hours, run:
````
ps -axo pid,ppid,etime,pcpu,command | grep '[c]laude-code'
Expected
At most one claude-code CLI subprocess per registered routine — the previous fire should be torn down before (or shortly after) the next fire spawns.
Actual
One claude-code CLI subprocess per fire that has occurred since app launch, all parented to /Applications/Claude.app/Contents/MacOS/Claude, each consuming 5–7% CPU on its own, idle but alive.
Forensic evidence
Live ps snapshot taken minutes after a fresh restart of the Claude desktop app, with three hourly fires of the same registered routine already accumulated:
PID PPID ETIME %CPU COMMAND
10074 1 02:49 - /Applications/Claude.app/Contents/MacOS/Claude
12068 12067 02:34 ~6% .../claude-code/.../claude --output-format stream-json --verbose
--input-format stream-json
--model claude-opus-4-7
--permission-prompt-tool stdio
--disallowedTools AskUserQuestion
--permission-mode bypassPermissions
--plugin-dir .../local-agent-mode-sessions/<UUID-A>/<UUID-B>/...
--replay-user-messages
16862 16861 01:34 ~6% .../claude-code/.../claude [identical flags, same UUID-A/UUID-B]
21658 21657 00:34 ~7% .../claude-code/.../claude [identical flags, same UUID-A/UUID-B]
Three orphans with elapsed times of 0:34, 1:34, 2:34 — exact one-hour spacing matching the routine's hourly cadence. All carry identical flag sets. The --plugin-dir paths under local-agent-mode-sessions/ use the same session UUID across all three orphans, indicating they're repeated fires of the same registered routine, each one spawning a new CLI subprocess instead of reusing the prior one or tearing it down.
~/Library/Logs/Claude/cowork_vm_node.log shows constant [postConnect] Installing SDK: subpath=...claude-code-vm entries every few minutes — each postConnect line corresponds to a new VM/SDK initialization. There are no matching [disconnect] or VM-teardown lines after agent turns complete.
Hypothesis
The local-agent-mode session manager:
- Receives a routine fire trigger,
- Spawns a fresh
claude-codeCLI with the routine's prompt, - Receives the final agent message,
- Marks the fire as "finished" and updates state,
- Skips terminating the CLI subprocess (no SIGTERM, no stdin close, no
killof the spawned PID).
The CLI process, with --output-format stream-json and an open stdin, has no signal to exit on its own and remains alive indefinitely until the user quits the desktop app.
Suggested fix
After receiving the final stream-json result message from the spawned CLI (or after the routine fire is marked finished/errored), the session manager should:
- Close stdin on the CLI subprocess, or
- Send SIGTERM to the spawned PID and wait briefly, or
- Both (close stdin first; SIGTERM if still alive after a short grace period).
User impact
Concrete numbers from this user's setup with multiple hourly routines:
- Two registered hourly local-agent-mode routines → up to 48 new orphans per day worst case (2 routines × 24 fires).
- After manually killing several hundred accumulated orphans on 2026-05-02, ~40 had re-accumulated by 2026-05-04 — consistent with ~24+/day net accumulation rate (some fires fail or succeed without leaking; most do leak).
- Each orphan consumes 5–7% CPU on its own, not combined — at 40 alive simultaneously, aggregate sustained CPU pressure is significant and the machine becomes noticeably loaded.
- Each orphan holds open MCP servers, plugin-dir handles, and file descriptors — multiplicative memory/FD pressure on top of CPU.
- The only mitigation available to users today is "quit and relaunch the desktop app," which is poor UX for users running long-lived local agents and forces them to manually break their own automations.
Workaround currently in use
A ps-based snapshot recorder running every 5 minutes via a launchd agent, plus a drafted reaper script that selects orphans by triple criteria (binary path contains claude-code/, command line contains local-agent-mode-sessions/, command line contains --disallowedTools AskUserQuestion), preserves the youngest process per session UUID, and SIGTERMs the rest. Both are user-side workarounds — the real fix belongs in the desktop app's session lifecycle.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗