[BUG] Remote Control "disconnect" always fails with "Cannot read properties of undefined (reading 'session_url')" (toggle-off path missing null guard)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Turning Remote Control OFF fails 100% of the time. Enabling works and mints a
session URL; disabling immediately throws "Cannot read properties of undefined
(reading 'session_url')" and surfaces "Remote Control failed to disconnect: ..."
in the chat. The disconnect does not complete cleanly (no bridge teardown), so the
remote bridge appears to stay live until the app is restarted.
What Should Happen?
Toggling off disconnects the remote bridge cleanly and shows "Remote Control
session ended."
Error Messages/Logs
Logs (main.log) - enable OK, every disable fails identically:
[info] Enabling remote control for session local_...
[info] [remote-control] bridge_state: ready
[info] Remote control enabled: https://claude.ai/code/session_01UDi5ZDmwtRNTgf6PrapUTY
[info] [remote-control] bridge_state: connected
...
[info] Disabling remote control for session local_...
[error] Failed to toggle remote control for local_...: Cannot read properties of
undefined (reading 'session_url')
(4/4 disable attempts across the session failed with this exact error; 0 failures
on enable.)
Steps to Reproduce
- Open a local Code session, send a message so a session is active.
- Toggle Remote Control ON (/remote-control). Observe it connects and a
https://claude.ai/code/session_... URL is produced.
- Toggle Remote Control OFF (/remote-control again).
- Observe the error toast/message: "Remote Control failed to disconnect: Cannot
read properties of undefined (reading 'session_url')".
Reproduces every time on step 3.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
1.21459.0.0
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
Root cause (confirmed by reading the shipped bundle)
File: app.asar -> .vite/build/index.chunk-DYOjx3aH.js (session-manager remote-control
toggle). API client: index.chunk-5mFusjzq.js (enableRemoteControl).
The toggle method calls enableRemoteControl(enabled) for both directions, then reads
session_url off the result without guarding the result itself:
const a = await e.query.enableRemoteControl(i, i ? e.title : void 0),
c = (n = a.session_url) == null ? void 0 : n.split("/").filter(Boolean).pop();
enableRemoteControl returns .response of the request:
async enableRemoteControl(t, r) {
return (await this.request({ subtype: "remote_control", enabled: t,
...(r !== void 0 && { name: r }) })).response
}
On disable (enabled=false) the reply has no .response payload, so a is undefined
and a.session_url throws. The existing check (n = a.session_url) == null ? ...
guards the property value, not the object a - one level too shallow. Enable only
works because its reply is a populated { session_url, ... } object. The throw is
caught and, because it's the disable branch, re-surfaced asRemote Control failed to disconnect: ${message}.
Showing cached comments. Read the full discussion on GitHub ↗
23 Comments
i got the same error. I was enabling/disabling the remote control from claude code desktop. however, it does stop the remote control even though the error message prompted
Confirmed on macOS as well (issue is currently labeled
platform:windowsonly):bridge_state: ready → connected).Two details from reading the shipped bundles on macOS that may help triage:
remote_controldisable request tears the bridge down before replying (…await Q.teardown(), Q = null…in the CLI binary), and the reply simply carries noresponsepayload — which is exactly what the app-sidea.session_urlread then trips on. So the disconnect itself succeeds and the failure is app-side bookkeeping only. This matches the earlier comment ("it does stop the remote control even though the error message prompted") rather than the bridge staying live.catch, only the enable branch resets state; the disable branch just emits the synthetic assistant message and leavesremoteControlEnabled/bridgeSessionId/bridgeSessionUrluntouched. So the UI keeps showing the session as connected, and every retry reproduces the same error until the app restarts.A fix likely needs both:
a?.session_url(or special-casing the empty disable reply), and resetting the bridge fields in the disablecatchbranch so the UI state can't wedge.Confirming on macOS, with logs and mobile-side evidence for a consequence I don't think is captured yet: sessions affected by the failed disable become permanently unreachable, and the bad state survives app restarts and a full reboot. So this isn't only cosmetic bookkeeping — it has been breaking Remote Control for me for several days.
Environment: macOS (Darwin 24.6.0, Apple Silicon), Claude Desktop, Claude Code 2.1.209 (app bundle) / 2.1.210 (CLI), claude.ai OAuth, Max plan,
ANTHROPIC_BASE_URLunset/default.The two failures are distinct log events
The
session_urlTypeError only ever fires on the disable path, exactly as reported here:But enable fails separately, with different error text, and repeatedly on the same session IDs:
Why this may present to users as "enabling throws session_url"
Speculative, offered because the user-visible symptom and the log text don't match. Subjectively I am clicking to enable a stale session and getting the
session_urlerror; the log records the event asDisabling. That would follow if the toggle state is read as "connected" and the click is therefore routed to the disable handler — intent is "start", code path taken is "stop".Caveat against my own theory: there are only 2
Disablingevents across 60 enables in three log files, which is fewer than my failed attempts. So either most attempts predate the available log window, or the routing explanation is wrong and something else produces the error at enable time. Flagging it as a lead, not a finding.Mobile-side confirmation: sessions accumulate and never recover
The mobile session list shows the other end of this: 19 sessions, 11 of them
Disconnected. Six projects appear as an exact-name pair — one live record and one or more stranded twins on the same repository.Names below are redacted, but the mapping is consistent:
project-ais the same repository everywhere it appears, and rows sharing a name share an identical name in the original.| Session name | Repo | Connected | Disconnected |
|---|---|---|---|
|
ProjectA Opus| project-a | 58m | 12h ||
ProjectA Sonnet| project-a | 12h | 23h ||
ProjectB Opus| project-b | 24m | 1d ||
project-c| project-c | 12h | 1d ||
project-e| (none shown) | 12h | 1d ||
project-d| project-d | 12h | 1d, 1d |This is not age-based expiry. Five of the six live records are themselves 12h old, sitting alongside dead twins of the same name. Records are being superseded: each failed disable leaves the old session alive but unreachable, and the next enable on that project mints a new record beside it rather than reclaiming it. New sessions connect fine — the failure is that a superseded session is unreachable forever, with no way to clear or revive it. Over several days this accumulates into a list that is mostly dead entries.
Separately, and possibly related: the Devices panel reads "No recently connected devices" while eight sessions in the same view simultaneously report
Connected. The device registry and the session registry disagree.Orphan accumulation
Across three log files spanning Jul 12–18: 60
Enabling remote controlevents against 2 disable attempts, both of which failed. Nothing is being torn down. Alongside that, repeatedly:Multiple workers contending for one session — which looks like the upstream cause of the
Session creation failed/Remote credentials fetch failedenable errors above, and matches the cascade in #78333 and #78484.The bad state is not client-side, and there is no known workaround
Worth flagging for anyone triaging: restarting the app does not clear this, and neither does rebooting. I have quit and relaunched the desktop app repeatedly, rebooted the machine, and taken at least one update over the period the failures persisted.
That rules out in-memory client state. It also isn't persisted client-side:
~/.claude.jsoncontains no remote-control registration record of any kind (onlyremoteControlUpsellSeenCount), so there is nothing local for a restart to clear in the first place. The stuck state appears to live server-side in the account's session records.If that's right, no client-side fix — null guard included — will recover already-affected sessions. Those need server-side cleanup, or a client path that can force-reset a session's remote-control registration.
Suggestion
The null guard fixes the visible symptom, but the toggle state also needs reconciling on failure: a disable that throws leaves the UI asserting "connected" against a bridge the CLI already tore down, with no user-facing way back. Treating a missing/undefined registration as "not connected" and clearing state (as proposed in #78786) would cover the crash — but please also consider how already-orphaned server-side sessions get reaped, since that's the part users can't work around.
Adding another confirmed trigger + recovery path for this crash, observed on the
desktop app (Windows), with a root cause that never gets surfaced to the user.
Environment
Trigger
The workspace had never been trusted via the CLI trust dialog. In that state, every
/remote-controlattempt in the desktop app failed with:The actual cause was only visible when running the bundled CLI directly:
So the init failure (untrusted workspace) leaves a half-initialized session object,
and the disconnect/cleanup path then crashes on the missing
session_url— consistentwith the missing null guard described in this issue. Related: #77716 (trust error not
surfaced), #78333 (broken session state accumulating after this crash).
Stale state until full app restart
Notably, after accepting the trust dialog via the CLI (root cause removed),
the desktop app kept failing with the same error on every
/remote-controlattempt. Only a full quit + restart of the desktop app cleared it; afterwards
/remote-controlworked normally.Confirmed recovery steps
Two fixes suggested by this case:
so recovery doesn't require an app restart.
Follow-up to my 2026-07-16 comment: still reproduces on the latest builds — Claude Desktop 1.22209.3 (macOS, Darwin 24.6.0, app auto-updated 2026-07-19) + Claude Code CLI 2.1.214. The shipped bundle still has the unguarded read (now in
index.chunk-BpE94K1E.js):const a = await e.query.enableRemoteControl(i, …)followed bya.session_urlwith optional chaining on.splitbut no guard onaitself.Two details I don't see in the thread yet:
1. Manual toggling is not required — the auto-disable path hits the same crash.
Typing a message in the desktop app into a session that was handed off from claude.ai/code triggers an automatic disable, which crashes identically:
Identical sequence observed on 2026-07-16 (previous app version) and 2026-07-19 (1.22209.3).
2. The failed disable wedges the session — reconnecting becomes impossible until app restart.
In the catch for the disable direction,
remoteControlEnabledis not reset (only the enable direction resets state on error). The app keeps believing RC is on while the CLI side has already disabled, so every subsequent toggle re-enters the disable branch and crashes again — the enable branch is unreachable. From my logs: auto-disable failed at 12:23:36; a manual toggle at 12:32:04 failed identically.On my machine the wedged flag is in-memory only — the session registry JSON (
claude-code-sessions/<acct>/<org>/local_*.json) contains noremoteControlEnabled/bridgeSession*state for the affected session — so a full quit + relaunch clears it and RC can be re-enabled. (That differs from @misterbisson's restart-surviving variant, which may be the server-side session accumulation tracked in #78333 / #78484.)Suggested fix remains as in the OP, plus one addition: guard the response (
a?.session_url) and resetremoteControlEnabledin the disable-direction catch, otherwise a single transport hiccup permanently wedges the toggle for that session.Also seeing this on macOS, Claude Code 2.1.207:
Remote Control failed to disconnect: Cannot read properties of undefined (reading 'session_url')when disconnecting Remote Control from the CLI side.Also reproduces on Linux, standalone CLI (not the desktop app) — adding as a datapoint since most reports here are Windows/macOS desktop:
~/.local/share/claude/versions/2.1.198)Remote Control failed to disconnect: Cannot read properties of undefined (reading 'session_url')The unguarded
session_urlread is present in the shipped 2.1.198 binary as well. Restarting the CLI session clears the state, consistent with the recovery path others described.I'm hitting this multiple times a day (roughly 6x) on macOS desktop. Each time,
Remote Control just stops working with this error and there's no way to recover
it from the phone — I have to physically go back to my Mac and restart Claude
Code to get it working again. This defeats the whole point of remote work for
me; I can't rely on it right now.
Happy to share logs/repro details if useful.
Confirming from two additional Windows machines (same account), with a pinpointed regression window and one extra variant:
Regression window: Code tab Remote Control worked normally on Desktop
1.18286.0.0. Both machines silently auto-updated to1.24012.1.0— on the primary machine at 2026-07-21 20:13 local time (WindowsApps package folder creation time; no bootstrapper entries inClaudeSetup.log, so it came via the in-app updater). Thesession_urlerror started immediately after on both machines — so the shallow null guard described in the OP is still shipping in1.24012.1.0.Enable-path variant: here the crash also fires when toggling Remote Control ON in a brand-new Code tab session — consistent with the OP's root cause if the app believes a stale/orphaned session is still enabled and runs the disable branch first. Context: this account has orphaned RC sessions whose owner processes died non-gracefully (PC restarts / forced process kills).
Local state ruled out: grepped
%USERPROFILE%\.claude.jsonand all 8,823 transcripts under~/.claude/projects+~/.claude/sessionsforsession_url/remoteControl— zero records. Whatever stale reference the toggle trips on is app/server-side.Unaffected: standalone terminal CLI v2.1.218 on the same machines —
/remote-controlenable + disconnect both work cleanly. Workaround we're using: Remote Control from the terminal only.Another trigger for this on macOS, worth flagging because it needs no manual
toggle at all — a configuration combination makes a session disable itself.
Environment
0adcaed, built 2026-07-21)Trigger: both auto-enable settings on at once —
ccRemoteControlDefaultEnabled(desktop app preference, Settings → Claude Code →"Enable remote control by default")
"remoteControlAtStartup": truein~/.claude/settings.json(the CLI-side key,labelled "Remote Control for all sessions" in the terminal
/configpanel)A new desktop session then connects normally, and the first message you send
produces an unprompted disable, which lands on exactly the unguarded read pinpointed
in an earlier comment here:
Nothing was toggled by hand at any point in that session. Removing
remoteControlAtStartupfromsettings.jsonstops it; with only the desktoppreference set, the same app connects and stays connected:
So separately from the missing null guard, there's a second question here: why is a
disable issued at all? The two settings look like they both act as owners of Remote
Control for the same session — the app resolves its own preference first
(
source=explicit_pref) and only falls through to the settings layers when thatpreference is unset (
source=explicit_settings), so with both set, two paths racefor one session. That part is inferred from log ordering and the app's
rcAutoEnableresolution order, not from instrumenting the CLI side.
Happy to split this into its own issue if you'd rather keep this one scoped to the
null guard — say the word.
Still reproduces on a much newer build — desktop app 1.24012.9.0 (bundled CLI 2.1.219), Windows 11. Confirming the root cause above from that bundle, and adding two details that explain the follow-on symptom in #81228 ("stuck until app restart") and the common report that remote control then won't re-enable on any chat.
1. The failed disconnect leaves
bridgeSessionUrlset, which makes later enables silently no-op.The
catchonly clears state on the enable branch:On the disable branch nothing is reset, so
remoteControlEnabledstaystrueandbridgeSessionUrlstays populated even though the CLI did process the disable. The guard at the top of the toggle then fires:A subsequent "turn it on" (
desired: true) matches!!bridgeSessionUrland returnsok: truewithout doing anything — reporting success against a bridge that is already dead. A plain toggle computes!remoteControlEnabled→false→ attempts disconnect again → same TypeError. So one failed toggle-off wedges that session permanently for the rest of the app run, in both directions.2. The wedged state is in-memory only — restarting the app is a complete workaround.
saveSessionis only reached on the enable branch, so neither field is persisted. Session JSON under%APPDATA%\Claude\claude-code-sessions\...carries onlybridgeSessionIds(the cleanup history) — noremoteControlEnabled, nobridgeSessionUrl:So a restart clears every wedged session. Until the null guard lands, the workaround is: never toggle remote control off — close or end the chat instead — and restart the app if you already did.
Log excerpt from this build, showing disable failing and enable succeeding a minute later on a fresh chat:
The fix on the disable path needs both halves:
c?.session_urlfor the throw, and clearingremoteControlEnabled/bridgeSessionUrlin thecatch's disable branch so a failure can't wedge the session.Confirming on macOS against current builds, and adding three things I don't think are in the thread yet: the exact producer-side code behind the
undefined, a fix that repairs already-installed clients without an app update, and a scope note.Everything already established here — the
catchwedge, thedesired === !!bridgeSessionUrlshort-circuit, the in-memory-only state — I re-confirmed and won't repeat.Environment: macOS (Darwin 25.5.0, Apple Silicon), Claude Desktop 1.24012.9, bundled CLI manifest 2.1.219 (commit
7006c4c3…), locally installed CLI 2.1.220 — the latest published version as of today. The disable branch is identical in 2.1.219 and 2.1.220, so this is not fixed on either side of the wire.The
undefinedoriginates in the CLI, not the app@jeremy9682 described this behaviorally above ("the reply simply carries no
responsepayload"). The exact code, for whoever picks this up — the success responder in the CLI's control-request handler always setsresponsefrom its second argument:The enable branch passes a payload; the disable branch calls it with one argument:
The SDK client returns that payload directly:
So
cis alreadyundefinedby the time the app reachesc.session_url— the app-side read is the crash site, but the contract mismatch is upstream of it. Worth stating explicitly because it makes the failure unconditional: every successful disable produces this, which is why the reported failure rate is 4/4, 2/2, 100%. Hibernation, an untrusted workspace, or theremoteControlAtStartupcollision in the comments above are all just ways of arriving at a disable — they don't change the outcome.A one-line CLI fix repairs every already-installed desktop build
The app-side fixes specified above (
c?.session_urlplus clearing state in the disablecatch) are both correct and still needed. But they only reach users after a desktop release. Since the empty payload is the CLI's, the CLI can also fix it alone:Any non-empty object makes
c.session_urlevaluate toundefinedinstead of throwing, so shipped desktop builds stop wedging as soon as they pick up a new bundled CLI — which moves independently of the app release train. Given how many open duplicates this has, the fast path may be worth taking alongside the proper client-side guard rather than instead of it.Related: the disable branch replies
successeven whenz === nulland it tore nothing down. If session reaping keys off that reply, it's a candidate for the orphan accumulation @misterbisson documented — awas_connected: falsereply would at least make that state distinguishable.Scope: the terminal REPL is unaffected
This is desktop app + any SDK client calling
enableRemoteControl(false). The terminal REPL has its own disconnect handler that drives the in-process bridge and printsREMOTE_CONTROL_DISCONNECTED_MSGdirectly — there is no control-response payload for it to dereference, so/remote-controlin a plain terminal session never hits this. That may be useful for labeling, and it gives affected users a workaround beyond "restart the app": disconnect from a terminal session instead.Analysis is from reading the shipped bundles on disk (
/Applications/Claude.app/Contents/Resources/app.asarand~/.local/share/claude/versions/2.1.{219,220}); identifiers are minified, control flow and strings are verbatim.Confirming on Windows 11 with current builds — Claude Desktop 1.24012.9.0 (Store install) + CLI 2.1.219, claude.ai OAuth — and adding a few traced details that I don't think are captured yet. I hit this today and dug through the shipped bundle plus logs while recovering a wedged session.
1. @misterbisson's routing theory is correct — confirmed from the toggle handler
The slash-command/toggle path computes the direction from in-memory state:
desired ?? !session.remoteControlEnabled. With remote control auto-enabled at session creation ([rcAutoEnable] verdict: enable=true source=explicit_pref), a later/remote-controltyped with intent "turn it on / reconnect" is routed to disable — intent is "start", code path taken is "stop", exactly as speculated. That disable then dies on the missing null guard:2. Why the wedge never self-heals within an app run
The only code that resets the stale fields (
remoteControlEnabled/bridgeSessionId/bridgeSessionUrl) outside the toggle itself isteardownQuery— which runs when the session's CLI worker exits. But the idle-pause path skips pausing sessions whoseremoteControlEnabledis true (debug-level log: "Skipping pause for session … - remote control is active"). So a wedged session is also exempted from the one lifecycle event that would clear the wedge. It sits warm indefinitely: enable no-ops (early return whendesired === !!bridgeSessionUrl), disable re-crashes, forever.Also worth noting for triage: none of the stale fields are persisted — the per-session JSON on disk only stores the
bridgeSessionIdshistory array. In my case a plain app restart would have cleared the wedge (the survives-restart reports, e.g. #80013, may be a compounding auth issue rather than this state).3. Server-side consequence — matches the "Disconnected twins" on mobile
After the failed disable, the claude.ai side of the bridge is left archived (the CLI tears the bridge down before replying, so the worker-side disconnect did go through). Opening the old session URL shows the transcript read-only with an "Unarchive" banner, and the unarchive call fails:
i.e. the stranded rows accumulate server-side and can't be revived from the web/mobile UI, consistent with the session-list table above.
4. No-restart recovery (workaround for wedged sessions)
teardownQueryresets the wedged fields./remote-controlin the composer — state is clean, so the toggle takes the enable path and mints a fresh bridge/URL. Verified working; the session reappears on claude.ai/code immediately.Fix-wise I'd echo the earlier comment, with one addition: besides the
?.guard and resetting bridge fields in the disablecatch, the idle-pause exemption means any future variant of a stuck-enabled flag is unrecoverable without process death — so resetting the fields on any toggle failure (not just enable) seems worth doing defensively.Confirming on macOS, and adding a trigger path I don't think is in the thread yet: the bridge gets evicted server-side with websocket code 4090, with no user action at all. That matters here because it's how users arrive at the toggle-off that crashes.
Everything already established — the always-empty disable payload, the
catchwedge, thedesired === !!bridgeSessionUrlshort-circuit, the idle-pause exemption — I re-confirmed and won't repeat.Environment: macOS 15.6.1 (arm64), Claude Desktop 1.24012.9, bundled CLI 2.1.219. Relevant detail: this machine never sleeps (
pmset -g→sleep 0, prevented by Claude/coreaudiod; no Sleep/Wake events inpmset -g log), so this is not the hibernation path from #78786.1. The eviction, and what precedes it by two seconds
14 failures across every remote-controlled session in ~90 seconds, starting 2s after the device bridge rotated its socket and re-authenticated. The rotation runs roughly every 30 minutes and is usually harmless, so it reads as a race rather than a deterministic kill — with
drain_wait_ms=0, the replacement connection claims the session worker slot while the incumbent is still registered, and the incumbent is evicted.2. Nothing retries — but the client clearly can
The evicted sessions are never retried. The next
[remote-control]line of any kind is at 08:12:57, three hours later, and it is an auto-enable on a different, newly created session (local_d7c7ff77…) — nothing ever went back for the ones that were evicted. That's not a general inability to recover; the JWT path recovers fine on the same build:So there is a reconnect path. 4090 just terminal-fails into it instead of re-registering.
3. Frequency, and a negative result on the restart workaround
88 occurrences across 30 bursts, Jul 17-29, single machine. Clustered overnight, which is why it presents as "every morning all my remote sessions are disconnected." Through all of it the desktop UI still shows remote control enabled for those sessions while mobile shows them gone.
Correcting something in my original comment: I first read the morning
Enabling remote controlentries as me re-registering dead sessions by hand. They are not — each is preceded by[rcAutoEnable] verdict: enable=true source=explicit_pref, i.e. the app auto-enabling on a newly created session because enable-by-default is on. The evicted sessions were simply abandoned. That distinction matters for triage: with enable-by-default, a fresh session always gets a working link, which masks the eviction and makes it look like recovery.More usefully, it lets me test #78878's "full restart restores it" against this machine, and it did not hold here. App restart at 20:51 on Jul 28, with several sessions evicted earlier that day:
No disable->enable pass ran for the restored sessions — they were skipped as "live session already in memory." I then hand-enabled seven of them at 21:14:49, 21:15:02, 21:15:11, 21:15:17, 21:15:29, 21:16:09 and 21:16:24, none preceded by an
rcAutoEnableverdict. So on macOS 1.24012.9 the restart path is not a reliable workaround; whether #78878's Windows build restores differently, or whether the difference is that its sessions were cold rather than in-memory, I can't say from here.4. Why this belongs on this issue
The null guard specified above is correct and needed, but it fixes the crash, not the user-visible failure. The realistic sequence is:
catchwedge and the idle-pause exemption documented above, the session can't self-healTo be precise about what's diagnostic and what isn't: as established earlier in the thread, the disable branch always replies with an empty payload, so the crash is unconditional and its presence does not prove an eviction happened. I'm reporting 4090 as the thing that sends users to the toggle in the first place — a no-user-action entry point into this failure, distinct from hibernation (#78786) and
/compact(#78482).5. Suggestion
Treat 4090 as retryable rather than terminal — re-register the session worker on eviction, the way the JWT path already reconnects — and drain the incumbent connection before the rotated one claims the worker slot (
drain_wait_ms=0today). If the eviction is better tracked as its own issue against the bridge/rotation rather than here, happy to split it out.Log excerpts are verbatim from
~/Library/Logs/Claude/main.log; org and device identifiers redacted.Same bug on macOS (Darwin 25.5.0), Claude Code desktop app, v2.1.220 (native install).
Remote Control failed to disconnect: Cannot read properties of undefined (reading 'session_url')/remote-control; happened mid-turn while background tasks were running (the/remote-controlcommand was queued behind an active turn).Confirming on Windows, and adding one thing that I think corrects a conclusion earlier in this thread: the wedged state _is_ persisted client-side, in a file that hasn't been mentioned yet. There is a workable client-side reset.
Environment
Disable failure rate: 7/7, across 3 days and 4 distinct sessions
Counting every toggle event in the four rotated
main*.logfiles (Jul 26 – Jul 31):| event | count |
|---|---|
|
Enabling remote control| 33 ||
Disabling remote control| 7 ||
Failed to toggle remote control … 'session_url'| 7 |Zero successful disables, ever. Consistent with the null-guard analysis in the issue body.
The state that survives restarts:
bridge-state.jsonAn earlier comment in this thread noted that
~/.claude.jsonholds no remote-control registration, and concluded from that the stuck state must live server-side only. On Windows the registration is persisted somewhere else entirely:This entry has been sitting at
enabled: truesince Jul 29 for acse_*session that no longer exists server-side. Every app launch reloads it and retries forever:Note the sequence: the session "reconnects successfully" at the registry level and then 404s at the transport level — the two layers disagree about whether the session exists.
The file's mtime is direct evidence for the missing-teardown analysis.
bridge-state.jsonwas last written at 11:10 (app launch). Four disable attempts at 17:39:53 / :54 / :57 and 17:45:49 left it untouched — the exception aborts before the state write, so nothing is ever cleared.Cross-machine symptom this produces
I run two Windows machines on the same account. The failure is asymmetric and confusing in a way worth capturing for triage:
Users will read that as "the other machine's copy is fine, mine is broken," when it's one failure: server teardown succeeds, local bookkeeping doesn't.
Client-side workaround (untested end-to-end — see caveat)
With the app fully quit:
Caveat: I have verified the file's contents, its mtime behaviour, and the 404 loop it drives, but I have not yet verified that clearing it restores the toggle — doing so requires quitting the app I'm working in. Offering it as a lead for anyone who can test it, not as a confirmed fix. Editing the file while the app runs is pointless; it gets rewritten.
If it does hold, then the recovery story is better than this thread currently assumes: orphaned server-side records may well need reaping as described above, but the local wedge — the part that blocks re-enabling on the affected machine — looks clearable without a server-side fix.
Suggested fix, adding to the null guard
Beyond
a?.session_urland resetting the bridge fields in the disablecatchbranch: the persistedbridge-state.jsonentry should be dropped when a transport connect returns 404. A remote session the server says is gone should not be retried every 15 seconds for days.Follow-up to my comment above — I've now tested the
bridge-state.jsonreset. It works, but only for half the problem, and the half it doesn't fix is the half users actually notice. Correcting my own framing, and this ends up confirming the earlier server-side analysis in this thread rather than contradicting it.Verified: the reset does clear the local wedge
Quit the app on both machines, replaced
%APPDATA%\Claude\bridge-state.jsonwith{}, relaunched. Before, every launch produced this every ~15 seconds, indefinitely:After: startup reaches
[sessions-bridge] account-change reevaluate: <none> → <ids>and stops there. ~1,100 log lines and 3+ minutes with zero reconnect attempts, and the file stays{}— the app does not resurrect the dead entry. So the persistent local retry loop is genuinely client-side and genuinely clearable.(Not yet verified: whether the toggle itself is usable again afterwards. No enable/disable attempted since the restart.)
Not fixed: the orphaned session record itself
The session that was stuck showing as connected is still listed. It belongs to my other machine — I have two Windows boxes on one account — and I confirmed it exists in no local store on the machine I cleaned: it is absent from all 60 local session records (
include_archived: true), andgrepfinds its title nowhere under%APPDATA%\Claude\claude-code-sessions\.So the orphan lives server-side, exactly as described earlier in this thread. No amount of local file cleanup reaches it. My earlier comment implied otherwise; that was wrong. The two failures are separable:
| | lives where | cleared by
bridge-state.jsonreset ||---|---|---|
| 15s 404 reconnect loop | client (
bridge-state.json) | yes || session stuck "connected" in the list | server-side record | no |
A possible reaping path for the orphans
One lead for the server-side half, from the app's own logs — archiving a local session does request teardown of its remote-control bridge sessions:
These fire cleanly, with no
session_urlerror — a different code path from the toggle. If it also reaps records whose disable already failed, then "archive the local session on the machine that owns it" is a user-reachable recovery for orphans, and worth documenting. I can't test it here because the affected session is on my other machine, and archiving is destructive enough that I'd rather not guess at it on a session I still want.Cross-machine detail worth keeping
Because the orphan is server-side but the wedge is client-side, a two-machine setup splits the symptom in a way that reads as two unrelated bugs: the session vanishes from the other machine's list while the originating machine keeps showing it connected and can't re-enable it. Anyone triaging from a single-machine repro will only ever see the second half.
Confirmed: archiving the session reaps the orphaned server-side record. This closes the loop on the lead in my previous comment, and I think it means this thread's "no known workaround" is no longer accurate.
Tested on the orphan I described — a session on machine B whose disable had failed days earlier, still showing as connected, unreachable, and untouched by any local cleanup on machine A. Archiving that session on the machine that owns it removed it. It's gone from the list.
So both halves now have a user-reachable recovery:
| symptom | lives where | recovery |
|---|---|---|
| 15s 404 reconnect loop, toggle wedged "on" | client (
bridge-state.json) | quit app, write{}to the file, relaunch || session stuck "connected", unreachable forever | server-side record | archive the session on its owning machine |
The archive path works because it doesn't go through the broken toggle. It emits
[LocalSessionManager] Requested CCR archive of N Remote Control bridge session(s)and completes without thesession_urlthrow — so it can tear down a registration that the disable handler died before reaching.Two caveats before anyone leans on this:
That still leaves the underlying bug: users shouldn't need to destroy a session to undo a toggle that failed. But for anyone currently sitting on an accumulated list of dead entries with no way to clear them, this works today.
Still reproduces on macOS, Claude desktop 1.24012.9 (OP reports 1.21459.0.0), driving CLI 2.1.220.
The root cause in the OP is correct and I won't restate it. Two things from the same bundle that seem missing here, because together they explain the "toggle stuck ON until app restart" reports being filed as separate issues (#78336, #81228, #82567):
1. The
catchresets state on the connect branch only.otruthy clears four fields;ofalsy clears none. The assignments that would clear them on disable live in the success path (o ? (…=!0…) : (e.remoteControlEnabled=!1, e.remoteControlAutoEnabled=void 0, e.bridgeSessionId=void 0, e.bridgeSessionUrl=void 0)), which the throw skips. So after a failed disconnect the session still readsremoteControlEnabled === truewith a livebridgeSessionUrl.2. That stale
bridgeSessionUrlis what makes it unrecoverable without a restart. Near the top ofhandleRemoteControlCommand:With
bridgeSessionUrlstill set, an explicit enable (desired: true) short-circuits as already-on and does nothing, while an explicit disable re-enters the same throwing path. Cycling the toggle cannot clear it — exactly the loop described in #81228. Only an app restart drops the in-memory state.A fix needs both halves.
c?.session_urlstops the crash but leaves the toggle stuck, because the state reset is still inside theo ?branch of the success path. Hoisting the disable-side reset so it also runs in thecatch— or resetting before the await and restoring on failure — is what makes toggle-off actually recoverable.Confirming this matches what we see too: the crash itself (session_url) was a one-off, but the "toggle stuck ON until app restart" pattern from this analysis happens to us regularly on macOS — once a disconnect attempt fails, re-enabling/disabling Remote Control silently no-ops until the app is fully restarted. Matches the root cause described above (disable-path state reset only lives in the success branch, not the catch).
Confirming on macOS, Claude Desktop 1.24012.9.
Same signature: enabling works and mints a session URL, disabling always throws
Cannot read properties of undefined (reading 'session_url').My
main.loghas 35 occurrences across 6 days (Jul 21, 25, 28, 29, 30 and Aug 1), interleaved with successful enables — so the enable path is unaffected here too. Retrying after a failure reproduces the same error, matching the wedged-UI-state description above.Confirming on Windows 11 Pro (10.0.26200) with both CLI and Desktop app fully up to date, so this is not a stale-version issue:
@anthropic-ai/claude-code, latest via npm at time of testing)Anthropic.Claude, installed via winget;winget upgradereports no newer version available)Symptom matches the reports above exactly:
Reproduces consistently when toggling Remote Control off from the desktop app. Toggle state stays stuck as if still connected, matching the root-cause analysis above (disable branch not resetting
remoteControlEnabled/bridgeSessionId/bridgeSessionUrlon the app side). No workaround found other than restarting the app, which only clears the UI state temporarily.Also hit this on macOS (darwin 24.6.0), Claude Code v2.1.220, zsh.
/remote-controldisconnect threw "Cannot read properties of undefined (reading 'session\_url')" twice in a row.