Remote control: server leaks a ccd-cli process per resumed session on every reconnect (sessions never torn down on disconnect)
Summary
The remote-control server (~/.claude/remote/srv/<hash>/server --serve) re-resumes its whole working set of sessions — one ccd-cli process per session — on every client reconnect, then closes the link on its own 5-minute idle timeout without tearing those session processes down. Every reconnect therefore leaks a full set of session processes. On an always-on Mac used as a remote host, this accumulated until the machine was effectively unusable.
Environment
- macOS (Apple Silicon Mac mini used as an always-on remote host)
- ccd-cli 2.1.247, sessions show
"entrypoint": "claude-desktop" - Clients connecting: Claude Desktop (macOS), iOS app, claude.ai
What we measured
After a weekend of ordinary on-and-off use (~30 connect/disconnect cycles in the server log, ~10 recent unarchived conversations in the working set):
- 148 live
remote/ccd-cliprocesses, including 11–14 simultaneous copies of the SAME--resume=<session-id> - 5.3 GB RSS + 7 GB swap, load average 29 on an otherwise idle machine
- 5 orphaned
server --servedaemons dating back days (PPID 1, nothing supervises or reaps them; an abandoned daemon re-leaks on any later connect) - 229 leaked dirs under
~/.claude/remote/plugins/(919 MB — one per spawn, never cleaned) and multi-MBremote-server.log.oldfiles
Reproduction / diagnosis path
- Use a machine as a remote-control host with several recent (unarchived) conversations.
- Connect from Claude Desktop, go idle. The log shows the server closing the link itself:
closing connection idle 5m2s in both directions. - Reconnect.
psnow shows a secondccd-cli ... --resume=<same-id>process per conversation; the first set is still running. - Repeat.
grep "\[Server\]" ~/.claude/remote/run/*/remote-server.logshows the connect → idle-close → reconnect cycle; the strings kill/cleanup/shutdown/terminate/reap appear zero times in 2.6 MB of log.
The working set that gets re-resumed is the user's recent unarchived conversations (we confirmed it is not pinning — no pins existed), so ordinary usage keeps refilling it; archiving conversations only shrinks the multiplier, it doesn't stop the leak.
Expected
- On connection close (including the server's own idle timeout), the server should tear down the session processes it resumed for that connection, or at minimum reap duplicates when re-resuming the same session id.
- Orphaned
--servedaemons with no clients should eventually exit. ~/.claude/remote/plugins/spawn dirs and rotated logs should be cleaned up.
Secondary effect worth knowing
The overload this caused pushed unrelated headless claude -p automation on the same host past its wall-clock timeouts, which is how we noticed — so the blast radius extends beyond the remote-control feature itself.
We're currently containing this with a cron that kills duplicate --resume= processes (keeping the newest per session id), which works, but the fix belongs in the remote server's disconnect path.
3 Comments
Fixing this, thanks for the report.
Still reproducing on ccd-cli 2.1.280 (2026-09-24), on a Linux remote host (Ubuntu 24.04, 8 cores / 16 GB) that Claude Desktop reaches over SSH. That's after the 09-08 "fixing this" comment, so either the fix hasn't shipped to this channel yet or it doesn't cover this path.
What happened
The host was under heavy load and the desktop connection dropped. On reconnect,
remote-server.logshows the server re-attaching one session's existing CLI but spawning a fresh CLI for a different session while the original kept running:PIDs 1232139 and 1519573 were running the same session (same transcript) at the same time for about 8 minutes. The orphaned copy was mid-turn and kept launching subagents and tool calls with nobody watching, and both processes appended to the same transcript
.jsonl. It stopped only when we killed it manually. Because the orphan kept doing work, it added load to a host that was already overloaded, so the problem fed itself.So in this version the server clearly can re-attach, but for some sessions on some reconnects it starts a new process instead and never reaps the old one.
A note for anyone copying the cron workaround above
In 2.1.280 the ccd-cli argv carries no
--resume=<id>(or any session flag); the session arrives over stdin. Duplicates can only be identified from the environment:CLAUDE_CODE_HOST_SESSION_ID=local_<uuid>in the ccd-cli's own/proc/<pid>/environ(the desktop session id, stable across the respawn)CLAUDE_CODE_SESSION_ID=<transcript id>in the environ of its children (MCP servers, tool shells)A cron keyed on
--resume=finds nothing on this version. Our current workaround is aSessionStarthook: when a session (re)starts, it terminates the process tree of any otherccd-cliwhose environ carries the sameCLAUDE_CODE_HOST_SESSION_ID(skipping its own ancestors and descendants, so/clearand compaction are safe). It works, but as the original report says, the fix belongs in the server's reconnect path: when it can't re-attach to an existing process for a session, it should terminate that process before spawning a replacement.Expected
On reconnect, either re-attach to the running process for every session, or terminate the old process before spawning a new one for the same session. There should never be two live processes writing one transcript.
Another data point from a Linux remote host (Ubuntu 24.04, Claude Desktop over SSH). Still reproducing through ccd-cli 2.1.284.
How many piled up in one server run (~6 days, counted from
remote-server.log):ccd-cliprocesses started and 36 exited, every one withcode 143, signalled at client request. 76 were still alive when the host went down.Processes from older builds aren't reaped either. The 76 still alive at the end, by version:
| ccd-cli | alive |
|---|---|
| 2.1.280 | 26 |
| 2.1.281 | 30 |
| 2.1.284 | 20 |
After the CLI updated, sessions got new 2.1.284 processes, and 56 processes from the two earlier builds kept running next to them.
For anyone adapting the workaround above: on 2.1.284 the ccd-cli argv includes
--resume=<session-uuid>again, so a cleanup script may need to handle both the argv form and theCLAUDE_CODE_HOST_SESSION_IDenviron form.@amorriscode is a fix for this still planned, and is there a version we should expect it in? We're happy to collect more logs if that would help.