[BUG] [Linux] Transient daemon respawns/displaces itself every ~52s while `claude agents` is open (storm of ~1 `claude daemon run`/sec); drops claude.ai bridge + MCP each cycle
Summary
On Linux (running inside a code-server Docker container), the transient background-job daemon is replaced every ~52 seconds, indefinitely, whenever a claude agents view is open. Each cycle tears down and re-establishes the claude.ai bridge and every MCP connection, and on older versions killed background workers outright. The daemon log shows 13,715 daemon start lines accumulated, with a metronomic ~52.13s cadence. There is no error logged — the daemon just gets displaced by a freshly-spawned one.
This is not the Windows auto-updater reinstall loop (#64104) — versions are identical across restarts here; nothing is reinstalling.
Environment
- Claude Code: 2.1.175 (reproduces; first seen on 2.1.150)
- Node: v22.22.3
- OS: Ubuntu 24.04.4 LTS, kernel 6.12.54 (Unraid host), inside a code-server Docker container
- Install: native (
~/.local/share/claude/versions/...) - Auth: claude.ai (Pro/Max), Remote Control + Agents view in use, several MCP servers configured
What happens
claude daemon status shows the serving daemon pid flipping every ~56s (confirmed by sampling — 2 flips in 2 minutes). The daemon log shows a new supervisor every ~52.13s:
[2026-06-13T04:19:48.884Z] [supervisor] ─── daemon start ─── version=2.1.175 pid=638325 origin=transient
[2026-06-13T04:20:41.005Z] [supervisor] ─── daemon start ─── version=2.1.175 pid=638554 origin=transient Δ52.12s
[2026-06-13T04:21:33.139Z] [supervisor] ─── daemon start ─── version=2.1.175 pid=638818 origin=transient Δ52.13s
[2026-06-13T04:22:25.264Z] [supervisor] ─── daemon start ─── version=2.1.175 pid=... origin=transient Δ52.13s
[2026-06-13T04:23:17.396Z] [supervisor] ─── daemon start ─── version=2.1.175 pid=... origin=transient Δ52.13s
Underneath, there is a storm of ~1 short-lived claude daemon run process spawned per second; almost all lose the lock race and exit in <1s, and one wins every ~52s and displaces the running daemon. Each spawn's parent is the claude agents view (and occasionally a --bg-spare worker):
daemon-run pid=629552 ppid=626854 parent=[claude agents]
daemon-run pid=630245 ppid=626961 parent=[... --bg-spare ...]
daemon-run pid=630254 (already exited)
daemon-run pid=630292 (already exited)
... ~1 new daemon-run pid per second ...
Each new supervisor logs only the routine adopt/spare lines — no error explaining the displacement:
[supervisor] ─── daemon start ─── version=2.1.175 pid=... origin=transient
[supervisor] workers=0
[bg] bg adopt: adopted=4 respawned=0 dead=0
[bg] bg orphan-spare reap: 1
[bg] bg spare spawned host pid=...
Historically the log also showed idle 5s with no clients — exiting and another daemon won the lock race (pid=...) — exiting, consistent with a spawn/lock-race storm.
Impact
- Background agents stop unexpectedly / "Interrupted" with no user action. On 2.1.159 / 2.1.172, the handoff killed workers —
bg adoptloggeddead=3,dead=4, up todead=11. Agent status dots ended up wrong (job finished but marked failed; "needs input" shown with nothing pending) because workers were killed before emitting their final result. - claude.ai Remote Control + every MCP server disconnect/reconnect every ~52s — constant connection churn, visible as repeated MCP connect/disconnect notifications and remote-control drops.
- Log + process-table noise (13,715 restarts; ~1 spawned process/sec).
Mitigation already observed
2.1.175 fixed the worst part: daemon handoffs now adopt workers (dead=0 — 12,343 of 12,474 adopts) instead of killing them, so background jobs survive the recycles much better. (dead>0 counts are almost all from the older 2.1.159/172 days.) But the ~52s churn itself is unchanged on 2.1.175.
Reproduction / things tried (none stop the churn)
claude update2.1.172 → 2.1.175.- Full clean restart: exit all sessions,
claude daemon stop --any, reopenclaude agents. - Closing a duplicate
claude agentsview (had two open) — a single agents view alone still churns at the identical 52.13s cadence, so it's not a multi-instance amplifier. - control.sock is present and freshly recreated each cycle;
/tmpis on stable container storage (not tmpfs / not being wiped), so it's not a stale-socket or cleared-/tmpissue.
Because service install is disabled in this version (claude daemon --help: "Service install is disabled in this version — the daemon runs on demand and exits when the last client disconnects"), there's no persistent-daemon workaround available to the user.
Expected
A single claude agents view should keep one stable daemon alive for its lifetime, not respawn/displace it every ~52s.
Asks
- Is the agents view's "ensure a daemon" path racing its own lock and respawning ~1/sec?
- Could a persistent/long-lived daemon option be re-enabled for Linux to avoid the transient-respawn storm?
Showing cached comments. Read the full discussion on GitHub ↗
8 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Additional evidence: worker kills correlate with daemon idle-exit while background jobs run unattended (still happening on 2.1.175)
2.1.175 reduced but did not eliminate worker kills. The kills are not from the rapid 52s hot-handoffs (those all log
dead=0— workers adopted cleanly). They happen on the cold start after the daemon has idle-exited, when background jobs were left running with no attached client.Concrete example from today (UTC), daemon-start intervals:
During the 56-minute gap there were no
daemon startlines at all — i.e. the transient daemon had fully exited (no attached client), even though background sessions were still supposed to be running. On reattach, the cold-start daemon could only adopt 2 survivors and reporteddead=3. The user observes this as "2–3 background agents stopped while I was away; I have to resume them."Every
dead>0adopt today lands on a reattach-after-gap, never during the steady 52s churn:This contradicts the documented promise that the daemon "waits for [background workers] to settle" before exiting — background jobs left running unattended are being orphaned (
ptyhost_orphan_watchdog) when the daemon idle-exits, then counted dead on the next reattach.Two distinct problems, then:
claude daemon run/sec storm). Harmless to workers (dead=0).--bgsessions. This defeats the purpose of background agents (running work while away).Ask
Background sessions should keep the daemon alive (or a persistent/service daemon should be available on Linux) so
--bgwork survives while no interactive client is attached. Today, leaving background agents running unattended on Linux reliably loses them.+1 this is impacting me as well. i'm on version 2.1.181
This is not a duplicate.
I can confirm the issue. Currently I fixed it by:
claude daemon stop --anyclaude agentsWhy this works: Stale entries in
roster.jsonwithpid=0or a mismatchedcliVersion(e.g.2.1.179after upgrading) appear as "running" inclaude daemon statusbut contribute 0 toliveHandleCount. The daemon's idle check usesM() = leaseCount() + liveHandleCount()— when both are 0 it fires the idle-exit timer. With stale entries present, the accounting is off and the daemon exits prematurely.Note: This is a workaround, not a fix. The roster can accumulate stale entries again. The upstream fix would enforce cleanup of entries with
pid=0or mismatchedcliVersionon daemon start.Can confirm the ~52s respawn pattern, and want to flag a dangerous side effect of the roster.json-clearing workaround posted above (stale
pid:0/mismatched-cliVersionentries corrupting the idle-check).It doesn't just stop the premature idle-exit — it can trigger a different bug on the very next organic restart.
Repro on Linux, claude 2.1.197, with 14 live background workers attached across 4 projects:
claude daemon stop --any --keep-workers— this part is safe; bg worker processes are systemd-reparented (PPID = the user's systemd instance, not the daemon's pid), confirmed viaps -o ppid, so they survive the supervisor stopping.rm ~/.claude/daemon/roster.jsonbg adopt: adopted=14 respawned=0 dead=0) — looked fixed.This killed 2 real, in-progress background agent conversations (each with hundreds of KB of accumulated conversation history) and silently replaced them with blank new sessions occupying the same pool slot — same cwd, brand-new sessionId, so it's easy to not notice it's not a resume. A 3rd worker was killed outright with no replacement at all.
Important control data point: the steady-state ~52s churn with an intact roster.json had been running harmlessly for 24+ hours before this (
dead=0every single cycle in the logs). It was specifically deleting roster.json that pushed the daemon into this other, more destructive code path.Worth a prominent warning in this thread since the roster-clear is the top suggested workaround here, and worth fixing the underlying handover-race/orphan-reap logic — right now there's no safe way for a user to recover from the original idle-exit bug without risking this one.
Confirming on 2.1.197 (Linux/WSL2), plus a sharper failure mode: the ~52s displacement can leave an orphaned
control.sock→EADDRINUSE→ the agent view goes dead for several minutes.Still reproduces on 2.1.197 (the newest data point in the thread so far was 2.1.181). Setup: multiple Claude instances, each with its own
CLAUDE_CONFIG_DIRand its own per-instance daemon under/tmp/cc-daemon-1000/<id>/. The affected daemon was transient (origin: transient — started on-demand by claude), not pinned, with three concurrentclaude agentsholders open from different working directories.1. The ~52s self-displacement (this issue):
interleaved with a steady
bg adopt: adopted=N respawned=0 dead=0heartbeat every ~52s ± 0.2s. Workers stay healthy the whole time (dead=0), which is why individual sessions keep working even as the bridge drops each cycle. On a transient daemon everyclaude agentsholder is a fresh lock-race entrant, so there is always a new contender to displace the incumbent — more concurrent holders → more churn.2. New failure mode — orphaned
control.sock→EADDRINUSE:Periodically the displacing supervisor wins the lock but cannot bind the control socket the displaced one still holds, and dies:
After that line the daemon log goes quiet for an extended window, during which no supervisor holds a reachable control pipe. In that window the agent-view TUI's per-session control round-trips fail, so every session click errors until a later on-demand spawn finally clears the orphaned socket and rebinds cleanly. Operator-visible symptom: "the agent view is dead for a few minutes and errors on every click, then recovers on its own." Workers are unaffected throughout (the adopt loop keeps re-adopting them), so it looks like only the view is broken, not the background jobs.
Likely fix: on lock-loss / abnormal exit the transient daemon doesn't reliably
unlink()itscontrol.sock, so the successor inherits an orphaned socket file andlisten()returnsEADDRINUSE, then exits too. Having the successor unlink a stale/unowned socket beforelisten()(unlink-then-bind) would let the lock winner bind immediately instead of dying — closing the dead window even in the cases where the ~52s displacement still happens.Happy to share more of the daemon log if it would help.
Not reproducing on 2.1.205 (Linux/WSL2) — the ~52s cadence stopped exactly at the 2.1.198 → 2.1.199 boundary, and has stayed stopped for ~7 days.
Following up on my comment above, which reported this live on 2.1.197. That report is now stale, and since it was the newest datapoint in the thread I'd rather correct the record than leave it standing.
Where it stopped. Same setup as before: several Claude instances, each with its own
CLAUDE_CONFIG_DIRand its own transient per-instance daemon. One instance's log caught the transition mid-storm. The last restarts are the familiar metronome, all on 2.1.198:The 23:44:03 line is the storming 2.1.198 daemon self-restarting one final time, onto 2.1.199. The metronome stops there. The next event on that instance is 17.2 hours later, and it is a clean
shutting down (cause=upgrade, uptime=61862s).It has stayed stopped. Supervisor lifetimes since, across 2.1.199 → 2.1.205 (~6.9 days; one instance's series shown, the others match):
...and the live 2.1.205 supervisors are at 25.0 h and counting. Every one of those terminated because the binary self-upgraded, not because it was displaced. A storming daemon never accumulates 70 hours of uptime. (For completeness: one instance also logged a
cause=idle, uptime=178sshutdown — that is the transient daemon's idle reap with no workers attached, not a displacement.)Load is higher than when I filed, not lower: 12 concurrent
claude agentsholders across the instances right now, versus the three in my original report. On a transient daemon, more concurrent holders was the aggravating factor. It no longer bites.The
EADDRINUSEvariant is gone too. The orphaned-control.sockfailure I described in my previous comment —[EADDRINUSE] listen ... /tmp/cc-daemon-<uid>/<id>/control.sock — control pipe unavailable; exiting, followed by a multi-minute window where the agent view errors on every session click — has not occurred on any instance since 2026-07-01T20:48Z.The 2.1.199 changelog entry — "Fixed the background-agent daemon on Linux killing itself and every running agent every ~50 seconds after an unclean shutdown left a corrupted worker record" — lines up exactly with where my logs show it stop.
Caveat, and why I'm not suggesting you close this. My environment is WSL2; the repro in the original report is a Docker/code-server container. A non-reproduction on my platform is not a fix on yours, and I have no visibility into whether the corrupted-worker-record precondition can still be reached by another path. Offering this as a Linux-side datapoint, not a verdict.
Cross-link: the
EADDRINUSEfailure mode I described in my comment above is filedseparately as #72334, which root-causes it from the binary rather than the logs — the
transientsupervisor hard-exits on the control-socket bind race where theservicepath degrades gracefully on the same failure (asymmetric
.catch), with the regressionwindow pinned to 2.1.185 → 2.1.195. That is a sharper account than my log-based
inference, and it looks like the actionable one for the socket half.
Read together: this issue is the ~52s displacement loop; #72334 is what happens when a
displacement lands on a socket the predecessor has not released.
Possibly the same family on other platforms: #72233 (macOS Apple Silicon, same ~52s
respawn) and #72898 (Windows, stale
daemon.lock+ PID reuse after unclean shutdown).