Remote Control startup failure is permanent for the session's life — no reconnect path, unreachable give-up cap, silent in --bg
Summary
When Remote Control fails to come up at session start, the failure is permanent for the life of that process. There is no reconnect path — only 3 HTTP attempts inside a ~2.4 second window — so a single transient blip at startup silently removes mobile reachability for a session that may run for hours. In a detached background session the failure is also unobservable: it is reported to a TUI that nobody is attached to.
Environment
- Claude Code 2.1.233, macOS 26.5.2 (arm64)
- Background session started with
claude --bg,remoteControlAtStartup: trueat user scope in~/.claude/settings.json
What happened
A background session logged Remote Control disconnected — Session creation failed at 2026-08-16T22:01:48.405Z. It never reconnected. 7.5 hours later that session still had no bridge, while every other live session on the same machine did:
11447.json bridge=YES 3324.json bridge=YES
11532.json bridge=YES 36140.json bridge=YES
11683.json bridge=YES 92363.json bridge=YES
11830.json bridge=YES 274.json bridge=NO <-- the failed session
13256.json bridge=YES
(bridgeSessionId present in ~/.claude/sessions/<pid>.json.) Session 92363 was spawned 5 h 44 m after the failure and got a bridge on its first attempt, so this was per-session, not a service outage.
The retry budget cannot cover the error class
Timing brackets the failure tightly: session claimed at 22:01:45.545Z, first transcript entry 22:01:46.039Z, failure reported 22:01:48.405Z — about 2.37 s for the whole sequence. With 3 attempts, 500 ms base backoff and jitter, that leaves roughly 250–300 ms per HTTP attempt: enough for a completed TLS handshake and a fast error response. That rules out the 10 s request timeout and is too slow for a TCP reset or DNS failure, which points at a transient HTTP status (429 or 5xx).
If it was a 429, the entire retry budget is ~1.5 s of backoff, which is structurally guaranteed to fail against that class.
Nothing schedules a second attempt
On the null return the hook increments a failure counter and returns. There is no timer, no backoff schedule, and no reconnect scheduler on that path. There is a give-up cap whose message reads "disabled after repeated failures · restart to retry" — but since nothing ever schedules a re-init, the counter never advances past 1 and that message is unreachable. Its absence from the logs is itself evidence that no second attempt was ever made.
The failure is invisible where it matters most
The handler surfaces the failure two ways: a TUI toast, and one system line in the transcript. A detached --bg session has no attached TUI, so the toast rendered to nobody. Nothing else records it — no push notification (despite agentPushNotifEnabled: true), no write to the job's state.json or timeline.jsonl, no daemon log entry (the bridge is in-process to the CLI, not the daemon).
So the channel whose purpose is reaching the user when they are not at the terminal died at startup and reported it only to the terminal they were not at.
This had a real cost in our case: the session later asked a question via AskUserQuestion and blocked 6 h 28 m waiting for an answer, because the one channel that could have reached the operator's phone had already been dead for 23 minutes and had said so only to an empty terminal. (Related but distinct: #83705, where the same session's job record misreports state: "working" while parked.)
Expected
- A failed startup connection is retried on a schedule with backoff appropriate to transient HTTP classes, rather than being permanent for the process lifetime.
- The give-up cap is reachable, or is removed as dead code.
- The failure is recorded somewhere durable — the job record, the timeline, or a notification — not only as a toast to a surface that may not exist.
Workaround
None in-session that we found. /remote-control is declared interactive (local-jsx), so driving it into a detached background session is unproven; the reliable path is invoking it from an attached session, which defeats the purpose for an unattended job. A local check is possible — absence of bridgeSessionId in ~/.claude/sessions/<pid>.json is a clean discriminator — but that is a user-side patch over a missing reconnect.