Skip to content

[Bug]: Settling a thread leaves its preview browser renderers running, leaking ~250 MB each until restart #16371

Closed as not planned

Description

@niko-itswld

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/web, apps/server, apps/desktop

Summary

Settling a thread doesn't close its preview browser tabs. Each preview tab is a <webview> with its own Chromium renderer process, so every thread that ever opened a preview keeps a renderer alive until T3 Code restarts. On an agent-heavy workflow these pile up fast.

On my machine after ~3 days of normal use: 35 orphaned preview renderers using ~8.4 GB of RAM. All of them belonged to threads I had already settled or closed, and all sat at 0% CPU. The dev servers they had pointed at were long gone. The machine had pushed 6.5 of 8 GB into swap. The only fix was to quit T3 Code, which interrupts any running threads.

Steps to reproduce

  1. In the desktop app, have an agent open a preview in a thread (e.g. the preview_open MCP tool against a local dev server).
  2. Let the turn finish, then settle the thread.
  3. Check Activity Monitor or ps -axo pid,rss,etime,command | grep 'T3 Code.*Renderer'.
  4. Repeat across a few threads.

Expected behavior

Settling a thread releases what it was holding. Settlement already does this for idle shells (ThreadSettlementService → terminals.closeIdle), so its preview tabs should close too, along with their renderer processes.

Actual behavior

The renderer for every preview tab in every settled thread stays alive indefinitely. One renderer accumulates per thread that ever used a preview, and memory only grows until the app restarts. Nothing in the UI shows these tabs still exist: t3_preview_list from a new thread reports no sessions, and the settled threads are out of the sidebar, so there's no obvious way to find or close them.

What I observed (renderer client IDs climbing from 119 to 234 over ~2 days, every one idle):

  PID    RSS     ELAPSED  TYPE      CLIENT ID
27541  1167MB  02-21:56  renderer  4      <- main window
16426    94MB  01-21:55  renderer  119    <- preview webview
34161   290MB  01-21:54  renderer  130
52613   293MB  01-21:00  renderer  132
...     (31 more, 128–347 MB each, all 0.0% CPU)
41623   303MB     16:49  renderer  234

Every leaked renderer has --disable-blink-features, which the main window renderer lacks, i.e. they're preview guests.

Root cause (from reading main @ 0ecb78e)

  • apps/web/src/browser/ElectronBrowserHost.tsx is mounted app-wide and renders a HostedBrowserWebview for every preview session of every thread in useActivePreviewSessions() (previewStateStore.ts). That includes threads that aren't visible, settled ones included.
  • A webview is only released when its server-side session closes (desktopTabLifetime.ts → previewBridge.closeTab on unmount).
  • apps/server/src/orchestration-v2/ThreadSettlementService.ts handles thread.settled by calling only closeIdleTerminals(threadId). It never closes the thread's preview sessions. The comment there ("Settling closes the thread's shells that sit at an idle prompt, so they stop holding the worktree") shows settlement is already meant to release per-thread resources. Previews were just left out.

So nothing ever closes a settled thread's preview sessions, and the host keeps rendering them.

Suggested fix

PreviewManager.close (apps/server/src/preview/Manager.ts) already takes { threadId, tabId? }, and omitting tabId closes every tab for the thread. Calling it from the thread.settled handler next to closeIdleTerminals should be a small change. The same handler is probably the right place to close the thread's Device panel sessions (deviceService.close) too.

If some users want previews to survive settlement, put this behind a setting ("Close preview browsers when a thread is settled", on by default). The default matters more than the toggle: the current behavior leaks memory without limit and gives no sign that it's happening.

Impact

Major degradation or frequent failure

This gets worse the more you use the product as intended. Agent workflows that verify UI open previews in many threads, so memory grows with every thread, invisibly, until the machine swaps or the user restarts the app mid-work. Users will blame "T3 Code is a memory hog" rather than a leak. It should be fixed soon.

Version or commit

0.0.46-nightly.20261003.2610 (desktop). Root cause read from main @ 0ecb78e.

Environment

macOS 26.7.1, Apple Silicon, 48 GB RAM, T3 Code (Nightly) desktop

Workaround

Quit and reopen T3 Code. This interrupts any running threads.

Activity

  1. juliusmarminge commented on Oct 6, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Thanks for the detailed write-up and the renderer measurements, @niko-itswld. This is the same leak tracked in #15579: ElectronBrowserHost keeps a live HostedBrowserWebview for every preview session in every thread, and nothing (settlement included) ever closes those sessions, so hidden preview renderers pile up until restart. The triage there already lists closing a thread's previews on idle/settlement, next to closeIdleTerminals, as a fix option.

    Closing this as a duplicate so the discussion stays in one place. Your macOS numbers (35 renderers, ~8.4 GB) and the PreviewManager.close({ threadId }) suggestion would be really useful as a comment on #15579. If you think settlement needs separate tracking, say so and we can reopen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    duplicateThis issue or pull request already exists

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions