Remote control is never re-established after the idle-triggered auto-update relaunch, making headless machines unreachable
Remote control is never re-established after the idle-triggered auto-update relaunch, making headless machines permanently unreachable
This is the Windows desktop GUI app (the Code tab), not the CLI.
Summary
The desktop app's "stealth" auto-update waits until the app looks idle, then quits and relaunches, killing every session PTY. After the relaunch, per-session remote control is not restored — not for sessions that had it via the ccRemoteControlDefaultEnabled startup policy, and not for sessions where it was enabled explicitly.
The only code path that re-enables remote control automatically is gated on a turn being taken in the session. So the bridge comes back only if someone types into that session locally.
That makes the failure self-sealing on the machines remote control is for. I run sessions on a desktop I am deliberately not sitting at, and reach them from a laptop. After an unattended update relaunch, the recovery procedure is "go to the machine and type into each session" — which is exactly the thing remote control exists to avoid. The feature silently turns itself off precisely when I am away, and can only be turned back on by being there.
Environment
- Claude desktop GUI app for Windows (Code tab) — not the CLI
- Version 1.44121.4, relaunched from 1.44121.2; MSIX / Microsoft Store install
- CCD 2.1.258
- Windows 11 Pro 10.0.26200, x64
ccRemoteControlDefaultEnabledset (logs showsource=explicit_prefon every auto-enable)
What happened
Seven live sessions had an active remote-control bridge (six via the startup policy, one explicit). The update had been staged since early morning and the updater polled every 20 minutes without acting. At 11:37:33 the last active session hit its 900s idle timer; two minutes later the updater took idleness as its cue and relaunched the app.
2026-09-03 11:19:18 [updater] Staged version 1.44121.4 is still current (latest: 1.44121.4, lastTarget: null)
2026-09-03 11:37:33 [WarmLifecycle:session] Idle timeout reached, disconnecting local_9152a8aa...
2026-09-03 11:37:33 [CCD] Skipping pause for session local_9152a8aa... - remote control is active (startup-policy bridge)
2026-09-03 11:39:36 [stealth-update] Triggering stealth update after idle timeout
2026-09-03 11:39:36 [remote-tools-device] close
2026-09-03 11:39:36 [CCD] Killing 8 PTY process tree(s) on quit
2026-09-03 11:39:37 beforeQuitForUpdate handler fired, going down for update
2026-09-03 11:39:39 [updater] Version changed since last launch: 1.44121.2 -> 1.44121.4
2026-09-03 11:39:40 Loaded 0 persisted sessions
After the relaunch, no session regained remote control. There is no Enabling remote control line for any session until 14:17:29, when I walked back to the machine and clicked the toggle by hand — 2h38m of a supposedly remotely-accessible machine being unreachable, with no notification that anything had happened.
The cleanest evidence is the same session ID before and after, from the pause bookkeeping:
2026-09-03 10:17:50 [CCD] Skipping pause for session local_56480d3a... - remote control is active (startup-policy bridge)
... update relaunch at 11:39 ...
2026-09-03 12:59:33 [CCD] Pausing session local_56480d3a... (idle_timeout)
Pre-update, the session was held open because its bridge was live. Post-update, the same session was paused on idle, because as far as the app was concerned it no longer had one.
Notably, the device-level bridge does restore itself correctly on boot, in the same second:
2026-09-03 11:39:40 [remote-tools-device] init: shrimp=on device=relmer-desktop
2026-09-03 11:39:40 [remote-tools-device] connecting wss://bridge.claudeusercontent.com/devices/.../relmer-desktop/bridge
2026-09-03 11:39:40 [remote-tools-device] authenticated
So one subsystem reconnects unattended and the other does not. The device is reachable; the sessions on it are not.
Root cause
From the 1.44121.4 bundle. maybeAutoEnableRemoteControl is the only automatic enable path, and it is gated by:
shouldEvaluateRemoteControlPolicy(e) {
return (this.remoteControlPolicyCovers(e) || !!e.remoteControlUserRequested)
&& !e.remoteControlEnabled && !e.remoteControlUserToggled && !e.remoteControlAutoInFlight
}
remoteControlPolicyCovers(e) { return !!e.remoteControlAutoEligible && !e.scheduledTaskId }
Every call site of maybeAutoEnableRemoteControl hangs off a turn:
| call site | trigger |
|---|---|
| finishInitialEnqueue (first message enqueued) | first_turn / cold_resume |
| prewarm claim, first send | first_turn |
| runRemoteControlPassOwedToTurn (kept-input recovery) | inherited |
| warm send path | warm_send |
There is no call on app boot, on session restore, or on [CCD] Warming session. The eligibility flag itself is only raised on a first turn — r && (e.remoteControlAutoEligible = !0), where r is isFirstTurn.
A session restored from disk after a relaunch is idle by definition, so the policy is never evaluated for it. The effective semantics are "on for new conversations", not "on for this session".
Supporting: all 12 [rcAutoEnable] verdict lines in my main.log, spanning several app restarts, are trigger=first_turn source=explicit_pref. Not one cold_resume or warm_send has ever fired.
The state needed to reconnect is already persisted — bridgeSessionIds and remoteControlAutoEligible are serialized (remoteControlEnabled is not). When I re-enabled session 38e16584 by hand at 14:17, it came back on session_01FjCTF6FKYu3gH7JPpccwcf, the identical URL issued to it at 09:06. The bridge identity sat on disk the whole time waiting for a pass that structurally could not arrive.
Expected behavior
A session that had remote control active before an app-initiated relaunch should have it active after, without local interaction. At minimum this should hold for an update relaunch, which the app chose to perform and can therefore plan for.
Suggested fixes
- Re-run the policy pass on session restore/warm, not only on turns. The persisted
remoteControlAutoEligibleandbridgeSessionIdsalready carry everything needed; this looks like a missing call site rather than missing state. - Treat an app-initiated relaunch as continuous. Record which sessions held a bridge in
beforeQuitForUpdateand re-establish those specific bridges on boot, the wayremote-tools-devicealready reconnects itself. - Failing either, do not take the update while sessions hold live bridges — the updater already defers indefinitely while waiting for idle, so deferring for live remote-control sessions is a small extension of behavior that exists. Or surface a notification to the remote client that the bridge dropped for an update, so the away user knows rather than discovering it hours later.
I would push for (1) or (2). (3) alone leaves the user informed but still stranded.
Workaround, and why it does not help
Reading the bundle, sending any message into a restored session should hit the warm_send call site and reconnect it without touching the toggle. (Unverified in practice — no warm_send verdict has ever appeared in my logs.)
Either way it requires being at the machine, or already having remote access to it. For the remote-access use case there is no workaround short of not letting the app update itself unattended.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Recurred the same day, and a correction to my "workaround" note
This happened a second time roughly 7.5 hours after the occurrence in the report, on the same machine — a larger version jump this time:
Seven sessions had live bridges going in. As before, the device bridge reconnected on its own within a second, and not one session bridge did. The machine stayed remotely unreachable for 2h42m, until I walked over to it.
The recovery, in the log, is what the fix should be measured against:
One session came back on its own because I typed into it. The other five are hand-clicks, ~6 seconds apart, no verdict lines — me toggling each session in the sidebar, one at a time, sitting at the machine. Twice in one day.
Correction
In the original report I wrote that no
warm_sendverdict had ever appeared in my logs, and flagged the "just send it a message" workaround as unverified. That was accurate when I wrote it and is now wrong:warm_sendhas since fired twice (14:27:46and21:52:25above), bothenable=true source=explicit_pref.This strengthens the case for fix (1) rather than weakening the report. It confirms that after an update relaunch the persisted state is complete and the policy resolver is willing —
remoteControlAutoEligiblesurvives,bridgeSessionIdssurvives, the resolver returnsenable=trueon the first pass it is given. The bridge is not failing to reconnect; nothing is asking it to. A single policy pass at session-restore time, using the state that is already on disk, appears to be the whole fix.It also sharpens why the workaround is not one.
warm_sendrequires a turn, a turn requires input, and input requires either being at the machine or already having the remote access that was just lost. On a machine you sit at, this bug is a minor annoyance you fix with a click. On a machine you deliberately do not sit at — which is the entire population of machines remote control exists for — it is unrecoverable until you physically return.Frequency
Twice in one day on a machine that was never asleep and never lost network. Both relaunches were triggered by the app judging itself idle, which on a headless box is its steady state. The more faithfully this machine matches the use case remote control is for, the more often the feature turns itself off.
Confirmed on macOS, plus an unattended workaround that works
Desktop 1.52386.3 (also 1.49585.0 → 1.52386.0), bundled Claude Code 2.1.260/2.1.266, macOS 26.
Same mechanism as the report. Two stealth updates today, both recovered without touching the machine:
What does and does not reconnect Remote Control (all verified against the logs)
| Path | Reconnects RC? |
|---|---|
| App re-warming the last-focused session on relaunch | No (warm alone never runs the pass) |
|
claude://resume?session=<id>deep link (warms any session, unattended) | No ||
remoteControlAtStartup: truein~/.claude/settings.json| No effect on restored desktop sessions || Message injected into the CLI child's inbox socket (
/tmp/cc-socks/<pid>.sock) | Starts a turn, but no — the pass only runs on the app's own send path || A message sent through the app's UI | Yes (
trigger=warm_send) || A message sent by Claude in another desktop session via
mcp__ccd_session_mgmt__send_message| Yes, and it cold-starts a stopped session too (trigger=cold_resume), reattaching to the recorded bridge id |So the only unattended-capable trigger is the app's cross-session
send_messagesurface, which only Claude inside a desktop session can call.Workaround: a persistent relay session
CLAUDE.mdsays: requests arriving on your inbox socket that carry a token matching.tokenin this folder are from my recovery script; for each listed session id, callsend_messageonce with the given text, then reply DONE. Created once by hand; never archived.bridgeSessionIdin~/.claude/sessions/*.json; when the app's pid changes andmain.logshowsLoaded N persisted sessions, wake the relay withclaude://resume?session=<its id>and post a one-line JSON{"type":"user","message":{"role":"user","content":…}}with the token to its socket.Costs and caveats: one short model turn per restored session plus the relay's; needs Settings → Claude Code → "Enable remote control by default" on; a session that was ever "parked" (opened while another process held its bridge) loses
remoteControlAutoEligibleand needs one manual toggle before this path works for it. Without theCLAUDE.md+ token, the relay refuses socket-delivered instructions as unverifiable, which is the right default.The fix this points at
Everything needed already exists in the app: the quit handler knows it is quitting for an update (
before-quit-for-update, the stealth-relaunch marker), each session record keepsbridgeSessionIds, and thecold_resumereconnect works. On a stealth relaunch, iterate the records that had a live bridge at quit and run that reconnect. Restricting it to sessions that were live at the moment of the update keeps the process count where it was before the update killed them.---
Code for the workaround (script, relay CLAUDE.md, launchd plist, README): https://gist.github.com/BuckG71/d83659fa9b85443de16d7ed259795799
Field update on the relay workaround above: three stealth-update relaunches since the relay-session version went live on 2026-09-11 (11th 17:38, 13th 08:49, 15th 13:00; app 1.52386.0 → 1.52386.3 → 1.52386.6 → 2.110.0). Every session that had a bridge at the quit came back on the phone unattended within about a minute each time. Zero manual reconnects in four days on a machine that had needed one after every update before.