Repository navigation
[Bug]: Linux: a hidden agent preview tab held Chromium's "Capturing" wake lock for hours, and t3_preview_close couldn't close the tab #17180
Description
Activity
Note
Grok responding on behalf of Julius.
Thanks for the detailed report and the inhibitor before/after. I read the code on
main(a4c9494b0) but didn't reproduce either symptom at runtime, so treat the following as code reading.1.
t3_preview_closefails with a generic errorThis looks like a real bug, and your guess about the owner appears to be right.
- The broker tags every request with
agentSessionId = environmentId + providerSessionId(apps/server/src/mcp/PreviewAutomationBroker.ts:177-178, sent at:640). It doesn't use the thread id. - The server browser's
closegoes throughrequireTab, which rejects a tab whosecontrol.agentIdisn't the caller's session with"This tab belongs to another agent session or a human. Open your own tab."(reasonagentMismatch) (apps/server/src/preview/ServerBrowser.ts:1420-1449, close at:1587-1596). Tabs are created withautomationOwner: request.agentSessionId(:1510-1516). So if the later run got a new provider session, its close would probably be refused here, before any desktop IPC. That would fit the ~20 msinvoke→respondand the missing close IPC. - That reason does survive back to the server as
PreviewAutomationControlInterruptedErrorwithreason(ServerBrowserPage.ts:43-47,PreviewAutomationBroker.ts:265-271). Butt3_preview_closethen drops it withEffect.mapError(unavailable)(apps/server/src/mcp/toolkits/previewControls/handlers.ts:36-38, and:41-43for the non-server path). That becomesorchestration_error/ "The operation could not be completed." (apps/server/src/mcp/threadAccess.ts:19-23). It's the same masking pattern as [Bug]: MCP thread tools replace the orchestrator's rejection reason with "The operation could not be completed." #15586, which wasn't fixed for the preview-control tools. The tool's failure union already includesPreviewAutomationUnavailableError(previewControls/tools.ts:16), so it could carry a real reason. - The tool description says "Close one preview tab owned by this thread" (
previewControls/tools.ts:43), but the server-runtime path actually enforces ownership by agent session. That appears to leave a new run in the same thread with no way to close the old run's tab.
Likely fix: surface the broker error instead of
unavailable(). Then either lett3_preview_closeclose server-runtime tabs owned by an earlier session of the same thread (for example viaPreviewManager.close, as the non-server branch does), or make the description match the per-session rule.2. "Capturing" wake lock on the stale hidden tab
I couldn't pin down which call took the lock. Here's what the code shows:
- Our only call on a server-runtime guest that should take Chromium's stay-awake ("Capturing") capturer is CDP
Page.captureScreenshotincaptureViewport(apps/server/src/preview/ServerBrowserPage.ts:154-175). It's used bypreview_snapshot(ServerBrowser.ts:1655-1658), recording start (:1217) and the viewer still (:2013-2021). In upstream Chromium,PageHandler::CaptureScreenshotholdsIncrementCapturerCount(..., stay_awake=true)until the screenshot callback runs or the pending request is dropped. Both of your snapshots returned, so I didn't find a path in our code that should leave that handle open. - The desktop's own
webContents.capturePage()calls pass nostayAwake(apps/desktop/src/preview/Manager.ts:305,:631,:2520,:3150). Electron defaults that tofalse, so they shouldn't take this lock (checked against Electron's current source, not 44.4.2 specifically). Tab recording throughgetDisplayMediawould hold one, but you saw no frame-capture span, and nothing here suggests it ran. - So either one of those CDP captures on the hidden, offscreen guest never finished on the Chromium side (hidden-guest capture stalls are a known pattern, see preview_snapshot on a background tab never returns and blocks every later action on that tab #16567), or Chromium/Electron didn't release the capturer. From here I can't tell whether that's our bug or an upstream one.
What our code clearly controls is how long the tab lived.
closeIdleAgentTabswould close an agent tab that nobody is viewing afterAGENT_TAB_IDLE_MS= 30 min (ServerBrowser.ts:335,:1085-1092). But its only caller ispreview_opencreating a new tab (:1503), and nothing runs it on a timer. An agent that stops using its tab therefore keeps the hidden guest, and whatever it holds, until restart. Here that was 3+ hours, where a periodic sweep would likely have closed it after about 30 minutes and probably dropped the lock along with the WebContents. This overlaps #15579, but that issue (and the hidden-tab part of #16961) is about rendering and throttling, not closing idle agent tabs.Related
- [Bug]: MCP thread tools replace the orchestrator's rejection reason with "The operation could not be completed." #15586: same error masking, fixed for the thread tools only
- [Bug]: Preview tabs from other threads stay alive as hidden, unthrottled webviews until restart (13 tabs, ~2.7 GB) #15579: hidden preview tabs live until restart; fix(desktop): browser tab fixes for fullscreen, shortcuts, links, reload and hidden tabs #16961 has a partial fix for it (visibility only)
- preview_snapshot on a background tab never returns and blocks every later action on that tab #16567: snapshot of a background tab can hang
- [Bug]: Desktop app held a Chromium "Video Wake Lock" for 14 hours, so the Mac never turned off its display or locked #15343: separate leaked wake lock on macOS ("Video Wake Lock")
- [Bug]: Element picker drops screenshot after five-second capture timeout #10366: capture timeout; a JS timeout doesn't release a native capture
None of the open PRs I checked (#8981, #16961, #14985, #13600) changes
t3_preview_closeerror mapping, its ownership check, or idle-tab cleanup. I couldn't fully rule out a wake-lock fix in #8981 or #13600, since both touch background-capture paths.- The broker tags every request with
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Oct 8, 2026
Before submitting
Area
apps/desktop
Steps to reproduce
I don't have a deterministic repro yet. What happened:
preview_open,preview_navigate,preview_evaluateandpreview_snapshottwice. Both snapshots returned in about 0.25 s. Nobody opened the preview panel, and the agent never closed the tab (runtime: "server",reveal: false)."Capturing".Expected behavior
A preview tab nobody is looking at shouldn't hold a display wake lock once the agent's calls have returned. An agent should also be able to close a preview tab its own thread opened.
Actual behavior
GNOME's inhibitor list held two idle inhibitors (flag 8 = idle):
"Capturing"is the wake lock Chromium'sWebContentsImpltakes while a capturer withstay_awake=trueis attached (GetWakeLock(kPreventDisplaySleepAllowDimming, ..., "Capturing")). CDPPage.captureScreenshottakes one and releases it when the screenshot callback runs, and tab capture (WebContentsFrameTracker) holds one while the stream is open. I found nopowerSaveBlockerand nostayAwakeinapp.asar, so the lock came from one of those paths.According to
renderer-history.ndjson, the only guest still alive was that stale preview tab (renderer created at 21:51,webContentsId2). The thread had a second preview tab, which closed at 22:09. When I killed the stale tab's renderer process, both inhibitors disappeared within 2 seconds. T3 Code immediately recreated the webview aswebContentsId4, and the lock did not come back. So a capturer on that tab's old WebContents was never released.I couldn't tell which call took the lock.
desktop.trace.ndjsonhas nostartFrameCapturespan for that tab, and bothpreview_snapshotcalls returned. One possibility is aPage.captureScreenshotorcapturePageon the hidden guest that never completed. The bundle already wrapscapturePagein a timeout because it can stall (see #10366), but a JS-side timeout doesn't release the native capturer handle.On a stock GNOME session, an idle inhibitor like this keeps the session from going idle, so the screen doesn't blank and automatic suspend doesn't happen. I didn't see that effect myself: on this machine GNOME's own idle blanking is turned off and a separate script powers the panel off based on Mutter's idle time, which ignores inhibitors. So I can confirm the leaked lock, but not a blank screen that failed to happen because of it.
Closing the tab from the agent didn't work either. Later, in a new run of the same thread, the agent called
t3_preview_closewith that tab'stabId. It failed twice with{"code":"orchestration_error","message":"The operation could not be completed."}. The server trace showsPreviewAutomationBroker.invoke→respondin about 20 ms, and the desktop trace shows no close IPC at all. Thet3_preview_closehandler maps every broker failure tounavailable(), which is the same masking as #15586 (fixed there for thread tools), so the actual reason is lost. My guess is the tab's automation owner was the agent session from the earlier run. The tool description says "Close one preview tab owned by this thread", though.Impact
Minor bug or occasional failure
Version or commit
0.0.46-nightly.20261007.2774 (AppImage; Electron 44.4.2, Chrome 152.0.7977.130)
Environment
Ubuntu 26.04.1 LTS, GNOME Shell 50.1 (Wayland), Linux 7.0.0, x86-64, Codex provider
Logs or stack traces
Workaround
Check
gdbus call --session -d org.gnome.SessionManager -o /org/gnome/SessionManager -m org.gnome.SessionManager.GetInhibitorsfor at3code"Capturing" inhibitor. Then either restart T3 Code, or kill the preview renderer (itsrendererPidforsurface: "preview"in~/.t3/userdata/logs/renderer-history.ndjson).Related: #15343 (a leaked wake lock on macOS, but there it's Chromium's "Video Wake Lock"), #15579 (hidden preview tabs stay alive until restart), #15586 (generic MCP error message), #10366 (capture timeout).