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). Nightly 0.0.46-nightly.20261006.2735 (fd1c3386c4) is an ancestor, and these files are unchanged since that tag. Nightly 20261005.2702 does 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.ts desktopRenders / connectDesktop). ElectronBrowserHost mounts a webview for every server tab, including reveal: false. open: false does 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 keeps visibility: visible so Electron 43 does not blank it, but it is still fully outside the window. hostedBrowserWebviewStyle.ts already says Electron does not composite that guest. Recordings avoid this by marking the surface rendering-active and placing it at 0,0 behind the app. A background agent tab does not.
preview_snapshot always takes a full snapshot, including when includeImage is false (handlers.ts). ServerBrowserPage.snapshot runs page.evaluate, ariaSnapshot (15s timeout), and captureViewport together. captureViewport sends a bare Page.captureScreenshot with 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 SessionControl queue, which has no per-action timeout. The broker gives snapshot 15s, then returns PreviewAutomationTimeoutError and disconnects the host. The in-flight capture is a tryPromise on a forked handler, so the disconnect does not cancel it. The server host is preferred: true and reconnects, which matches the trace. The next call on that tab waits behind the same capture.
preview_open with open: true reveals the tab inside that same queue, so a reveal issued after a stuck snapshot never starts. An open on the preferred host gets the 660s budget meant for a Chromium install (PreviewAutomationBroker.ts). preview_status and t3_preview_close skip the queue. close does 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.
visible is tab.viewers.size > 0 in both reportLiveTabs and preview_status. Viewers are screencast subscribers only. A desktop <webview> never adds one, so a desktop-rendered tab stays visible: false after open: 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
- #15328 moved agent snapshots onto this server CDP path. That is the regression boundary versus nightly
2702. - #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. - #3713 (closed) was the same symptom on the old desktop host, which retried
capturePageat 1s times 3. - #16264 and #12898 are the evaluate and wait_for host-eviction cases. Neither operation ran in this report.
Next step
Bound Page.captureScreenshot the way desktop capturePage is bounded, and make a broker timeout stop counting as the tab's current action so a later open: true can 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: false report 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: false split, and the trace are enough.