Repository navigation
Description
Note
🤖 Claude Opus 5.5 writing on behalf of sndstone, with a review by GPT 6.1 Sol.
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/desktop
Steps to reproduce
Desktop app (macOS) with its local environment, so the agent's preview tab is rendered by the desktop.
- Serve these two files locally (for example
python3 -m http.server 8765):<!-- index.html --> <button id="popout" onclick="window.open('popup.html?kind=popout', 'player', 'width=520,height=360')">Pop-out player</button> <button id="auth" onclick="window.open('popup.html?kind=auth', 'auth', 'popup,width=420,height=520')">Sign in</button>
<!-- popup.html --> <!doctype html><title>Popup</title><p>Popup loaded</p>
- Agent:
preview_open({ url: "http://127.0.0.1:8765/index.html", reuseExistingTab: false })→ tab A. - Agent:
preview_act({ tabId: A, action: "click", selector: "#popout" }), then the same for#auth. - Agent:
preview_status({ tabId: A }). - Agent:
preview_evaluate({ tabId: A, expression: "window.open('', 'player').location.href" }).
Expected behavior
Each popup becomes a tab the agent owns, with window.opener intact, as popups from headless server tabs already do through adoptPopup. The agent can snapshot it, act in it, and see it close.
Actual behavior
- Step 3: a native window appears for each click.
- Step 4:
tabslists only A. Neither popup is addressable as a preview tab, so the agent has no snapshot, act, or close for it. - Step 5 returns
http://127.0.0.1:8765/popup.html?kind=popout: the popup is loaded and working. A same-origin popup can sometimes be scripted or closed indirectly throughpreview_evaluateon the opener, but that gives no normal tab tooling. Cross-origin restrictions prevent reading or interacting with the popup's DOM through the opener, although limited operations such asclose()andpostMessage()may remain available.
This can block any flow that continues in an HTTP(S) popup opened with window features, such as a content player's pop-out, a sign-in, payment or share window. The repro uses a synthetic player/sign-in page; I have not tested real OAuth providers.
HTTP(S) requests classified as foreground-tab or background-tab, without a POST body, follow the separate new-tab path. Ordinary target="_blank" links commonly take this path. Ownership of those tabs is #26250, which also notes the relay limitation; #29198 covers closing unclaimed tabs in general. This issue concerns native new-window popups. Related: #6561 (earlier OAuth popup blocking) and #13348 (open PR that parents native popups to the T3 window and disables FedCM for preview guests).
Root cause (main 12ec17c)
previewWindowOpenActionreturns"popup"for an HTTP(S)disposition: "new-window", and the guest's window-open handler allows a nativeBrowserWindow, kept native so OAuth keepswindow.opener(Manager.ts#L447-L465, Manager.ts#L1540-L1544).- The server drives desktop-rendered tabs over Playwright
connectOverCDPthrough the desktop's CDP relay, which lists, attaches and announces only the tab's own page (CdpRelay.ts#L157-L186; its test is "announces only the tab's page, under its real target id"). The popup'swebContentsis never exposed. - So
page.on("popup")→adoptPopup(ServerBrowser.ts#L979-L980, ServerBrowser.ts#L1305) never runs for desktop-rendered tabs. This omission does not affect headless server tabs, where Playwright's own Chromium reports popups.
Suggested direction
Make native HTTP(S) popups discoverable and controllable through the preview tools, inheriting the opener's agent ownership while preserving browser popup semantics and desktop behavior. Exposing their targets through the CDP relay appears to let the existing adoptPopup path handle ownership; adopted native popups would also need to retain desktop-specific handling. The local candidate keeps the native windows; named-window reuse and full desktop integration still need verification.
I have a local candidate fix along these lines, described below, and can open a PR for it once the direction is triaged.
Candidate fix, evidence and open work
What it does
- Desktop:
did-create-windowattaches a debugger to the popup'swebContentsand hands it to the opener tab's relay (DesktopBrowserHost.attachPopup). - Relay:
CdpRelay.addPopupannounces the popup once, after the tab, as apagetarget withopenerIdset to the tab. The relay includes it ingetTargets, letsattachToTargetandcloseTargetreach it, and routes its own and child-frame sessions to the popup's debugger. On close it sendsdetachedFromTarget(withtargetId, and on the parent session fornewCDPSessionsessions) andtargetDestroyed. - Server:
createTabalso adopts desktop popups that already existed when it connected, because theirpopupevent fired before the listener existed.
Evidence
vp run check:changedpasses. New tests fail on main: a relay case (announce once after the opener, routing, close and detach routing) and a server case (a pre-existing desktop popup becomes the agent's tab).- An Electron 44 harness drove the real
DesktopBrowserHostand relay with Playwright 1.60connectOverCDPdirectly. On main, Playwright saw only the opener and nopopupevent. With the candidate fix, in five recorded runs, Playwright listed a popup opened before it connected, gotpage.on("popup")for the pop-out click, read and clicked inside the popup, sawwindow.openerand the popup'spostMessagereach the opener, and saw the popup close. - The harness bypasses
ServerBrowser,adoptPopupand the MCP tools, so full agent-tool integration in the desktop app is still unverified.
Open work before a PR
- Person-opened tabs: the relay is also connected for desktop tabs a person opened. The candidate adopts their popups as unclaimed preview tabs alongside the native windows. Whether adoption should be limited to agent-owned openers needs triage.
- Desktop provenance after adoption: an adopted popup has
desktop === null, socreateTabtreats it as headless. It emulates a 1280×800 viewport onto the small native window, always intercepts file choosers (ServerBrowser.ts~L531, so the OS picker is lost even for a person), and appliespresentAsChromeand clipboard instrumentation meant for headless pages. - Focus and painting: popup commands skip
DesktopBrowserHost's focus emulation, painting, and one-at-a-time screenshot handling, so a hidden or minimized popup could stall input or capture. - Downloads: downloads started inside the popup are not placed or reported.
- Release and reconnect: these must stop popup streams, release paused child targets, and restore adopted popup tabs without duplicates.
Alternatives considered
- Opening
new-windowpopups as preview tabs instead breakswindow.opener, returned window handles, named-window reuse and POST semantics. - Electron's
createWindowoverride, hosting the popup'swebContentsinside the preview, is promising long term. It doesn't fit the current<webview>model, anddid-create-windowno longer fires with it.
Impact
Major degradation or frequent failure
Version or commit
T3 Code (Nightly) 0.1.7-nightly.20261103.4106; source analysis on main 12ec17c
Environment
macOS 27.0 (arm64), desktop app with its local environment, Claude Code provider.
Logs or stack traces
# preview_status for tab A after both clicks (trimmed)
"tabs": [
{ "tabId": "tab_1_…", "url": "http://127.0.0.1:8765/index.html", "owner": "agent", "ownedByCaller": true }
]
# server trace for tab A shows DesktopBrowserChannel.awaitAttached / endpoint / pointer, so the tab is desktop-renderedWorkaround
A person can finish the flow in the native popup window. An agent can script a same-origin popup only indirectly, through preview_evaluate on the opener; it cannot read or act in a cross-origin popup's page.
Activity
fix(desktop): agents can drive the popup windows their desktop tabs open
Wladefant commentedon Nov 5, 2026
Changed-head review result for the native desktop popup issue.
Upstream PR #30460 remains OPEN / UNMERGED, author-owned by shivamhwp on branch issue-watch/30384, exact head 65b0e26. Separate current-head review #5779978791 was submitted and read back as CHANGES_REQUESTED.
New P2 confirmed in apps/desktop/src/preview/DesktopBrowserHost.ts:509: when the popup's Electron debugger detaches (including DevTools takeover), hidePopup removes the relay and listener, but openedPopups retains the still-live WebContents. No event retries showPopup when DevTools closes or the debugger becomes available; the only showPopups triggers are relay creation, initial popups command and a new openedPopup. Existing DesktopBrowserHost.test.ts exercises agent CDP Target.detachFromTarget, not the Electron debugger detach/re-attach lifecycle. Required regression: same popup remains open through DevTools takeover/close, host reattaches exactly once and resumes routing, opener/siblings remain usable, and destroying the popup does not retry. No native-window close/recreation.
Exact-head normal PR workflow #41964181949 succeeded on synthetic merge candidate 53b4656 (head 65b0e + main 5a906). Selected logs read: DesktopBrowserHost 36, CdpRelay 22, ServerBrowser 39; Test job 515 files / 5,484 tests; Lint, Build/Typecheck, Release Smoke and Check succeeded. This is not current-main composition: latest merged main observed 67eebac, candidate 10 ahead / 4 behind from merge base e313a12; main has an overlapping ServerBrowser.ts change. Normal forward composition and exact-head CI remain needed before landing.
Prior author-reported macOS package acceptance was at 9f6d46, not this current head. Current-head native popup/browser acceptance remains NOT RUN. Review only; no source or branch custody, CI dispatch, merge, installation, host/device action or landing authority.