Scheduled tasks leak their process pair: work completes with end_turn but the session never exits (~16 GB/day)
Summary
Every scheduled-task run leaks its process pair. The task's work completes normally and the turn ends cleanly, but neither the session process nor its disclaimer parent ever exits. They accumulate indefinitely.
On a machine with one hourly task plus ~10 daily ones this is ~36 leaked pairs/day, roughly 16 GB of resident memory per day, until something kills them manually.
Environment
- macOS 15 (Darwin 25.3.0), Apple Silicon
- Claude Code 2.1.237, launched by the desktop app's scheduled-task runner
- Also observed on 2.1.234
What happens
- A scheduled task fires.
- It runs correctly — in my case a ~21 second run of a local Python script.
- The session emits its final assistant message with
stop_reason: end_turn. The transcript's last write is in the same minute it started. - The process does not exit. It sleeps indefinitely, holding ~220 MB.
Observed one still alive 29 hours after finishing a 21-second job.
Evidence
Every leaked run is a pair — a Contents/Helpers/disclaimer parent plus the claude child:
PID PPID ELAPSED STAT CPUTIME
37942 1145 03:56:31 S 0:00.00 <- disclaimer parent, zero CPU
37943 37942 03:56:31 S 2:33.42 <- session child
The child:
- has no subprocesses (so it is not a local MCP server holding it open)
- has fds 0/1/2 as unix sockets to the still-alive parent — neither end closes
- holds 3 ESTABLISHED HTTPS connections to the API
- state
S, accumulating a small amount of CPU forever (event loop still ticking)
Suspect
I examined the transcripts of 33 leaked sessions spanning 8 different scheduled tasks. All 33 contain a bridge-session entry with lastSequenceNum: 0 — a cloud-side session opened, never used to carry a single message, and apparently never torn down:
{"type":"bridge-session","bridgeSessionId":"cse_...","lastSequenceNum":0,
"ownerAccountUuid":"...","ownerOrganizationUuid":"..."}
That correlates 100% with the leak and would explain the three open HTTPS connections. I can't tell from outside whether the bridge is the cause or another symptom of the same teardown failure.
The queue is clean — enqueue then dequeue 59 ms apart, nothing pending.
Not the cause
Ruled out by measurement:
- Not the task's script or config. Output is complete and correct every time.
- Not local MCP servers. The leaked process has no children.
- Not one specific task. 8 different tasks all leak; an hourly one just dominates the count.
Reproduction
Any scheduled task, on any schedule. Check afterwards for surviving processes carrying the runner's signature (--disallowedTools AskUserQuestion) whose elapsed time greatly exceeds the task's actual runtime.
I could not reproduce it by invoking the binary directly with the same argv — a manually launched session exits at "Not logged in" before reaching the state where the leak occurs, so the runner's own credentialing appears to be part of the path.
Impact
Silent and cumulative. Nothing errors, nothing logs, and every ordinary health check reports fine — the process is alive, its port responds, and it is using CPU, so anything testing for "is it running" or "is it idle" says healthy. It is only visible as memory pressure and rising load days later.