Scheduled-task sessions leak orphaned background processes (marked isRunning: false but still alive)

Status Open
Reported on v2.1.217
Maintainer reply None cached
Activity 5 comments · opened Jul 24, 2026

Environment: macOS, Claude desktop app, Claude Code binary version 2.1.217

Summary:
Recurring scheduled tasks (created via the schedule/loop skill, cron-based) leave behind orphaned OS-level processes after each firing, even though the session management API reports the session as isRunning: false (i.e., completed).

Observed impact:
A scheduled task firing every 30 minutes (7am-9pm daily) accumulated 116+ background processes over ~2.5 days, all tied to the same underlying local-agent-mode-sessions plugin directory. Each leaked process was still consuming real CPU (not idle/zombie), and the accumulation drove system load average above 90, noticeably degrading the whole machine.

Diagnosis:

  • list_sessions showed dozens of sessions titled with the task name, spaced ~30 min apart, every one marked isRunning: false.
  • ps on the actual machine showed a disclaimerclaude process pair still alive well after the corresponding scheduled firing should have completed (a task that should run for seconds was still running, still consuming CPU, minutes later).
  • The leaked process's command line had no --resume flag and --disallowedTools AskUserQuestion — consistent with an unattended scheduled-task invocation, as opposed to an interactive session.
  • Killing the orphaned process pair directly (SIGTERM to both the disclaimer parent and the claude child) resolved it for that instance.

Expected behavior:
When a scheduled-task session is marked "done" in the session registry, the underlying OS process(es) it spawned should also be terminated at the same time — not left running indefinitely.

Frequency risk:
Any recurring scheduled task appears susceptible, scaled by firing frequency. A task firing every 30 min accumulates leaks fast; a weekly task would leak far more slowly but is presumably subject to the same root cause.

View original on GitHub ↗

4 Comments

breakthroughchampion · 1 month ago

Confirming this on Windows too — same symptom, different platform, with some additional data that might help narrow down the cause.

Environment: Claude Code 2.1.219, Claude desktop app 1.24012.9.0, Windows 11 Home 10.0.26200, 32 GB RAM, ~4 scheduled tasks on hourly cron schedules.

Same core symptom: every scheduled-task run leaves behind a claude.exe child that never exits. Accumulates at roughly one per run until RAM is exhausted, at which point every scheduled task — including unrelated ones — silently stops firing. No error anywhere: the task registry still shows enabled: true, no log records a failure (the task never ran to write one), lastRunAt just stops advancing. I hit this twice — once for ten days before I noticed, once again the same day I found it.

Quantitative data at the point I found it:

| Metric | Value |
|---|---|
| claude.exe processes | 111 |
| Combined working set | 26.84 GB (of 31.86 GB total) |
| Free RAM | 9.81 GB |
| Processes older than the last successful run | 98 |
| Oldest leaked process | ~20 hours |

After terminating only the leaked task processes (75 of them): 111 → 35 processes, 26.84 GB → 7.85 GB working set, 9.81 GB → 16.98 GB free.

Per-process state while leaked: ~380 MB working set, ~300 open handles, ~2.5% CPU each (a live event loop idling, not a blocked read, not a zombie), no children except a conhost.exe. Measured leak rate ~3.75 processes/hour ≈ 1.4 GB/hour — saturates 32 GB in 15-20 hours.

A concrete theory on the mechanism: the spawned command line includes --input-format stream-json, which is streaming-input mode — the session stays open awaiting further input until stdin hits EOF. It looks like the runner sends the prompt, receives the completed result, and then neither closes stdin nor terminates the child, so the child waits forever for input that will never come. That matches the observed state exactly: live event loop, low idle CPU, no work in flight, no exit.

Why I haven't worked around it myself: nothing inside a task's own prompt can close its own stdin or force its own exit — I tried having a task terminate its own process as its last step, and the run just stalled and never completed. A separate janitor process that kills stale claude processes is possible in principle, but I run tasks that I sometimes trigger remotely from my phone, and I can't prove a janitor wouldn't kill a session I'm mid-way through remotely — so that's not a safe workaround either. This really does need the runner itself to reap its children (or close stdin) once a scheduled run's result has been received.

Also worth calling out per this issue's own "Expected behavior": a scheduled task that silently stops firing should surface somewhere. The registry-says-enabled/actually-dead state is what turned my first outage into ten days instead of one hour.

cmsapia · 1 month ago

Confirming on macOS with the same versions as the Windows report above: Claude desktop app 1.24012.9, bundled claude-code CLI 2.1.219 (Darwin 25.5.0, Apple Silicon).

A scheduled task on a */15 * * * * cron accumulated ~185 orphaned claude processes (binary at ~/Library/Application Support/Claude/claude-code/2.1.219/claude.app/Contents/MacOS/claude) over ~27.5 hours — one per firing. Each idled at ~1.5–7% CPU with 7–13 hours of accumulated CPU time; the aggregate caused system-wide sluggishness, heat, and fileproviderd churn. Plain SIGTERM terminates them cleanly. Also observed the same leak ~a week earlier on the same setup before disabling the task.

johnhaskew · 29 days ago

Confirming again on macOS, same versions: Claude desktop app, Claude Code CLI 2.1.219 (macOS 26.6, Apple Silicon).

Found 124 orphaned claude+disclaimer process pairs accumulated over ~4 days, tied to ~9 active scheduled tasks ranging from every-3-hours to weekly. Leaked-process start timestamps lined up precisely with each task's cron schedule/lastRunAt. 54 of the 124 were simultaneously in a runnable state (not idle), ~347% combined CPU, and the resulting load average exceeded 100 on an otherwise idle machine.

One detail not yet mentioned here that might be useful: the CPU/GIL contention from the leaked processes degraded an unrelated local service (a Python Flask dev server on the same machine) badly enough that it looked like a bug in that service's own code — cost real debugging time before uptime + the claude process count revealed the actual cause. Worth calling out because the blast radius isn't just "the Mac feels sluggish," it can actively mislead troubleshooting of anything else running on the same box.

Plain SIGTERM to all 124 (verified none were live interactive sessions or active background subagents first, via isRunning/scheduledTaskId on each --resume=<uuid>'s session record) resolved it immediately and cleanly — every process exited on the first signal, load average back to ~6 within about a minute. Matches every other report here.

Jess-tx · 29 days ago

Another Windows data point, and one measurement that may help rule a cause OUT rather than add another confirmation.

Windows 11, desktop app 2.1.219, 52 scheduled tasks configured. Same shape as everyone above: one resident process per fire. Matched second-exact against each task's recorded lastRunAt rather than by eye, and it was one-for-one on every fire checked (16 of 16 on one day, 4 of 4 the next). A single instance up ~23 hours held 34 orphans at ~6.6 GB. Re-measured on a fresh instance two weeks later: 21 processes, ~5 GB, in the two hours after restart.

The part I have not seen mentioned: these are being left behind by runs that SUCCEEDED, so whatever reaps the process is not simply failing to run because the run errored. Across the whole local history I find 4 server-side overload responses total (all 529, all inside one 24-minute window on a single evening) and zero 500s. That is nowhere near enough failed requests to account for one leaked process per fire, every fire, across weeks.

What there is a lot of is connection resets: 254 ECONNRESETs across 250 transcripts in the last 9 days. I am not claiming those cause the leak, and the direction could easily run the other way. Flagging it only because an ever-growing pool of resident processes on one machine is a plausible contributor to connection churn, and someone with visibility into both would be better placed to judge than I am.

Reporting this because it is easy to mistake for something on your own side. I originally chased it as "constant server errors" and it was not that.

Impact here is memory and disk rather than lost work, and a daily restart of the desktop app clears it, which is the workaround I am on.

(Edited to correct a figure: my first version said zero 529s over an eight-day window. That came from a measurement taken before the 529s occurred, and I should have re-run it rather than restate it. Corrected above with the actual all-time count.)

Showing cached comments. Read the full discussion on GitHub ↗