[BUG] Windows Desktop: completed runs' CLI processes are never terminated — every scheduled-task run leaks a persistent process with open stdio pipes and live API connections

Status Open
Reported on v2.1.237
Maintainer reply None cached
Activity 1 comment · opened Aug 26, 2026

Environment

  • Claude Desktop 1.34493.1.0 (Electron 42.9.2), claude-code CLI 2.1.237
  • Windows 11 Pro 10.0.26300

Summary

The Desktop app launches every run — scheduled-task runs especially — as a persistent interactive CLI process (--input-format stream-json --output-format stream-json, no prompt argument, no exit condition). When the run completes, the app marks the session not-running in its own session registry but never closes the child's stdio pipes and never terminates the process. In stream-json interactive mode the CLI waits on stdin indefinitely by design, so an un-reaped child never exits on its own. Every scheduled run therefore leaks one main CLI process, plus its child shells/MCP servers, plus its open TLS connections to the Anthropic API, until something external kills them.

Observed impact (one workstation, 2026-08-25)

  • 61 lingering main CLI processes, all direct children of the app process; 60 of 61 had zero child processes left to wait on.
  • A 12-hour-old leaked process still held its libuv stdio pipes to the app open (verified with Sysinternals Handle: \Device\NamedPipe\uv\13-<appPid> and uv\14-<appPid>) and 2 ESTABLISHED TLS connections to the Anthropic API. The app side had accumulated 229 named-pipe handles from its leaked children.
  • Secondary pile-up: 426 cmd.exe/conhost.exe/node.exe descendants hung under the leaked sessions (one leaked session alone held 25 shell children).
  • ~6.6 GB RAM recovered by manually killing confirmed-dead processes.
  • Under the resulting sustained process/handle load, concurrent process launches on the machine began failing probabilistically with 0xc0000142 / STATUS_DLL_INIT_FAILED (System event log ID 26) — user-visible error dialogs, and in our case each failed launch was a silently-skipped hook script.

Root-cause chain (each link observed directly, not inferred)

  1. The app's per-session state file (claude-code-sessions/.../local_<id>.json) records scheduledTaskId, cliSessionId, and timestamps — but no PID and no process-lifecycle state. The app has no record that an OS process belongs to a session, so nothing in it can ever reap one.
  2. In stream-json interactive mode the CLI blocks on its stdin pipe forever, by design.
  3. Run finishes → app marks the session not-running → stdio pipes stay open → child waits forever → process, RAM, and API connections persist until killed externally.

This is not specific to any one task: every scheduled-task run leaks its run's process (verified across several distinct scheduled tasks the same day).

Suggested fix

On run completion, close the child's stdio pipes and/or terminate the process tree; record the child PID (plus process creation time, so PID reuse can't cause a wrong kill) in the session state file so orphans from a crashed app can be reaped on next start.

Workaround we deployed locally (for other affected users)

A Stop-hook that, for sessions whose state file carries a scheduledTaskId, spawns a detached watchdog which waits for 10 idle minutes, re-verifies process identity by executable name + creation FILETIME, then kills the leaked tree; plus a SessionEnd sweep of leftover descendants. Interactive sessions are never touched. This fully contains the leak but is compensating for lifecycle management the app itself should do.

Possibly related (different platforms/subsystems, same lifecycle-management family): #83218 (macOS MCP subprocesses survive app quit), #89110 (Linux orphans on quit).

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗