remote-control: session children exit on expired session_token with no refresh, then daemon permanently refuses to re-spawn them

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

Summary

Long-lived cloud sessions served by claude remote-control --spawn worktree become
permanently unreachable from every client (desktop, mobile, web). Three issues compound:

  1. A session child exits when its session_token expires because no refresh is delivered.
  2. After that exit, the daemon marks the workId "already-completed" and refuses to re-spawn it,

even though the server keeps re-queueing the attach request. The session is unrecoverable.

  1. Restarting the daemon — the only way to clear (2) — deletes the bridge environment, which

orphans every session belonging to that daemon, not just the stuck one.

Environment

  • Claude Code CLI 2.1.220 and 2.1.221 (Linux x64, native install via ~/.local/bin/claude symlink)
  • Daemons run as systemd user services:

claude remote-control --name NAME --spawn worktree --capacity N --permission-mode default --debug-file PATH

  • Clients: desktop app and mobile app, each on independent release schedules — both affected

identically, which indicates the fault is daemon/child-side and client-version-independent.

Bug 1 — session_token expires with no refresh delivered

A child that had run for 4d 5h died at its token expiry. Its debug log shows healthy
20-second heartbeats up to the final second:

2026-08-04T21:06:48.669Z [DEBUG] CCRClient: Heartbeat sent
2026-08-04T21:07:08.667Z [DEBUG] CCRClient: Heartbeat sent
2026-08-04T21:07:28.678Z [DEBUG] CCRClient: Heartbeat sent
2026-08-04T21:07:48.684Z [ERROR] CCRClient: session_token expired
    (exp=2026-08-04T21:07:30.000Z) — no refresh was delivered, exiting

Across the child's entire 4-day log, no successful session_token refresh appears — only
plugin-marketplace refreshes and SSETransport: Refreshed headers for reconnect. The token
appears to be issued at session start and never renewed.

13 children died this way across three daemons over five days, at ages from 1d 15h to 4d 7h,
all with exit_code=1:

[bridge:session] sessionId=... failed exit_code=1 pid=32784
[bridge:session] sessionId=... workId=cse_... exited status=failed duration=4d 5h 1m

Varying lifetimes suggest refreshes sometimes land and extend the token, until one fails.

Bug 2 — daemon permanently refuses to re-spawn an exited session

Once a child has exited, every subsequent attach request is discarded. The server returns the
work item with "state":"queued", and the daemon skips it:

[bridge:api] GET .../work/poll -> 200 workId=cse_... type=session
[bridge:api] <<< {"id":"cse_...","type":"work","environment_id":"env_...","state":"queued", ...}
[bridge:work] Skipping already-completed workId=cse_...

This repeats indefinitely (6+ attempts logged per session; 66 skip events on one daemon).
The session still appears in the client session list but can never be opened again. There is
no user-facing indication of why.

Expected: a queued work item for a session whose child has exited should re-spawn the
child (resuming the on-disk transcript), or surface an actionable error.

Bug 3 — restarting the daemon orphans all of its sessions

Restarting the daemon does clear the state from Bug 2, but graceful shutdown deregisters the
environment:

POST /v1/sessions/session_.../archive
DELETE /v1/environments/bridge/env_OLD -> 200
[bridge:shutdown] Environment deregistered, bridge offline

Startup then registers a new environment id. Every client-side session record still points
at the deleted one, so opening any of them fails with:

Session initialization failed (environment_deleted)

So the only remedy for one stuck session destroys reachability for all sessions on that
daemon, including healthy ones. It also removes the live session's git worktree and deletes
its branch.

Expected: either environment identity should be stable across daemon restarts, or existing
sessions should be re-bound to the new environment.

Impact

Any session left running longer than its token lifetime (~2–4 days) is eventually lost, and
the loss is unrecoverable through the UI. This particularly affects users who work in bursts
across days rather than finishing a session in one sitting. Transcripts survive on disk, so
content is recoverable manually via claude --resume <uuid> outside the daemon, but the
cloud session itself cannot be revived.

Workaround

Run sessions standalone rather than daemon-served — claude --resume <uuid> --remote-control
<name>
inside tmux. These still hit Bug 1 eventually, but Bug 2 does not apply, so recovery
is a single resume command instead of a daemon restart that orphans everything.

Notes

Also observed at daemon startup, possibly unrelated:

[bridge] Session creation failed with status 400:
    GitHub repository access check failed — re-authorize GitHub in settings

Identifiers (org uuid, machine name, session/environment ids, absolute paths) have been
redacted; happy to supply them privately if useful.

View original on GitHub ↗

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