anthropics / anthropics/claude-code

Remote daemons never release chat processes and outlive their clients (self-hosted, ~300MB/chat, two hard outages)

Abierto
#92,548 0 comentarios 0 reacciones 0 asignados Ver en GitHub
area:self-hosted-environments bug has repro perf:memory platform:linux
Lenguaje dominante
Python
Estrellas
145k
Forks
23.1k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

### Preflight Checklist

- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code

### What's Wrong?

On a self-hosted Linux server, Claude Code's remote daemon accumulates one ~300 MB `ccd-cli` process per chat and never frees them. Reopening a chat spawns a duplicate. Each client update starts a new daemon and abandons the old one, still holding everything it had. This has taken the machine down twice.

**Environment:** Unraid, Linux 6.12.24, 15 GB RAM, no swap. Transport is Tailscale SSH. Clients are the desktop and mobile apps only — no terminal use, which matters because the only mitigation the daemon offers requires a shell.

Four distinct behaviours, in order of importance:

1. **Chat processes are never released.** Opening a chat starts a `ccd-cli` process holding 250–420 MB. It stays resident indefinitely, long after the chat is finished.
2. **Reopening a chat spawns a duplicate.** The previous process is not reused or released. This happens *within a single daemon*, so device-switching is not required to reproduce it.
3. **Daemons outlive their clients.** Each client update starts a new daemon on a new socket; the old daemon keeps running indefinitely with every chat process it was holding. One was alive 11 days, 4 days after anything last connected to it.
4. **Processes keep running binaries deleted from disk.** The daemon prunes old CLI builds (`-cli-keep`, default 3) but does not restart processes using them. Version 2.1.247 was pruned and two processes were still executing it.

There is no idle timeout, session cap, or eviction setting anywhere in the daemon's flag list, so nothing reclaims any of it.

**Impact.** This host also runs a Home Assistant VM, Plex, Immich and ~20 other containers. Two hard outages, both traced to this:

| Date | Load | State | Cause |
|---|---|---|---|
| 2026-08-31 | 87 | ~60 processes in D state, disks at 100% | 23 abandoned session processes holding 8.1 GB |
| 2026-09-01 | 97 | 324 MB free of 15 GB | 59 session processes holding ~15 GB |

With no swap there is no page cache once memory fills, so the machine stops responding rather than slowing down. Both times, every printer on the network dropped its MQTT connection simultaneously.

**Related issues.** #92059 (Windows: memory-pressure governor evicts `0 of 0` idle sessions while 15+ are live, vetoed 587 times by `[CCD] Skipping pause for session local_ - remote control is active`) is the same theme from the client end. This report is the server-side counterpart and adds the duplicate `--resume` UUIDs, showing processes are *created* redundantly rather than merely retained. #49790 requests the opposite behaviour (sessions surviving disconnect). #1935 covers orphaned MCP servers — different component, same lifecycle family. Nothing found covers daemons outliving their clients, or processes running a deleted binary.

### What Should Happen?

1. **A chat's process is released when the chat is closed, or after an idle threshold.** This is the core issue — nothing else matters if processes accumulate without bound.

2. **Reopening a conversation reuses its existing process** rather than spawning a second one against the same `--resume` id.

3. **A daemon exits once its last client disconnects**, or hands its sessions to the successor daemon on a client update, instead of both running indefinitely.

4. **The session lifecycle is visible and controllable from the apps** — a session count and a way to end one. A user on desktop and mobile has no shell to run `-stop` from, and `-stop` is all-or-nothing per daemon anyway, so it cannot be used to release one finished chat while another is in use.

Every mitigation currently available on the user's side is a scheduled process-killer working around the defect. That should not be the price of self-hosting.

### Error Messages/Logs

```shell
No error is emitted — the daemon logs nothing abnormal, which is part of why this went unnoticed until the host fell over. The evidence is in process state.

**1. One conversation, several processes.** Live process list keyed on the `--resume` UUID each process was started with. Four conversations, nine processes:

Conversation PID Daemon socket Age
2e283802... 363856 5b2efa6a 117.1 h
2e283802... 267743 5b2efa6a 68.6 h <- SAME daemon, started ~48h apart
2e283802... 3753765 85fbdb5e 93.1 h
c41780db... 338535 5b2efa6a 117.3 h
c41780db... 3753764 85fbdb5e 93.1 h
f2498e39... 2110967 77f380c3 118.0 h
f2498e39... 958621 4927f78e 2.5 h
59aa5e70... 2110977 77f380c3 118.0 h
59aa5e70... 958053 4927f78e 2.4 h

The second row is the decisive one: two processes for one conversation under the *same* daemon, started roughly 48 hours apart. That rules out device-switching as the explanation.

**2. Daemons outliving their clients.** Each socket directory is one daemon; last-client time is the final `Connection closed` entry in that daemon's own `remote-server.log`:

Socket Started Last client seen Chats held State
85fbdb5e Aug 25 19:22 Sep 2 11:28 2 idle 4 days
77f380c3 Sep 1 10:25 Sep 1 23:39 2 idle 5 days
5b2efa6a Sep 1 11:10 active 4 in use
4927f78e Sep 6 05:48 active 3 in use

The first had gone 11 days since start and 4 since anything connected, still holding two chats. Both idle daemons were self-daemonized and reparented to init, so nothing supervises them.

**3. Processes running a deleted binary.**

on disk: 2.1.255 2.1.258 2.1.260
running: 2.1.247 x2 <- no longer on disk
2.1.255 x3
2.1.258 x2

**4. No lifecycle controls exist.** The daemon's complete flag list — no idle timeout, no max session count, no eviction:

-bridge Connect stdio to the running server
-cli-checksum Expected SHA256 of the compressed CLI .zst
-cli-dir Directory for per-version CLI binaries
-cli-keep How many most-recent CLI versions to keep (default 3)
-cli-url Download URL for the CLI .zst
-cli-version Required CLI version
-cli-zst Path to an already-uploaded CLI .zst
-install Ensure CLI present, prune old versions, print JSON facts
-serve Self-daemonize and run the RPC server
-socket Path to the daemon's Unix socket
-stop Stop the running server (server.shutdown RPC)
-token-file Read auth token from this file at startup, then unlink it
-version Print version and exit

**5. Still reproducing at time of filing.** Re-checked while writing this report — two conversations each running twice on the same daemon, one copy on 2.1.247 (deleted from disk) and one on 2.1.258:

PID Conversation Version RSS Age
338535 c41780db 2.1.247 397MB 121.9 h
3911065 c41780db 2.1.258 418MB 96.8 h
363856 2e283802 2.1.247 277MB 121.7 h
2677438 2e283802 2.1.258 370MB 73.2 h

Also observed the same afternoon: the client-to-server link closing every 2–6 minutes for hours (`[Server] Connection closed: @` at 12:32:26, 12:32:29, 12:34:36 x2, 12:40:12, 12:44:02, 12:44:03, 12:52:33, 12:54:33, 13:00:32 x2), with a session going unresponsive mid-turn. Possibly related, reported here for completeness rather than as a confirmed part of the same defect.
```

### Steps to Reproduce

1. Set up Claude Code Remote Control on a Linux host (here: Unraid, Linux 6.12.24, 15 GB RAM, no swap), reached from the desktop and mobile apps over Tailscale SSH.

2. Open a conversation. A chat process appears, holding 250–420 MB:

```
ps -eo pid,ppid,rss,etimes,args | grep ccd-cli | grep -v grep
```

3. Close the conversation and leave it closed. Re-run the command hours later — the process is still resident. It is never released.

4. Reopen the same conversation. Re-run the command: a **second** process now exists started against the same `--resume` UUID, and the first is still there. No device switch is required; this reproduces on one client.

5. Let the client auto-update (or update it), then open a chat. A new daemon appears on a new socket under `/root/.claude/remote/run/`, while the previous daemon keeps running with every chat process it was holding:

```
ps -eo pid,etimes,args | grep -- '--serve'
ls -1 /root/.claude/remote/run/
```

6. Compare each daemon's last client against its held processes:

```
grep -a 'Connection closed' /root/.claude/remote/run//remote-server.log | tail -1
```

Daemons with no client for days are still holding chat processes.

7. Continue normal use for several days. Process count and resident memory grow without bound; nothing reclaims them. On this host it reached 59 processes / ~15 GB and took the machine down.

**Minimal version:** open a chat, close it, reopen it, and inspect `ccd-cli` processes. Two processes for one conversation, neither released, is the whole bug.

### Claude Model

None

### Is this a regression?

Yes, this worked in a previous version

### Last Working Version

_No response_

### Claude Code Version

ccd-cli 2.1.260 (server-side; 2.1.247, 2.1.255 and 2.1.258 also resident). Daemon build 7d193f89fc02cf1035a391245312e34ad419f63e, built 2026-08-25T00:11:43Z. Bridge build 4534d8648b686881955c6f13baf46ae72ee72f4c.

### Platform

Anthropic API

### Operating System

Other Linux

### Terminal/Shell

Other

### Additional Information

**Access pattern.** This host is used exclusively through the Claude Code desktop and mobile apps via Remote Control over Tailscale SSH. There is no terminal use, which is why `-stop` is not a usable mitigation — it requires a shell, and it is all-or-nothing per daemon, so it cannot release one finished chat while another conversation on the same daemon is in use.

**Version coverage.** The behaviour has been consistent across every CLI version resident on this host: 2.1.247, 2.1.255, 2.1.258 and 2.1.260. Latest published at time of filing is 2.1.263; this host had not yet picked it up. Note that a version change is itself one of the triggers — each update starts a new daemon and abandons the previous one — so updating is not expected to clear the accumulated processes, only to add another daemon.

**Host context.** The machine also runs a Home Assistant VM (~2.1 GB), Immich (~1.5 GB across three processes), Plex and roughly twenty other containers. It has no swap configured, which is why memory exhaustion presents as the host becoming unresponsive rather than degrading gradually.

**Prior mitigation attempted.** Two idle daemons were shut down manually using the daemon's own `-stop` RPC, which released four abandoned chat processes and recovered ~478 MB. That is a one-time clear-up, not a fix; the processes accumulated again over the following days, and a daemon currently hosting a live conversation cannot be stopped without killing that conversation.

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Línea de trabajo

Start with the remote daemon launched by `--serve`, its per-socket `remote-server.log`, and the `ccd-cli` processes identified with `--resume`. Reproduce the minimal open-close-reopen sequence, then compare process lists and `Connection closed` timestamps; done should include releasing closed chats, avoiding duplicate processes, and cleaning up daemons after their clients disconnect.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
linux, python
Área
backend, infrastructure
Tipo de issue
Error
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Activo
Claridad
Bastante claro
Aptitud para principiantes
32/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.