You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Preview tabs from other threads stay alive as hidden, unthrottled webviews until restart (13 tabs, ~2.7 GB) #15579
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
Over a working day, let agents in several threads call preview_open (13 tabs across a few threads here).
Never close those previews by hand. The agents never call preview_close either.
Switch to another thread with the preview panel closed and leave the app running.
Expected behavior
Preview tabs that the user can't see, in threads they're not looking at, should be throttled, frozen or discarded. They should also be closed eventually, for example when the thread is idle, archived or deleted. They shouldn't keep a full renderer running for the rest of the session.
Actual behavior
Nothing ever closes them. The desktop trace log shows 13 createTab and 0 closeTab since start. ElectronBrowserHost mounts a HostedBrowserWebview for every session in every thread. Hidden ones are only moved offscreen with CSS, and there is no backgroundThrottling, freeze or discard. The pages keep running scripts, including the ad and tracker iframes of the visited sites. PreviewManager holds the sessions in memory until an explicit close(), and ResourceCleanupService doesn't close previews.
Measured 2026-10, with the T3 Code window visible on another thread and no preview panel open:
18 renderer processes, about 2.7 GB PSS in total. Most of that is the preview tabs; the main window is one renderer.
The main process "Gamepad polling" thread wakes about 245 times a second; the same thread in another Chromium browser here is idle. T3 Code itself doesn't use the Gamepad API, so this is presumably a script on one of the preview pages calling navigator.getGamepads() (fingerprinting scripts do this).
None of this is visible in the UI, so the user can't tell that the previews are still open.
Related: #14966 (sessions outlive a deleted thread), #14730 (inactive macOS previews keep rendering) and #13610 (orphaned hidden tab leaks GPU memory). This issue is the Linux, cross-thread version: threads that are neither deleted nor orphaned.
Impact
Major degradation or frequent failure
Version or commit
v0.0.45 (desktop)
Environment
NixOS, Linux 7.2.8, GNOME (Wayland), Intel Lunar Lake laptop on battery
Workaround
Restart T3 Code (preview sessions aren't persisted), or open each thread and close its previews.
Thanks so much for the careful measurements, @davidvornholt, they made this easy to trace. I checked it on current main (eac52f0087) and on v0.0.45 (6c8fed35dd), and the relevant code is the same on both: preview tabs you aren't looking at stay mounted as live Chromium guests for the rest of the session.
What I found
ElectronBrowserHost sits outside the router and mounts a HostedBrowserWebview for every preview session in every thread, not just the thread on screen (apps/web/src/browser/ElectronBrowserHost.tsx). A thread stays in that index as long as it has sessions (syncActivePreviewThread in apps/web/src/previewStateStore.ts).
closeTab runs only when the React guest unmounts (apps/web/src/browser/desktopTabLifetime.ts), which happens only after an explicit preview.close from the UI or from preview_close / t3_preview_close. Closing the panel or switching threads doesn't close tabs.
On Linux, an inactive guest is parked at (-100000, -100000) with visibility: hidden (apps/web/src/browser/hostedBrowserWebviewStyle.ts). data-preview-rendering="suspended" is only a marker for automation. Nothing calls Chromium freeze or discard, and nothing destroys the guest.
The webview preferences are only contextIsolation=false,sandbox=true,nodeIntegration=false (apps/desktop/src/preview/WebviewPreferences.ts). Electron's default background throttling only applies once Chromium considers the page backgrounded, and the guest is temporarily set to setBackgroundThrottling(false) while recording or in picture-in-picture.
The repo never calls navigator.getGamepads(), so the Gamepad polling wakeups very likely come from a script inside one of those guests, which fits your theory.
ResourceCleanupService only cleans up terminals and attachments, and thread deletion, archive, and settlement don't touch previews (apps/server/src/orchestration-v2/ThreadDeletion.ts).
How this differs from the related issues you linked:
Really suspending inactive guests (panel closed, other thread, and not recording, in picture-in-picture, or held by automation). That could mean freezing them, which keeps page state, or discarding the webview and recreating it from the session URL when the thread is shown again, which frees the memory but reloads the page.
Keeping recording, picture-in-picture, and automation on the existing renderingActive path.
Separately, closing previews when a thread is idle or settled, the way idle terminals already close. That would change current behavior, since tabs are kept today so they're still there when you come back.
Restarting the app or closing each thread's previews remains the workaround for now.
Reopening: #16961 (merge d9f7c0c) still listed Fixes for this issue, but the hidden-tabs commit was reverted before merge (f3bbe7f — Revert "fix(web): hidden browser tabs on macOS stop painting"). main still keeps inactive macOS guests paintable via keepPaintableWhenInactive: isMacPlatform(...), so this is not fixed yet.
Additional measured incident on macOS with the newer server-hosted browser path. Posting here to avoid duplicating the broader retained-preview report; the headless rendering path may warrant separate tracking. Related: #17090.
Environment:
T3 Code Nightly 0.0.46-nightly.20261009.2861, installed commit 3b6af0bd1600.
macOS 26.6, build 25G72, Apple Silicon (Mac17,9), 15 CPU cores, 48 GiB RAM.
Managed chrome-headless-shell 154.0.8037.92.
The user reported mild lag across the system. During read-only inspection:
A T3-owned headless browser's graphics process consumed 140.9% CPU in a timed top sample and 153.7% in a separate rolling ps reading (100% = one core).
Browser launch flags included --disable-gpu --enable-unsafe-swiftshader --force-device-scale-factor=2 --disable-background-timer-throttling --disable-renderer-backgrounding.
Its graphics process used --use-angle=swiftshader-webgl.
The browser had been running for approximately 4 hours 42 minutes and was parented by the T3 backend.
T3's Electron GPU/interface processes and WindowServer were also busy; total system CPU still had about 60% idle capacity.
Memory pressure was normal, swap was zero, data-volume free space was about 846 GiB, and pmset -g therm reported no thermal/performance warning.
preview_status and t3_preview_list for the diagnostic thread returned no tabs. These tools are thread-scoped, so this does not establish that tabs in other threads were inactive or had no automation consumers. Their individual visibility was not inspected.
At the user's request to close previews, SIGTERM was sent only to the identity-verified shared headless browser root process. Its graphics process and all managed headless-browser processes then disappeared; the T3 desktop and backend remained running. A subsequent timed system sample had 72.4% CPU idle (the preceding sample had 74.9%). This was a short live observation, not a controlled benchmark, and no claim is made that all perceived lag was resolved. The browser was stopped at process level because the available normal tab-close tool only covered the diagnostic thread; preview-session metadata cleanup was not verified.
This supports investigating the CPU cost and lifecycle of retained server-hosted browser tabs on local macOS installations, in addition to the earlier native webview reports. It does not establish a memory leak, an exact reproduction sequence, or that SwiftShader alone caused the system-wide lag. Inactive tabs without automation, recording, or viewing consumers should suspend rendering, and users should be able to discover and close retained tabs across threads.
Measurements and this comment prepared with Codex on the user's behalf.
Adding another symptom from #20890 (closed as a duplicate of this one): when the project is a browser game or any page that autoplays audio, every lingering preview tab keeps playing its own copy of the soundtrack. After a few rounds of agent checks across threads, you hear dozens of copies of the same loop playing over each other, and the UI gives no way to tell which tabs are making the noise. Settling the threads doesn't stop it either. On macOS here (v0.0.45).
I've opened #20893 in Ideas for user-facing controls (mute preview tabs by default, an audio indicator, "Mute all" / "Close all preview tabs") as a stopgap alongside the lifecycle fix here.
Activity
juliusmarminge commentedon Oct 4, 2026
Note
Grok responding on behalf of Julius.
Triage
Thanks so much for the careful measurements, @davidvornholt, they made this easy to trace. I checked it on current
main(eac52f0087) and on v0.0.45 (6c8fed35dd), and the relevant code is the same on both: preview tabs you aren't looking at stay mounted as live Chromium guests for the rest of the session.What I found
ElectronBrowserHostsits outside the router and mounts aHostedBrowserWebviewfor every preview session in every thread, not just the thread on screen (apps/web/src/browser/ElectronBrowserHost.tsx). A thread stays in that index as long as it has sessions (syncActivePreviewThreadinapps/web/src/previewStateStore.ts).closeTabruns only when the React guest unmounts (apps/web/src/browser/desktopTabLifetime.ts), which happens only after an explicitpreview.closefrom the UI or frompreview_close/t3_preview_close. Closing the panel or switching threads doesn't close tabs.(-100000, -100000)withvisibility: hidden(apps/web/src/browser/hostedBrowserWebviewStyle.ts).data-preview-rendering="suspended"is only a marker for automation. Nothing calls Chromium freeze or discard, and nothing destroys the guest.contextIsolation=false,sandbox=true,nodeIntegration=false(apps/desktop/src/preview/WebviewPreferences.ts). Electron's default background throttling only applies once Chromium considers the page backgrounded, and the guest is temporarily set tosetBackgroundThrottling(false)while recording or in picture-in-picture.navigator.getGamepads(), so the Gamepad polling wakeups very likely come from a script inside one of those guests, which fits your theory.ResourceCleanupServiceonly cleans up terminals and attachments, and thread deletion, archive, and settlement don't touch previews (apps/server/src/orchestration-v2/ThreadDeletion.ts).How this differs from the related issues you linked:
preview_resizetimeout. These tabs were never evicted.Likely fix area
A few options a maintainer could weigh:
renderingActivepath.Restarting the app or closing each thread's previews remains the workaround for now.
A maintainer will decide on the fix direction.
juliusmarminge commentedon Oct 8, 2026
Note
Grok responding on behalf of Julius.
Reopening: #16961 (merge
d9f7c0c) still listedFixesfor this issue, but the hidden-tabs commit was reverted before merge (f3bbe7f— Revert "fix(web): hidden browser tabs on macOS stop painting").mainstill keeps inactive macOS guests paintable viakeepPaintableWhenInactive: isMacPlatform(...), so this is not fixed yet.TheCommandCat commentedon Oct 9, 2026
Additional measured incident on macOS with the newer server-hosted browser path. Posting here to avoid duplicating the broader retained-preview report; the headless rendering path may warrant separate tracking. Related: #17090.
Environment:
0.0.46-nightly.20261009.2861, installed commit3b6af0bd1600.Mac17,9), 15 CPU cores, 48 GiB RAM.chrome-headless-shell154.0.8037.92.The user reported mild lag across the system. During read-only inspection:
topsample and 153.7% in a separate rollingpsreading (100% = one core).--disable-gpu --enable-unsafe-swiftshader --force-device-scale-factor=2 --disable-background-timer-throttling --disable-renderer-backgrounding.--use-angle=swiftshader-webgl.pmset -g thermreported no thermal/performance warning.preview_statusandt3_preview_listfor the diagnostic thread returned no tabs. These tools are thread-scoped, so this does not establish that tabs in other threads were inactive or had no automation consumers. Their individual visibility was not inspected.At the user's request to close previews, SIGTERM was sent only to the identity-verified shared headless browser root process. Its graphics process and all managed headless-browser processes then disappeared; the T3 desktop and backend remained running. A subsequent timed system sample had 72.4% CPU idle (the preceding sample had 74.9%). This was a short live observation, not a controlled benchmark, and no claim is made that all perceived lag was resolved. The browser was stopped at process level because the available normal tab-close tool only covered the diagnostic thread; preview-session metadata cleanup was not verified.
This supports investigating the CPU cost and lifecycle of retained server-hosted browser tabs on local macOS installations, in addition to the earlier native webview reports. It does not establish a memory leak, an exact reproduction sequence, or that SwiftShader alone caused the system-wide lag. Inactive tabs without automation, recording, or viewing consumers should suspend rendering, and users should be able to discover and close retained tabs across threads.
Measurements and this comment prepared with Codex on the user's behalf.
rene-demonsters commentedon Oct 17, 2026
Adding another symptom from #20890 (closed as a duplicate of this one): when the project is a browser game or any page that autoplays audio, every lingering preview tab keeps playing its own copy of the soundtrack. After a few rounds of agent checks across threads, you hear dozens of copies of the same loop playing over each other, and the UI gives no way to tell which tabs are making the noise. Settling the threads doesn't stop it either. On macOS here (v0.0.45).
I've opened #20893 in Ideas for user-facing controls (mute preview tabs by default, an audio indicator, "Mute all" / "Close all preview tabs") as a stopgap alongside the lifecycle fix here.