[BUG] Remote Control (--spawn worktree) self-restarts in a rapid loop, eventually hits systemd start-limit-hit and dies

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

What's Wrong?

claude remote-control --spawn worktree (run as a systemd --user service, Type=simple, Restart=on-failure) enters a rapid restart loop that is not caused by a process crash. The main process exits cleanly (status=0/SUCCESS) after receiving SIGTERM, and the debug log shows this is a self-initiated graceful shutdown ([DEBUG] [bridge:shutdown] SIGTERM received, shutting down[DEBUG] [bridge:shutdown] Skipping archive+deregister to allow resume in one failure mode, or a clean environment deregister in another), not an unhandled exception.

Two distinct failure signatures were observed on the same host, back to back, over ~2.5 hours:

Signature A — poisoned queued work item (before manual cleanup):
A work item polled from the backend (GET .../work/poll) referenced a session created 16 days earlier ("created_at":"2026-08-02T00:25:47.623735Z", "acknowledged_at":null). Every time the parent process spawned a child to resume it (--resume=https://api.anthropic.com/v1/code/sessions/cse_...), the child took ~1s to boot (skills/MCP/hooks load), then failed with:

[ERROR] CCRClient: Epoch mismatch (409, reason=session_not_active), shutting down

exiting with exit_code=1 every single time. The parent logged [ERROR] Bridge session failed: Process exited with error, called /work/.../stop, went idle, and ~18–25s later the entire systemd unit received SIGTERM and restarted — re-polling the same stale work item on the next boot. This repeated for hours (11+ restarts observed in one ~1h window), because the backend kept re-serving the same never-expiring queued item.

Signature B — after deleting the stuck session/environment from claude.ai/code (all sessions in the environment removed "to be safe"):
The epoch-mismatch error disappeared, but the service now registers a brand-new environment_id on every single restart (never reusing the value persisted in the local bridge-pointer.json), gets a clean GET .../work/poll -> 200 (no work, ...), and then within 1–52 seconds receives SIGTERM again, cleanly deregisters (DELETE /v1/environments/bridge/env_... -> 200), and restarts — registering yet another new environment. This repeated fast enough (down to ~10s between restarts) to trip systemd's own StartLimitBurst=3/StartLimitIntervalSec=120 circuit breaker, putting the unit into failed (Result: start-limit-hit) — i.e. Remote Control becomes permanently unreachable until a human manually runs systemctl --user reset-failed && systemctl --user start.

In both signatures, a full 30-minute D-Bus capture (busctl --user monitor org.freedesktop.systemd1) while the unit was masked (systemctl --user mask) showed zero StartUnit/RestartUnit calls against this unit from any external source — confirming the restart is not triggered by cron, a systemd timer, an external script, or a local watchdog. The only D-Bus activity referencing the unit was periodic UnitNew/UnitRemoved pairs from systemd --user itself (routine status-query bookkeeping, unrelated to starting anything). This strongly points to the claude remote-control process restarting itself (very likely via a self-issued systemctl --user restart or equivalent) as an internal recovery mechanism — but the debug log (via --debug-file) shows no error, warning, or log line explaining why it decides to do this before issuing the SIGTERM-to-self.

What Should Happen?

  • A single stale/orphaned queued work item should not be re-served indefinitely by the backend poll endpoint after repeated failures — it should expire, get marked failed permanently, or be skipped after N failed attempts, instead of poisoning the whole environment's poll loop forever.
  • The Remote Control server should not need to fully restart its own process (losing all other live worktree sessions in the process — this happened while other unrelated sessions were attached) to recover from one bad work item; it should isolate/skip the bad item and keep serving the rest of the queue.
  • If a full self-restart really is the intended recovery strategy, it should back off (not restart every 10-90s) and should never restart so fast that it trips systemd's StartLimitBurst, since that leaves Remote Control completely dead until manual intervention — the opposite of what a "resilience" restart is supposed to achieve.
  • The debug log (--debug-file) should log the reason the process is telling itself to restart (currently it just shows SIGTERM received, shutting down with no preceding explanation of why the shutdown was decided).

Error Messages/Logs

# Signature A (before session cleanup) — repeats every ~18-25s cycle:
2026-08-18T10:37:21.854Z [DEBUG] [bridge:init] Adopted session cse_01CmQ1VrvVPe7ZGiAZ2m7kz9 re-queued via bridge/reconnect
2026-08-18T10:37:22.508Z [DEBUG] [bridge:api] GET .../work/poll -> 200 workId=cse_01CmQ1VrvVPe7ZGiAZ2m7kz9 type=session sessionId=[REDACTED]
2026-08-18T10:37:22.508Z [DEBUG] [bridge:api] <<< {"id":"cse_01CmQ1VrvVPe7ZGiAZ2m7kz9", ... "created_at":"2026-08-02T00:25:47.623735Z","acknowledged_at":null, ...}
2026-08-18T10:37:23.349Z [DEBUG] [bridge:session] Spawning sessionId=[REDACTED] sdkUrl=https://api.anthropic.com/v1/code/sessions/cse_01CmQ1VrvVPe7ZGiAZ2m7kz9
# ... child process boots (~1s), loads skills/MCP/hooks, then:
2026-08-18T10:38:48.583Z [ERROR] CCRClient: Epoch mismatch (409, reason=session_not_active), shutting down
# parent:
2026-08-18T10:37:24.873Z [DEBUG] [bridge:session] sessionId=[REDACTED] failed exit_code=1 pid=1710773
2026-08-18T10:37:24.877Z [ERROR] Bridge session failed: Process exited with error
2026-08-18T10:37:25.582Z [DEBUG] [bridge:work] stopWork succeeded for workId=cse_01CmQ1VrvVPe7ZGiAZ2m7kz9 on attempt 1/3
# ~18s later, unprompted:
2026-08-18T10:37:43.139Z [DEBUG] [bridge:shutdown] SIGTERM received, shutting down
2026-08-18T10:37:43.140Z [DEBUG] [bridge:shutdown] Skipping archive+deregister to allow resume (env env_01Xq17zwDto2GMiTf9B1nkNu, spawnMode worktree)
# next boot re-polls the SAME workId=cse_01CmQ1VrvVPe7ZGiAZ2m7kz9, loop repeats.

# Signature B (after deleting the stuck session/environment from the app) — repeats every 1-90s:
2026-08-18T10:46:43.418Z [DEBUG] [bridge:api] POST /v1/environments/bridge -> 200 environment_id=env_01WcaErHgh5rK75s9JAwQ41D
2026-08-18T10:46:43.420Z [DEBUG] [bridge:work] Starting poll loop spawnMode=worktree maxSessions=32 environmentId=env_01WcaErHgh5rK75s9JAwQ41D
2026-08-18T10:46:43.773Z [DEBUG] [bridge:api] GET .../work/poll -> 200 (no work, 1 consecutive empty polls)
2026-08-18T10:47:35.279Z [DEBUG] [bridge:shutdown] SIGTERM received, shutting down
2026-08-18T10:47:35.290Z [DEBUG] [bridge:api] DELETE /v1/environments/bridge/env_01WcaErHgh5rK75s9JAwQ41D -> 200
2026-08-18T10:47:35.870Z [DEBUG] [bridge:shutdown] Environment deregistered, bridge offline
# immediately re-registers as a DIFFERENT environment_id (env_01Gkaw23GLnhZLhW9yFbeXbs), clean poll, SIGTERM again 4s later, repeat.
# ...eventually:
systemd[1639]: claude-remote-control.service: Start request repeated too quickly.
systemd[1639]: claude-remote-control.service: Failed with result 'start-limit-hit'.

(sessionId/session_id values were [REDACTED] by Claude Code's own debug logger; environment_id and workId values above are real, from this incident, and are safe to correlate against backend records for this account/org.)

Steps to Reproduce

I don't have a minimal, from-scratch reproduction — this was found on a long-running host, and Signature A specifically required an already-stale (16-day-old) queued work item to exist, which isn't something a fresh setup starts with. Rough steps that led to it:

  1. Run claude remote-control --spawn worktree --name "<name>" as a long-lived systemd --user service (Type=simple, Restart=on-failure, RestartSec=15).
  2. Over time, a queued session/work item for that environment becomes stale/orphaned (in our case, ~16 days old, acknowledged_at stuck at null).
  3. Observe the service restart repeatedly (every ~15s-10min) with the CCRClient: Epoch mismatch (409, reason=session_not_active) error on the spawned child, and a parent-level SIGTERM received, shutting down ~18-25s after each failed spawn (Signature A).
  4. From claude.ai/code, open the environment (https://claude.ai/code?environment=env_...) and delete/archive all sessions shown for it, hoping to clear the stale item.
  5. Restart the systemd service once, cleanly, with no other interference.
  6. Observe the restart interval shorten to 10-90s, with a brand-new environment_id registered on every restart and a clean SIGTERM a few seconds after each successful (empty) poll (Signature B) — until systemd's StartLimitBurst=3/120s trips and the service dies entirely (start-limit-hit), requiring a manual systemctl --user reset-failed && systemctl --user start to recover.

A 30-minute D-Bus capture with the unit masked showed zero external StartUnit/RestartUnit calls, ruling out cron/timers/scripts/watchdogs as the trigger — the restart is self-initiated by the claude remote-control process.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Claude Code Version

2.1.234 (Claude Code)

Platform

Anthropic API (OAuth via Claude subscription, claude /login — not a raw API key, Bedrock, or Vertex)

Operating System

Other Linux (Fedora, bare-metal, systemd --user service)

Terminal/Shell

Non-interactive/CI environment (runs as a systemd --user Type=simple service, not attached to any terminal)

Additional Information

  • Full unit file (relevant parts):

``ini
[Service]
Type=simple
ExecStart=%h/.local/bin/claude remote-control --spawn worktree --name "<name>"
Restart=on-failure
RestartSec=15
RestartMaxDelaySec=120
RestartSteps=4
StartLimitIntervalSec=120
StartLimitBurst=3
``

  • Reproduced the "self-restart, not external" finding via: systemctl --user mask claude-remote-control.service (after moving the real unit file aside and manually symlinking it to /dev/null, since systemctl mask refused to overwrite an existing regular file), then busctl --user monitor org.freedesktop.systemd1 for 30 minutes — zero StartUnit/RestartUnit calls against the unit; only routine UnitNew/UnitRemoved signal pairs from systemd --user itself (unrelated status bookkeeping, confirmed not to trigger activation).
  • Tried two mitigations, neither stopped it: (1) deleting all sessions for the environment via the claude.ai/code web UI (changed the failure signature from A to B but didn't stop the loop), (2) adding --no-create-session-in-dir to ExecStart (no effect).
  • While the loop is active, other unrelated, legitimately-attached worktree sessions in the same environment get disrupted too, since the entire Remote Control server process restarts, not just the one bad work item's child process.
  • Happy to share the account/org UUID and machine ID privately with an Anthropic engineer if useful for looking up server-side records for this incident — omitted here since this report is public.

View original on GitHub ↗

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