Repository navigation
[Bug]: Agent and browser panel show two different browser pages for the same browser tab #16901
Description
Activity
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.Issue needs maintainer review and initial categorization.
on Oct 7, 2026 Note
Grok responding on behalf of Julius.
Thanks for the precise repro and timeline. Confirmed in code on
main(f570bd2).What happens
- Fallback after 10s. On a desktop-launched server, a new server tab waits for the desktop's
<webview>to attach (DESKTOP_ATTACH_TIMEOUT = "10 seconds",desktopRenders). If nothing attaches in time,createTabopens its own headless page. Because the agent'spreview_opensetsautomationOwner, that page gets an isolated per-tab context with fresh storage. That's why your sign-in in the panel never reached the agent. Only this headless path sets a fixed viewport (L761-L763,UNATTACHED_FILL_VIEWPORT= 1280×800), so that's theviewportfield you saw. - Why the desktop doesn't attach for a background thread.
ElectronBrowserHostmounts webviews only for threads that have sessions in the client preview store (activePreviewSessionsAtom). Onmain, that store is filled byusePreviewSession, which runs only for the active thread (PreviewSessionSyncinChatView) and in its preview panel. A tab the agent opens on a thread you aren't viewing gets no webview until you come back, which is long after the 10s wait. - Nothing reconciles a late attach. The desktop channel records and publishes
attachedevents (DesktopBrowserChannel.tsL123-L125). ButServerBrowseronly usesawaitAttachedonce, while creating the tab, and otherwise subscribes only todetached. After the tab exists,ensureTabkeeps returning the headless tab. Meanwhile the client decides "native webview vs. stream" only by environment and runtime (rendersServerTabNatively). It never learns that the server fell back to headless, so it mounts its own webview, which loads the URL independently, and the agent can't see it.preview_statusalso doesn't say which page backs the tab:visibleonly counts viewers.
Fix direction
- Open PR fix(web): host local server browser tabs outside chat views #16620 removes the main trigger. A persistent desktop-side subscription hydrates
ElectronBrowserHostfrompreview.list/events, so background threads' tabs get a webview while the server is still waiting. - fix(web): host local server browser tabs outside chat views #16620 doesn't change
ServerBrowserat all, though. Any attach that lands after the 10s timeout, such as from a renderer that isn't ready yet, still produces the same split, with no swap and no signal. To close that gap,ServerBrowserneeds to handle lateattachedevents for tabs that are running headless. It could switch the client to the stream view for those tabs, or move the tab onto the desktop page. Either way, the snapshot orpreview_statusshould say which page backs the tab.
Workaround
Stay on the thread while the agent opens a new browser tab. A tab that has already split stays split for its lifetime, so have the agent open a fresh tab while you're on the thread, and use that one.
Related but distinct: #16701 (default profile, handing human tabs to agents), #16596 (Cloudflare on the headless page), #14171 (agent pinned to another desktop's copy of a tab).
- Fallback after 10s. On a desktop-launched server, a new server tab waits for the desktop's
- addedvia-triageFiled through npx t3 triageFiled through npx t3 triageand removedneeds-triageIssue needs maintainer review and initial categorization.Issue needs maintainer review and initial categorization.
on Oct 7, 2026 I ran into this while using Codex to configure an OAuth client in Google Cloud through T3 Code’s Browser panel. The visible page was already signed in, but Codex’s browser tools kept seeing Google’s sign-in form.
It still happens after updating and fully restarting T3 Code Desktop on
0.0.46-nightly.20261009.2861(macOS 27.0.1, Apple Silicon). For the post-restart attempt, Codex opened a fresh tab withopen: true,show: true,reuseExistingTab: false, andprofileId: "default".The corresponding trace window on Oct 9, UTC:
08:26:47.051 preview_open invocation begins (PreviewToolkit.invoke) 08:26:59.307 Tool response: available:true, visible:false, 1280×800; sign-in page 08:27:23.201 Desktop PreviewManager.createTab 08:27:24.438 Desktop PreviewManager.registerWebviewThe desktop webview was registered about 37 seconds after the request began. After that, repeated
preview_snapshotcalls still showed the sign-in form while the visible page showed the signed-in Cloud console. Navigating the visible page through native input did not change the page returned by the tool.The T3 address bar also retained the sign-in URL while its visible page content showed the signed-in console.
Same on macOS 26.7.1 arm64, T3 Code Nightly
0.0.46-nightly.20261008.2833, Claude Code thread, local environment. Our timings fit the 10 s attach fallback (UTC, 2026-10-09):- 08:51:14:
preview_openreturned after 11.6 s. The desktop<webview>for that tab was only created at 08:55:34. - 09:23:19:
preview_openreturned after 11.0 s. The desktopcreateTabfor it ran at 09:23:58, 39 s later. - In both cases the agent opened the login page in its tab, and I signed in from the Browser panel.
preview_evaluateon the agent's tab then found 0 ID tokens inlocalStoragefor the same origin, while my panel tab was signed in. This repeated three times (09:24, 09:41, 09:54).
Evidence:
server.trace.ndjson(McpServer tools/calldurations) andrenderer-history.ndjson(webviewcreatedevents).- 08:51:14:
Still present on
0.0.46-nightly.20261009.2873(Linux, AppImage) and in the code onmain@ 75c439f. Three additions: a scripted repro against the shipped server that needs no UI, two more symptoms, and a case for the stream fix direction over moving the tab onto the webview.Scripted repro (no UI)
This test runs the shipped
apps/server/dist/bin.mjs(under the AppImage's Electron withELECTRON_RUN_AS_NODE=1) in a throwaway T3 home. The test process plays the desktop on the desktop browser channel: it writes theDesktopBackendBootstrapenvelope withdesktopBrowserFd: 6, desktopBrowserControlFd: 7to fd 3, writesattachedevents to fd 6 and reads commands from fd 7. It opens a tab withpreview.open {runtime: "server"}over/ws, signing in with the bootstrap token.- Control:
attachedsent right afterpreview.open. The server sendscdpframes for the tab and starts no headless page. Passes. attachedsent after the tab went headless (the server's 10 sawaitAttachedtimed out; the headless page exists). The test waits 5 s and expects the server to adopt the webview (cdp), release it, or close its headless page. Fails: no command is sent for the tab, and the headless page stays.
The root cause is the early return in the
attachedhandler (ServerBrowser.ts L2411-L2420):if (tabs.has(tabKey(...))) return;.createTabdecides the host once (L745-L752). MeanwhilerendersServerTabNatively(previewRuntime.ts L61-L72) is true for every primary-environment server tab in Electron. So the panel never streams a headless tab: it always mounts a webview for it, and that webview is never the tab.The harness and test are about 200 lines of plain Node, and I can attach them. A port into the server test suite (fake desktop channel;
attachedafterawaitAttachedtimed out; expect acdporreleasecommand, or the headless page closed) would make a regression test for whichever fix lands.Two more symptoms
- The panel shows a frozen page under the live URL and title. The late webview loads
navStatus.urlonce, at mount (initialUrl,srcinElectronBrowserHost). The panel's URL bar and title come from the server snapshot, which keeps following the headless page. Example: an agent signed in to one site, then calledpreview_navigateto a second, unrelated site. The panel kept drawing the first site's login page under the second site's address and title. The webview also never reports back (serverDrivenwebviews skipreportStatus), so nothing corrects it. - The late webview loads the URL a second time, on its own, with the desktop partition's cookies. For one-time links (magic sign-in, email confirmation, OAuth callbacks), this second GET can use up the link or start a second session.
Fix direction: stream the headless tab
#16620 removes the main trigger by mounting webviews for background threads, and the closed #17336 moved a late tab onto the webview. Both make the Electron webview the only way to see an agent's tab. Some users run the server browser on purpose. Sites treat it as a normal Chrome (Google sign-in refuses Electron webviews). It also keeps its own persistent profile, which agents use. For them, the right result of a late attach is to keep the headless page and stream it in the panel. The server and client already support streaming for remote environments.
Concretely:
- Add the backing host to the session snapshot, e.g.
renderer: "desktop" | "server", set increateTab. - Make
rendersServerTabNativelyrequirerenderer === "desktop", so a headless tab gets the screencast viewer instead of a webview. - On a late
attachedfor a headless tab, send the desktop arelease(or a newunmount) command so it doesn't keep a second page. - Optional: a setting such as "Show agent tabs from the server browser", which makes
desktopRendersreturn false. It also removes the 10 s wait on everypreview_open.
This also fixes
preview_status.visiblewould then be true when the user watches the stream; today it reportsfalsewhile the user is looking at the stale copy.- Control:
Reproduced on macOS (Darwin 25.6.0, arm64), desktop app nightly
0.0.46-nightly.20261010.2935, Codex provider, local environment. No commits onmainsincev0.0.46-nightly.20261011.2980touch this.Timeline (UTC, 2026-10-11)
- 15:00:02: thread created. 15:00:14: Codex calls
preview_open{url: "https://us.posthog.com/…", profileId: "default"}. Nodesktop.ipc.preview.createTaborregisterWebviewappears in the desktop trace at that time; the thread wasn't focused with the panel open. - 15:12:45: the user opens the thread's Browser panel. The desktop creates the webview and the user signs in to PostHog there.
- 15:13:22–15:13:28: the agent's
preview_navigate/preview_open {show: true}on the sametabIdstill lands on/login?next=…, andpreview_statusreportsvisible: false.
Extra detail. With an explicit
profileId: "default", the fallback isn't an isolated context. It's the persistent<stateDir>/server-browser/profiles/defaultlaunched bylaunchPersistentContext, which has its own cookie jar separate from the Electron partitionpersist:t3code-preview-<hash>. Comparing cookie names (not values):- Electron partition: PostHog
sessionidandph_authenticated_us server-browser/profiles/default: only anonymousph_*_posthogandposthog_csrftoken
So "sign into the default profile" can never fix it from the UI once the tab has fallen back. Also, the
desktopChannel.attachedhandler inServerBrowser.tsreturns early when the tab already exists (if (tabs.has(...)) return;), which is why the late attach never takes over the headless page.This is a common path for scheduled or skill-driven runs, where the user usually isn't watching the thread when the agent opens its first tab.
Filed via
t3 triageby Claude Code (Opus 5.5).- 15:00:02: thread created. 15:00:14: Codex calls
Before submitting
Area
apps/server
Steps to reproduce
sleep 45, then callpreview_openwith{"url": "https://example.com", "open": false, "reuseExistingTab": false}, tell me the tabId, and stop."preview_openwith thattabIdand"open": true, so the browser tab shows in the browser panel.preview_evaluate(document.body.style.background = "red"), then read the value back.Expected behavior
The agent and I use the same browser page. When the agent makes it red, the browser page in my panel turns red. When I type or sign in on the browser page in my panel, the agent sees it.
Actual behavior
The agent's browser page is red (it reads back
rgb(255, 0, 0)and its screenshot is red), but the browser page in my panel stays white. One browser tab, two different browser pages.The agent can't tell this happened.
preview_openreturnedvisible: falseboth when it broke and when it worked. The only difference was that aviewportof 1280×800 came back when it broke. The desktop trace shows the browser page in my panel being created about 40 seconds after the agent opened the browser tab, when I came back to thread A.If I stay on thread A while the agent opens the browser tab (same steps, no
sleep, no switching), it works, and we share one browser page. The browser page in my panel turns red, and text I typed into a box on that browser page was read back by the agent.I first ran into this when an agent needed me to sign in to a website. I signed in on the browser page in my panel, but the agent's browser page stayed on the login form. Opening a new browser tab didn't help.
Likely cause: when the agent opens a browser tab,
ServerBrowser.tswaits up to 10 seconds (DESKTOP_ATTACH_TIMEOUT) for the desktop app to provide the browser page (a webview). If none arrives, the server makes its own hidden, headless browser page with fresh storage, because the tab has anautomationOwner. The desktop only loads browser tabs for the selected thread, so its browser page can arrive later than that. When it does, nothing swaps the browser pages or tells the client. My panel shows the desktop's browser page while the agent keeps using the hidden one. Showing the hidden browser page in the panel as a stream, which the server already supports, would avoid the split. So would telling the client which browser page the agent is using.Related: #16701 covers the default browser profile and handing your own browser tabs to an agent. This one is about one browser tab ending up with two browser pages.
Impact
Major degradation or frequent failure
Version or commit
0.0.46-nightly.20261007.2774
Environment
macOS 15 (arm64), T3 Code Nightly desktop app, local environment, Claude Code (Full access)
Logs or stack traces
Screenshots, recordings, or supporting files
1-split-run-two-pages.png
2-control-run-shared-page.png
Workaround
Stay on the thread while the agent opens a new browser tab. A browser tab that has already split stays split. Have the agent open a new browser tab while you're on the thread.