[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

Status Fixed / completed
Reported on v2.1.175
Maintainer reply None cached
Activity 9 comments · opened Jun 13, 2026 · closed Aug 18, 2026

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

  1. Background agents stop unexpectedly / "Interrupted" with no user action. On 2.1.159 / 2.1.172, the handoff killed workers — bg adopt logged dead=3, dead=4, up to dead=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.
  2. 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.
  3. 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)

  1. claude update 2.1.172 → 2.1.175.
  2. Full clean restart: exit all sessions, claude daemon stop --any, reopen claude agents.
  3. Closing a duplicate claude agents view (had two open) — a single agents view alone still churns at the identical 52.13s cadence, so it's not a multi-instance amplifier.
  4. control.sock is present and freshly recreated each cycle; /tmp is on stable container storage (not tmpfs / not being wiped), so it's not a stale-socket or cleared-/tmp issue.

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?

View original on GitHub ↗

8 Comments

github-actions[bot] · 2 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/64744
  2. https://github.com/anthropics/claude-code/issues/67467
  3. https://github.com/anthropics/claude-code/issues/64104

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

aandersen2323 · 2 months ago

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:

2026-06-13T14:18:28Z  Δ61s
2026-06-13T14:19:20Z  Δ52s
2026-06-13T15:15:41Z  Δ3381s  <-- 56-min gap: no client attached, daemon down
2026-06-13T15:15:41Z  [bg] bg adopt: adopted=2 respawned=0 dead=3   <-- 3 bg jobs orphaned & killed
2026-06-13T15:16:33Z  Δ52s

During the 56-minute gap there were no daemon start lines 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 reported dead=3. The user observes this as "2–3 background agents stopped while I was away; I have to resume them."

Every dead>0 adopt today lands on a reattach-after-gap, never during the steady 52s churn:

2026-06-13T04:40:41Z dead=2
2026-06-13T13:21:49Z dead=2
2026-06-13T15:15:41Z dead=3

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:

  1. Steady ~52s respawn storm while a client is attached (cosmetic: drops claude.ai bridge + MCP each cycle; ~1 claude daemon run/sec storm). Harmless to workers (dead=0).
  2. Daemon does not persist for unattended background jobs — it idle-exits when the last interactive client disconnects, orphaning and killing running --bg sessions. 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 --bg work survives while no interactive client is attached. Today, leaving background agents running unattended on Linux reliably loses them.

jasonkrone · 2 months ago

+1 this is impacting me as well. i'm on version 2.1.181

hbt · 2 months ago
* To prevent auto-closure, add a comment or 👎 this comment

This is not a duplicate.

I can confirm the issue. Currently I fixed it by:

  • claude daemon stop --any
  • clearing the roster.json in ~/.claude
  • restarting claude agents

Why this works: Stale entries in roster.json with pid=0 or a mismatched cliVersion (e.g. 2.1.179 after upgrading) appear as "running" in claude daemon status but contribute 0 to liveHandleCount. The daemon's idle check uses M() = 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=0 or mismatched cliVersion on daemon start.

DLANSAMA · 2 months ago

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-cliVersion entries 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:

  1. 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 via ps -o ppid, so they survive the supervisor stopping.
  2. rm ~/.claude/daemon/roster.json
  3. Triggered a fresh daemon start, which cleanly adopted all 14 live workers (bg adopt: adopted=14 respawned=0 dead=0) — looked fixed.
  4. ~52s later, the next organic restart cycle (not something I triggered manually) hit a race:
[bg] bg: skipped post-adopt sweeps + roster rewrite — daemon.lock is held by pid <pid> (yield/handover in flight)
[bg] bg orphan-reap: 2 roster-less pty host(s)
[bg] bg orphan-spare reap: 9

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=0 every 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.

0xdhx · 2 months ago

Confirming on 2.1.197 (Linux/WSL2), plus a sharper failure mode: the ~52s displacement can leave an orphaned control.sockEADDRINUSE → 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_DIR and 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 concurrent claude agents holders open from different working directories.

1. The ~52s self-displacement (this issue):

[supervisor] another daemon won the lock race (pid=…) — exiting     # ~18:58, 19:04, 19:06, 20:47

interleaved with a steady bg adopt: adopted=N respawned=0 dead=0 heartbeat 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 every claude agents holder 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.sockEADDRINUSE:

Periodically the displacing supervisor wins the lock but cannot bind the control socket the displaced one still holds, and dies:

[supervisor] bg manager failed to start: [EADDRINUSE] listen EADDRINUSE:
    address already in use /tmp/cc-daemon-1000/<id>/control.sock — control pipe unavailable; exiting

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() its control.sock, so the successor inherits an orphaned socket file and listen() returns EADDRINUSE, then exits too. Having the successor unlink a stale/unowned socket before listen() (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.

0xdhx · 1 month ago

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_DIR and 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:

[2026-07-02T23:40:33Z] ─── daemon start ─── version=2.1.198 origin=transient
[2026-07-02T23:41:26Z] ─── daemon start ─── version=2.1.198 origin=transient   (+52.3s)
[2026-07-02T23:42:18Z] ─── daemon start ─── version=2.1.198 origin=transient   (+52.3s)
[2026-07-02T23:43:10Z] ─── daemon start ─── version=2.1.198 origin=transient   (+52.3s)
[2026-07-02T23:44:03Z] ─── daemon start ─── version=2.1.199 origin=transient   (+52.3s)  <- picked up the new binary

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):

uptime=61862s   (17.2 h)  cause=upgrade
uptime=25439s   ( 7.1 h)  cause=upgrade
uptime=252783s  (70.2 h)  cause=upgrade
uptime=82441s   (22.9 h)  cause=upgrade
uptime=12180s   ( 3.4 h)  cause=upgrade
uptime=75362s   (20.9 h)  cause=upgrade

...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=178s shutdown — 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 agents holders 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 EADDRINUSE variant is gone too. The orphaned-control.sock failure 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.

0xdhx · 1 month ago

Cross-link: the EADDRINUSE failure mode I described in my comment above is filed
separately as #72334, which root-causes it from the binary rather than the logs — the
transient supervisor hard-exits on the control-socket bind race where the service
path degrades gracefully on the same failure (asymmetric .catch), with the regression
window 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).

Showing cached comments. Read the full discussion on GitHub ↗