Scheduled-task runner leaks finished stream-json sessions (never closes stdin / signals exit) → CPU exhaustion

Resolved 💬 2 comments Opened Jun 29, 2026 by mikebronner Closed Jun 29, 2026

What happened

A scheduled task (cron routine) firing every 20 minutes accumulated 136 idle
claude agent-mode processes
over ~21 hours, driving system load to 70 and
CPU to ~85% (66% in kernel). Each leaked process holds a full MCP stack
(computer-use, markdown-vault, plugin MCPs), so the pile-up is heavy.

Root cause is a teardown bug in the scheduled-task ("rpm") runner: it launches
each routine as a persistent stream-json session and never reaps it after the
turn completes.

Diagnosis

The routine runner spawns sessions in persistent interactive mode:

claude --output-format stream-json --input-format stream-json \
       --replay-user-messages --resume <id> ... (full plugin/MCP stack)

A stream-json session only exits on stdin EOF or a signal. After the
scheduled turn finishes, neither happens:

  • stdin stays open, held by the desktop app. The leaked session's fd 0 is a

unix socket whose peer is owned by the Claude desktop app process — confirmed
via lsof. The app never closes it.

  • The turn finished. Example process: 5m38s wall clock, 6.97s CPU total,

then idle.

  • Idle in the event loop, not stuck working. Main thread parked in

kevent64 (libuv waiting for events). All leaked sessions show the identical park.

  • MCP children keep handles alive. markdown-vault-mcp and plugin MCP

servers remain attached as live stdio handles, so Node has no reason to exit.

Every 20-minute fire adds one more idle event-loop + MCP stack.

Proof it's the runner, not the routine's work

The routine's own sub-agents are launched one-shot
(claude -p ... --no-session-persistence) and exit cleanly every time. Only
the runner sessions the app spawns in persistent mode leak. The bug is in the
spawner's completion handling.

Expected

After a scheduled-task turn completes, the runner should close the session's
stdin (or send SIGTERM) so the process and its MCP children exit.

Suggested fix

Either:

  1. Launch routines in -p / --no-session-persistence one-shot mode (matches

the sub-agent path that already exits cleanly), or

  1. Close stdin / signal the session once the final result is received.

Environment

  • Claude Code 2.1.187 (desktop app)
  • macOS (Darwin 25.5.0), Apple Silicon
  • Scheduled task cadence: */20 * * * *

View original on GitHub ↗

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