Repository navigation
Description
What happened
I was signed in to an internal web app by hand, in the collaborative browser preview panel, and could see three tabs showing a logged-in dashboard. The agent I asked to read a page from that app told me I was not signed in. It said so three times, each time with what looked like solid evidence: the login form's markup, and document.cookie === "". Nothing errored, nothing timed out. I only worked out it was wrong by screenshotting my own screen and showing it.
Quitting and relaunching T3 Code fixed it.
Diagnosis
Two preview automation hosts were registered in the environment at the same time. Every automation request went to the one the user was not looking at, and it answered successfully from its own browsing context.
1. Two clients were connected with preview surfaces for the whole session. ws.rpc.previewAutomation.connect opened for connection 44c44045… at 08:45:37 and for 4842efea… at 08:55:10. Both stayed open until PreviewAutomationBroker.disconnect fired for both at 10:49:07, 64 ms apart, when the app quit. Both reported ws.rpc.preview.reportStatus throughout.
2. Every automation request in the fault window went to the client that was not being focused. Between 10:38:00 and 10:49:00 (local, UTC+1): PreviewAutomationBroker.invoke × 61, ws.rpc.previewAutomation.respond × 61 — all 61 responses from 4842efea…, none from 44c44045…. In that same window 44c44045… issued ws.rpc.previewAutomation.focusHost × 11, including 20 s and 4 s before the first wrong read. 4842efea… issued 2.
3. The desktop main process never executed any of them. The pre-restart desktop trace has 5,024 spans in that window, including desktop.ipc.preview.createTab × 6, registerWebview × 18, PreviewManager.createControlSession × 6 and closeTab × 2 — and zero automationEvaluate, automationStatus, automationClick or automationSnapshot. It logged those before the fault (last at 10:23:33, PreviewManager.performAutomationEvaluate) and again immediately after the restart (10:49:47). So tab creation went through the desktop — which is why the panel visibly followed preview_open — while the reads were answered somewhere else entirely.
4. The restart collapsed it to one host and the fault went away. PreviewAutomationBroker.make at 10:49:23, then a single connection d74d3b7c… at 10:49:26 issuing both focusHost and respond. Desktop automation spans returned, preview_open returned visible: true, and reads matched the panel.
Why the wrong host wins. In apps/server/src/mcp/PreviewAutomationBroker.ts, a provider session with no live lease picks its host by:
.sort((left, right) =>
right.supportedOperations.size - left.supportedOperations.size ||
Number(right.focused) - Number(left.focused) ||
right.focusOrder - left.focusOrder)[0]and the winner is pinned in assignments, keyed by environmentId \0 providerSessionId, then deliberately never re-evaluated while the connection lives. apps/web/src/components/preview/PreviewAutomationHosts.tsx registers every host with supportedOperations: [...PREVIEW_AUTOMATION_OPERATIONS], so between two current clients the first sort key always ties and the decision falls through to focused / focusOrder — effectively registration order. Because the lease is keyed by providerSessionId, a new agent session re-runs that sort, which matches the timeline: the session that started at 10:38 was new, and reads at 10:20 and 10:23 in the same app run were correct.
Why preview_status cannot detect it. currentStatus in the same file returns available: Boolean(previewBridge?.automation) — true whenever the Electron bridge object exists, regardless of whether the responding client has that tab rendered — and when the tab is not desktop-backed it falls back to the server snapshot's navStatus for url and title rather than reading the live page. Hence available: true and a plausible URL throughout. No field in any preview_* response distinguishes this from a healthy read.
Two things that look like the cause and are not.
visible: falseis not a signal. It wasfalsefor the correct reads at 10:20 and 10:23 in the same app run,falsethroughout the fault, andfalseon a healthy single-host session afterwards while reads were correct. Only the post-restart call returnedtrue.- The preload
TypeErrorin the evidence below fired on every page load in the fault window and looks causal. It is not specific to this fault: the same two lines appear inpreview_snapshotconsoleEntriesin local provider logs from 2026-09-11, 09-16 and 09-17, in sessions whose reads were correct.
What I could not determine. Server traces rotate every 2–4 minutes on this machine, so the window before 10:23:50 was already gone and I cannot show which host served the correct earlier reads, nor the moment routing changed. Span attributes in both trace files are empty, so environmentId, clientId and tabId are not recoverable from them — the connection identities above are traceId/parentSpanId pairs for the WebSocket connections. I also could not identify what created the second host: the desktop trace shows no new renderer at 08:55:10, only a desktop.ipc.connectionCatalog.set moments earlier, and the on-disk catalog is encrypted.
Two minor asides, not filed separately. preview_status on a tab id that does not exist returns {"available":true,"visible":false,"tabId":null} rather than an error, which reads like a healthy answer. And tab ids in the fault window were tab_7, tab_8, tab_b, tab_c, restarting at tab_1 after the relaunch — they are not decimal and not enumerable, which cost time during triage.
Steps to reproduce
I could not reproduce this deterministically, and would rather say so than invent steps. Observed once; traces for most of the fault window are preserved and I can attach them.
Predicted from the traces and the v0.0.42 source, but not confirmed:
- Get two preview automation hosts registered in one environment at once (I have not identified what created the second one).
- Sign in to a site by hand in the preview panel you can see.
- Start a new agent session — a new
providerSessionId— and have it callpreview_evaluateorpreview_snapshotagainst that site.
Verified negative control: with a single registered host, the same sequence against a throwaway localhost server behaves correctly. preview_navigate to a cookie-setting endpoint followed by preview_evaluate returns the signed-in document and the real document.cookie.
Version
0.0.42 (tag v0.0.42, commit 719a76c)
Environment
macOS 27.0 (26A428) darwin arm64, Electron 44.1.0, Chrome 152.0.7977.65, desktop app against the local server, provider Claude Code (claudeAgent) with Opus 5, preview device toolbar active (Responsive, 1280x800)
Evidence
# two hosts connected, both alive until the app quit (server.trace.ndjson*)
08:45:37 ws.rpc.previewAutomation.connect trace 44c44045 parent f7386175
08:55:10 ws.rpc.previewAutomation.connect trace 4842efea parent 2dc7c84e
10:49:07 PreviewAutomationBroker.disconnect 4842efea
10:49:07 PreviewAutomationBroker.disconnect 44c44045
# fault window 10:38:00-10:49:00, routing counts
PreviewAutomationBroker.invoke 61
ws.rpc.previewAutomation.respond 61 ALL from 4842efea, 0 from 44c44045
ws.rpc.previewAutomation.focusHost 11 from 44c44045 (2 from 4842efea)
# same window, pre-restart desktop trace (5024 spans total)
desktop.ipc.preview.createTab 6
desktop.ipc.preview.registerWebview 18
PreviewManager.createControlSession 6
desktop.ipc.preview.automationEvaluate 0
desktop.ipc.preview.automationStatus 0
desktop.ipc.preview.automationClick 0
desktop.ipc.preview.automationSnapshot 0
# last desktop-executed automation before the fault, first after the restart
10:23:33.105 PreviewManager.performAutomationEvaluate Success 2.0ms
10:49:47 desktop.ipc.preview.automationEvaluate Success
# agent-visible results across the restart boundary (site redacted: private intranet)
10:38:30 preview_open {"available":true,"visible":false,"tabId":"tab_7", ...}
10:45:43 preview_open {"available":true,"visible":false,"tabId":"tab_b", ...}
10:47:06 preview_open {"available":true,"visible":false,"tabId":"tab_c", ...}
10:46:27 preview_evaluate {"cookie":"","txt":"Username Password Sign in with ...", ...}
10:47:13 preview_evaluate tabId=tab_c -> "CLAUDE-MARKER-TAB-C" (user saw no change on screen)
10:47:15 preview_evaluate tabId=tab_b -> "CLAUDE-MARKER-TAB-B" (user saw no change on screen)
--- app quit and relaunch at 10:49:07 ---
10:49:44 preview_open {"available":true,"visible":true,"tabId":"tab_1", ...}
# on every page load in the fault window, and also in unaffected sessions since 2026-09-11
Electron sandboxed_renderer.bundle.js script failed to run
TypeError: Cannot destructure property 'preloadScripts' of 'binding.startupData' as it is null.
at node:electron/js2c/sandbox_bundle:2:133687
at node:electron/js2c/sandbox_bundle:2:134791
at ___electron_webpack_init__ (node:electron/js2c/sandbox_bundle:2:134795)
at node:electron/js2c/sandbox_bundle:2:134918Related issues
Not a duplicate of #11167 (wrong client, but calls time out; here it answers successfully), #12146, #12898, #12273, #12319 (host eviction/disconnect, all loud), #11223, #7212, #3715 or #8332.
Fix applied or workaround
Nothing was written to the machine. Workaround is to quit and relaunch T3 Code: the Electron profile survives, so a persistent login survives, but a non-persistent session cookie does not and you will need to sign in again.
Filed by
claude (opus-5) in T3 Code, following .github/triage/PLAYBOOK.md
Activity
juliusmarminge commentedon Sep 22, 2026
Triage
Verdict: Confirmed on
v0.0.42(719a76ca1) and currentmain(aff9318bf). Keep open. Not fixed by nightlies after 0.0.42. Not a duplicate of #11167 / #12146 / #12898 / #12273 / #12319.When two preview automation hosts are registered for the same environment, a new provider session can be pinned to the host the user is not looking at. That host answers successfully from its own browsing context (empty cookies / login markup), while the visible desktop panel keeps creating/showing tabs. Quit + relaunch collapses to one host and the fault goes away. That matches the traces and the code.
What the code does
PreviewAutomationBroker.invokepins a provider session to one host (environmentId+providerSessionId) for the life of that connection so multi-step browser flows do not jump cookie/DOM runtimes. A live lease is not re-sorted on laterfocusHostupdates; only disconnect prunes it. Tests encode that: failover only after disconnect; a pinned session stays on the first host even if another host becomes focused.When there is no live lease, the picker sorts by:
supportedOperations.sizefocusedfocusOrderPreviewAutomationHostsalways advertises the fullPREVIEW_AUTOMATION_OPERATIONSset, so between two current desktop clients the first key ties and the decision falls through to focus / registration order. A new agent session (newproviderSessionId) re-runs that sort and can pin the wrong host for the whole turn.currentStatusreportsavailable: Boolean(previewBridge?.automation)whenever the Electron bridge exists, and when the tab is not desktop-backed on that host it falls back to the server snapshot’snavStatusfor URL/title. Sopreview_status/preview_opencan look healthy (available: true, plausible URL) while evaluates read a different cookie jar.visible: falseis not diagnostic; it appears on healthy single-host sessions too.The preload
TypeErroraboutpreloadScriptsis a red herring (also present in unaffected sessions), as the report already notes.Related, not duplicates
Nightlies that include #12535 (
v0.0.43-nightly.20260919.*onward) still have the same sort + sticky assignment. Updating past 0.0.42 alone will not fix this.Workaround
Quit and relaunch T3 Code so only one automation host remains. Electron profile/login can survive; non-persistent session cookies will not.
Next step
Keep open as a real silent multi-host routing bug:
preview_status/ evaluate responses distinguishable when the responding host does not have the tab’s live desktop webview (do not reportavailable: truefrom bridge presence alone).focusHostflips, or surfaceclientId/ host identity in preview tool results so agents can detect a mismatch.The report’s predicted repro (two registered hosts → sign in by hand on the visible panel → new agent session →
preview_evaluate) matches the broker behavior; a deterministic dual-host fixture would unlock a regression test beside the existing pin/failover tests.