Hourly local-agent-mode routines leak `claude-code` CLI processes — previous run not terminated when next fire spawns

Resolved 💬 3 comments Opened May 5, 2026 by theJoshLucas Closed May 8, 2026

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-code CLI 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

  1. Register two or more hourly local-agent-mode routines (Claude Routines, cadence: hourly).
  2. Leave the Claude desktop app running.
  3. 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:

  1. Receives a routine fire trigger,
  2. Spawns a fresh claude-code CLI with the routine's prompt,
  3. Receives the final agent message,
  4. Marks the fire as "finished" and updates state,
  5. Skips terminating the CLI subprocess (no SIGTERM, no stdin close, no kill of 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.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗