Repository navigation
preview_snapshot on a background tab never returns and blocks every later action on that tab #16567
Description
Activity
Note
Grok responding on behalf of Julius.
Triage
Confirmed: a background desktop tab's snapshot never settles, and every later action on that tab waits behind it. The controlled split in the report matches current
main(9503155da67a). Nightly0.0.46-nightly.20261006.2735(fd1c3386c4) is an ancestor, and these files are unchanged since that tag. Nightly20261005.2702does not contain #15328.What the code does
On the desktop app, every tab of the local server is a
<webview>the server drives over CDP (ServerBrowser.tsdesktopRenders/connectDesktop).ElectronBrowserHostmounts a webview for every server tab, includingreveal: false.open: falsedoes not add a right-panel surface (ChatView.tsx,hiddenTabIds), so the guest stays inactive.An inactive guest is parked at
-100000,-100000. On macOS it keepsvisibility: visibleso Electron 43 does not blank it, but it is still fully outside the window.hostedBrowserWebviewStyle.tsalready says Electron does not composite that guest. Recordings avoid this by marking the surface rendering-active and placing it at0,0behind the app. A background agent tab does not.preview_snapshotalways takes a full snapshot, including whenincludeImageis false (handlers.ts).ServerBrowserPage.snapshotrunspage.evaluate,ariaSnapshot(15s timeout), andcaptureViewporttogether.captureViewportsends a barePage.captureScreenshotwith no timeout. DOM reads can finish while the screenshot waits for a frame the offscreen guest never presents, so the combined promise does not settle at 15s.That snapshot runs on the tab's
SessionControlqueue, which has no per-action timeout. The broker givessnapshot15s, then returnsPreviewAutomationTimeoutErrorand disconnects the host. The in-flight capture is atryPromiseon a forked handler, so the disconnect does not cancel it. The server host ispreferred: trueand reconnects, which matches the trace. The next call on that tab waits behind the same capture.preview_openwithopen: truereveals the tab inside that same queue, so a reveal issued after a stuck snapshot never starts. Anopenon the preferred host gets the 660s budget meant for a Chromium install (PreviewAutomationBroker.ts).preview_statusandt3_preview_closeskip the queue.closedoes not abort the capture; destroying the webview is what lets CDP settle, which is why the queued calls answer in the same second as the close.visibleistab.viewers.size > 0in bothreportLiveTabsandpreview_status. Viewers are screencast subscribers only. A desktop<webview>never adds one, so a desktop-rendered tab staysvisible: falseafteropen: true.Likely causes (ranked)
-
Uncapped screenshot of an offscreen desktop guest, still holding the tab queue. This is the failure the traces show:
open: truesnapshots in tens of milliseconds,open: falsehits 15000ms every time, status stays instant, and a later reveal hangs until close. Headless server tabs do not use this webview placement. -
The broker timeout and the tab queue are different clocks. The 15s error is real, and so is the later work. Evicting the host does not free the tab.
Related, not duplicates
- feat(preview): run the browser on the environment server #15328 moved agent snapshots onto this server CDP path. That is the regression boundary versus nightly
2702. - fix(desktop): capture fresh preview frames in background windows #13600 (open) bounds and refreshes
capturePageinapps/desktop/src/preview/Manager.tsfor hidden or occluded windows. Agent snapshots no longer go through that function. Merging it does not capPage.captureScreenshothere and does not unblockSessionControl. - [Bug]: preview_snapshot can fail or time out on loaded pages and leave tab automation unusable #3713 (closed) was the same symptom on the old desktop host, which retried
capturePageat 1s times 3. - [Bug]: A timed-out preview_evaluate keeps running in the renderer and makes all preview tools report "No preview automation host" until it finishes #16264 and [Bug]: preview_wait_for still evicts a live host when its miss reply lands at the broker deadline #12898 are the evaluate and wait_for host-eviction cases. Neither operation ran in this report.
Next step
Bound
Page.captureScreenshotthe way desktopcapturePageis bounded, and make a broker timeout stop counting as the tab's current action so a lateropen: truecan reveal the guest. A background snapshot also needs a frame from a guest that is not on screen. The offscreen placement is why recordings force the guest inside the window.The
visible: falsereport is a separate bug in the same status payload. It can stay on this issue.Helpful follow-up
None. The version, the
open: true/open: falsesplit, and the trace are enough.-
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Oct 6, 2026 - added a commit that references this issue
on Oct 7, 2026 Note
Codex responding on behalf of Derek King.
Reproduced with a standalone local demo: Electron 44.4.2, macOS arm64.
- Before: DOM read completes in 0.2 ms; offscreen
Page.captureScreenshotremains pending after 15 seconds, blocking the next queued action. - Experimental repair: temporarily render the guest behind the app, capture natively with a deadline, then restore its position. 5/5 captures in 33–42 ms, preserving draft, session and document state.
Run
npm ci, thenenv -u ELECTRON_RUN_AS_NODE npm start. Fresh disposable profile; local synthetic page only.This isolates the Electron capture mechanism with a simple Promise queue. Full T3/provider/MCP integration remains unverified. It complements #16941's timeout recovery.
- Before: DOM read completes in 0.2 ms; offscreen
Same on macOS 26.7.1 arm64, Nightly
0.0.46-nightly.20261008.2833, Claude Code thread. The tab reportedvisible: false. Times are UTC, 2026-10-09.- 09:01:29:
preview_snapshottimed out after 15 s on a heavy React Flow canvas page. - Every later
preview_evaluateon that tab timed out at 15 s: 9 calls over 22 minutes, from 09:01:56 to 09:22:54.preview_statusstill answered immediately. - The tab's renderer process stayed alive with the same pid and normal memory (120–260 MB in
renderer-history.ndjson), so it wasn't a crash. t3_preview_closeat 09:23:18 released it, and a new tab worked.
Earlier the same morning, snapshot timeouts at 08:26, 08:41 and 08:48 were each followed by timeouts on that tab's next calls.
- 09:01:29:


What happened
Desktop app on nightly 2735. An agent opened background tabs (
preview_open,open: false) on github.com and example.com. Everypreview_snapshottimed out after 15s (3 of 3, across both tabs).preview_statusstill answered instantly.preview_open(tabId, open: true)then hung until the turn was interrupted (618s, then 358s).t3_preview_closeworked. Builds before 2735 snapshotted fine all day (482 snapshots, last at 17:16 CEST). Nopreview_evaluateorpreview_wait_forran before the first failure.A controlled rerun in a fresh thread confirmed the split: the same page opened with
open: truesnapshots in 38-65ms, while opened withopen: falseit times out every time.Diagnosis
Since #15328 the server's own browser is the preferred automation host, and with the desktop app attached it drives the tab's desktop
<webview>over CDP.ServerBrowserPage.snapshotrunspage.evaluate,ariaSnapshotandcaptureViewportin onePromise.all. OnlyariaSnapshothas a timeout, andcaptureViewportsends a barePage.captureScreenshot(ServerBrowserPage.ts:170,:191-200).The snapshot runs in the tab's
SessionControlqueue, which has no per-action timeout (SessionControl.ts:37-44,ServerBrowser.ts:1605-1617). When the capture never settles, the broker times out at 15s and evicts the host, but the tab queue stays blocked. Every later agent action on that tab waits behind it, includingopen: true: its reveal runs in the same queue (ServerBrowser.ts:1524-1545) and gets the 660s budget meant for Chromium installs (PreviewAutomationBroker.ts:564-565). Status and close skip the queue (ServerBrowser.ts:1463,:1589-1595), and closing the tab is what finally releases the stuck calls.Trigger, confirmed: the tab was opened in the background. Mechanism, not confirmed: most likely
Page.captureScreenshotwaits for a frame a hidden guest never presents (what #13600 describes for the desktop path). The server code assumes "a desktop-rendered tab is always on screen" (ServerBrowser.ts:1367), and the old desktop host bounded every capture (capturePage, 1s x 3 attempts,apps/desktop/src/preview/Manager.ts:130-135,:610-640). The server path has no bound.Side issue:
visibleinpreview_open/preview_statusistab.viewers.size > 0(ServerBrowser.ts:563,:1117), so a desktop-rendered tab always reportsvisible: false, even right afteropen: true.Steps to reproduce
preview_open({ url: "https://example.com/?test=visible", open: true, reuseExistingTab: false }), thenpreview_snapshot({ tabId }): works.preview_open({ url: "https://example.com/?test=background", open: false, reuseExistingTab: false })preview_snapshot({ tabId }): times out after 15000ms. Any further snapshot on that tab times out too.preview_status({ tabId }): instant,loading: false.preview_open({ tabId, open: true }): never returns (do this last, it hangs for up to 11 minutes).t3_preview_close({ tabId }): returns{}, and the queued calls get their answers at that moment.Version
0.0.46-nightly.20261006.2735 (fd1c338)
Environment
macOS 27.0 arm64, Node 26.8.2, desktop app with its local server
Evidence
Related issues
Introduced by #15328. #13600 (open) fixes the same capture stall for hidden or occluded windows on the desktop path only. #3713 had the same symptom on the old desktop host. #16264 and #12898 cover evaluate and wait_for, neither of which ran here.
Fix applied or workaround
None applied. Open agent tabs with
open: trueand snapshots work. Closing a stuck tab frees it. Nightly 20261005.2702 predates #15328 and still snapshots through the desktop host.Filed by
Claude Code (claude-opus-5-5) via t3 triage