Skip to content

[Bug]: Desktop app: server browser tabs never drain, so preview_open fails in every thread at 32 #24159

Open

Description

@edgarc-bbadmin

Before submitting

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

Related: #18231 (closed, not planned: agent tabs staying open is intended) and #18147 (desktop-rendered tabs skip idle cleanup). This issue is about what those two cause together. In the desktop app, the server's 32-tab budget fills up and never drains, and then preview_open fails in every thread until T3 Code restarts.

Area

apps/server

Steps to reproduce

  1. Use the desktop app with its local server.
  2. Over a day or two, let agents in several threads (delegated child threads are the quickest, e.g. verifiers) call preview_open and finish without calling t3_preview_close. Previews you open yourself from chat links or the Preview pane count too. Get to 32 server tabs in total.
  3. Close the Preview panes in those threads with the panel's close button.
  4. Wait more than 30 minutes.
  5. From a new thread that owns no tabs, call preview_open, with or without open: false.

Expected behavior

The new thread gets a tab. Agent tabs nobody has used for 30 minutes give up their slot, as #15328 describes: "agent tabs nobody is watching close after 30 minutes without an agent request."

Actual behavior

preview_open fails with tabLimit in every thread:

Too many server browser tabs are open. Close one with t3_preview_close, or reuse a tabId from preview_status tabs.

The caller can't act on this. t3_preview_list returns sessions: [] and preview_status returns available: false, tabId: null, because the tabs belong to other threads and t3_preview_close only closes the caller's own. Closing Preview panes doesn't help, and waiting doesn't help. Only restarting T3 Code (preview sessions are in-memory) or deleting the threads that hold the tabs frees them.

Why (code at main 66ee628):

  1. The limit is shared by every thread on the server. ServerBrowser.ts#L1263-L1274 counts every server tab against SERVER_TAB_LIMIT = 32: agent- and human-owned, in active, settled, archived or delegated-child threads. The message agents see (previewAutomation.ts#L985-L986) replaces the server's text, so it drops "(8 per agent session, 32 per server)". It reads as the caller's own problem.
  2. In the desktop app every server tab is desktop-rendered. For the primary environment the client opens previews on the server runtime (previewRuntime.ts#L13). The desktop host stays subscribed to every thread that ever had a tab (usePreviewSession.ts#L105) and mounts a native webview for each server tab (ElectronBrowserHost.tsx#L31-L39). That includes background child threads nobody is looking at. So createTab attaches the desktop page for each (ServerBrowser.ts#L721).
  3. Idle cleanup skips desktop-rendered tabs (fix(preview): keep desktop-rendered tabs out of idle cleanup #18147, ServerBrowser.ts#L1276-L1289). Together with point 2, the sweep never closes anything in the desktop app. fix(preview): keep desktop-rendered tabs out of idle cleanup #18147's own description notes "retained desktop tabs continue to count toward existing tab limits until closed". The server also can't tell when a person is looking at a desktop tab: visible is viewers.size > 0 (ServerBrowser.ts#L592), and the desktop only sends attached/detached (DesktopBrowserHost.ts#L181).
  4. Only thread deletion releases a thread's tabs. preview.cleanup is enqueued only on delete (ThreadDeletion.ts#L224-L227). Archive cleans up terminals but not previews (Orchestrator.ts#L3774-L3783), and settling closes idle terminals but not previews (ThreadSettlementService.ts#L553). Delegated child threads end completed and are never settled, archived or deleted.
  5. The Preview panel's close button keeps the tab. It collapses into the mini-player (useRightPanelActions.ts#L162-L176). Only the × on the browser's tab in the tab strip closes it.

#22557 deleted the per-agent limit test and #18147's retention test, so nothing on main covers this behavior now.

On the machine where this happened, the local server had been up for about 3 days. Desktop traces show desktop preview tabs growing by about 29 net over roughly 10 hours and never shrinking. Then every preview_open failed from that point on, across all threads, including verifier child threads that had never opened a tab.

Possible fix. Keep #18147's rule while the server has room, and when the server is full, close the agent-owned tabs idle longest (past the 30-minute window, no streamed viewer), desktop-rendered or not, until one slot is free. Never close tabs a person opened. Then the budget can't deadlock, and below the limit nothing changes. Separately, and probably worth their own issues:

  • release a thread's previews on archive, settle and delegated-task completion, the way terminals are;
  • have the desktop report whether a desktop-rendered tab is actually on screen, so the idle sweep can treat unwatched ones like headless tabs;
  • make the tabLimit message say the limit covers all threads and that other threads' tabs can only be closed from those threads.

Impact

Blocks work completely

Version or commit

0.1.1-nightly.20261020.3411 (installed). Code reading at main 66ee628, same behavior.

Environment

macOS (Darwin 27), Apple silicon, T3 Code (Nightly) desktop app with its local server; Claude and Codex agents.

Logs or stack traces

PreviewAutomationControlInterruptedError: Too many server browser tabs are open. Close one with t3_preview_close, or reuse a tabId from preview_status tabs.
    at classifyResponseError (apps/server/dist/binCli-*.mjs)
    at PreviewAutomationBroker.awaitResponse
    at PreviewAutomationBroker.invoke
    at PreviewToolkit.invoke
Caused by: Too many server browser tabs are open (8 per agent session, 32 per server). Close one with t3_preview_close or reuse one from preview_status tabs.

Workaround

Quit and reopen T3 Code while no agent turns are running. Preview sessions are in-memory, so this frees every tab; threads, worktrees and browser profiles survive. To make it happen less often, close browsers with the × on their tab rather than the panel's close button, and have agents that open previews (verifiers) close every tab they opened before they finish.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions