claude-code - 💡(How to fix) Fix Hourly local-agent-mode routines leak `claude-code` CLI processes — previous run not terminated when next fire spawns [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#56190Fetched 2026-05-06 06:34:47
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Timeline (top)
labeled ×4commented ×1

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.

Root Cause

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.

Fix Action

Fix / Workaround

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.

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

Code Example

ps -axo pid,ppid,etime,pcpu,command | grep '[c]laude-code'

---

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]
RAW_BUFFERClick to expand / collapse

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.

extent analysis

TL;DR

The likely fix involves modifying the local-agent-mode session manager to properly terminate the claude-code CLI subprocess after each routine fire is completed.

Guidance

  • Review the session manager's code to ensure it sends a SIGTERM signal to the spawned claude-code CLI process after receiving the final agent message or when the routine fire is marked as finished.
  • Consider implementing a mechanism to close stdin on the CLI subprocess to prevent it from remaining alive indefinitely.
  • Verify that the session manager correctly handles errors and edge cases, such as when the CLI process fails to exit after receiving SIGTERM.
  • Investigate the ~/Library/Logs/Claude/cowork_vm_node.log log file to understand why there are no matching [disconnect] or VM-teardown lines after agent turns complete.

Example

# Example of sending SIGTERM to a process
kill -SIGTERM <PID>

Replace <PID> with the actual process ID of the claude-code CLI subprocess.

Notes

The provided forensic evidence and hypothesis suggest that the issue is related to the session manager's failure to terminate the claude-code CLI subprocess. However, without access to the session manager's code, it is difficult to provide a more specific solution.

Recommendation

Apply a workaround by implementing a script that periodically identifies and terminates orphaned claude-code CLI processes, similar to the user's current workaround. This will help mitigate the issue until a proper fix can be implemented in the desktop app's session lifecycle.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING