Skip to content

fix(server): a timed-out preview call no longer evicts the shared server browser - #36241

Merged
juliusmarminge merged 2 commits into
mainfrom
factory/issue-16921-a34970
Nov 13, 2026
Merged

juliusmarminge merged 2 commits into
mainfrom
factory/issue-16921-a34970

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

In PreviewAutomationBroker.invoke, a foreground request that got no answer within timeoutMs + HOST_RESPONSE_GRACE_MS called disconnect(..., true). That fails every pending request on the connection. For the server browser (SERVER_BROWSER_AUTOMATION_CLIENT_ID), one host is shared by all threads in the environment, so one stalled call (for example a second call queued behind a long waitFor on the same tab) failed other threads' unrelated in-flight calls with PreviewAutomationClientDisconnectedError, and new calls got NoAvailableHost until the host re-registered. The fix skips the timeout eviction when the connection is the server browser. The stalled call still fails with its own PreviewAutomationTimeoutError, and a late response is dropped by respond() because the pending entry is already removed. Eviction for desktop and web hosts is unchanged; it exists to recover a hung remote stream, which cannot happen to the in-process server browser, and the ServerBrowser reconnect loop still handles real disconnects. The change is 5 lines in one file, with no new regex or parser. I reproduced the bug with the repro branch's unit test, which fails on the base, rather than by rerunning the reporter's live two-thread dev-server repro.

Fixes #16921

Reproduction on main

Commit 9b6dde9 (origin/main). Ran T3CODE_SERVER_BROWSER_SANDBOX=0 T3CODE_DEV_SEED=0 vp run dev (fresh DB), added a project via node src/bin.ts project add, started two threads A and B with thread start --provider devReplay --model question (turns stay active waiting for an answer), and drove the real /mcp endpoint with each thread's own MCP bearer token from a scratch Node script. Served page A at 127.0.0.1 (static) and page B at localhost (text 'Ready now' appears after 28 s). Steps: A preview_open page A; B preview_open page B; B preview_wait_for {tabId:B, text:'Ready now', timeoutMs:40000}; A preview_wait_for {tabId:A, text:'never appears', timeoutMs:25000}; 1 s later, in parallel, A preview_evaluate {tabId:A, expression:'document.title'} (queued behind A's own wait_for on the same tab). Observed at +18.7 s: A evaluate -> 'Preview automation evaluate timed out after 15000ms.'; at the same instant B's wait_for -> 'Preview automation client server-browser disconnected during waitFor.' (it would have succeeded at 28 s); A's wait_for got the same disconnect; B's preview_status immediately after -> 'No preview automation host is available for status ...'. Control run: when A only froze its page and then snapshotted it, the browser answered with a typed timeout at about 10 s and nothing was evicted, so a single slow call does not trigger it (this is why the earlier #34970 attempt passed on both commits). Unit test on branch factory/repro-16921 (PreviewAutomationBroker.test.ts, 'keeps another thread's request alive when one request times out on the shared server browser') fails on main with PreviewAutomationClientDisconnectedError for the other thread's waitFor.

Evidence

Run two active replay threads through the real MCP endpoint: B waits up to 40s for text appearing at 28s; A waits 25s for absent text, then queues evaluate after 1s.

Before After
Commit 9b6dde9a23 6fb4635ccf
Outcome B disconnected at +17.0s; status reported no host A timed out independently; B completed at +27.8s
Capture Before: shared host evicted After: B completes
Recording Before recording After recording

Scenario source. Environment: Linux, server Chromium, fresh synthetic state, 1280×800 transcript capture. scenarios:prove reported failsOnBase=true and passesOnHead=true.

Not shown:

  • Native Windows execution was not exercised; the supplied shared-browser reproduction was demonstrated on Linux.

Tests

vp test run src/mcp/PreviewAutomationBroker.test.ts -t 'keeps another thread' without the fix: fails with PreviewAutomationClientDisconnectedError. vp test run src/mcp/PreviewAutomationBroker.test.ts src/preview/ServerBrowser.test.ts with the fix: 110 passed. vp run check:changed: format, lint, knip, typecheck (3 packages), tests (2 files) all passed.

Not in this change

  • SessionControl.agent in ServerBrowser checks a queued agent operation's deadline only when it starts, so a call waiting behind a busy tab still gets no typed timeout of its own from the browser; it now fails alone through the broker's timeout instead of evicting the host.
  • The comment in ServerBrowser.ts above the host reconnect loop says the broker disconnects timed-out hosts including slow first installs; that no longer applies to the server browser.

Who did the work

  • Written by Claude Opus 5.5, in Claude Code
  • Reviewed by GPT-6.1 Sol, in Codex, and by Claude Opus 5.5, each on its own
  • Proof captured by GPT-6.1 Sol and checked by Claude Opus 5.5

Run by the t3code factory in T3 Code.

juliusmarminge and others added 2 commits November 13, 2026 16:59
@github-actions github-actions Bot added vouch:trusted Author is trusted by repo permissions or the VOUCHED list. size:XS 0-9 changed lines (additions + deletions). labels Nov 13, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 6fb4635

Macroscope's review found this PR approvable — Timed-out preview requests no longer evict the shared server browser, avoiding disruption to other threads. Timeout errors and pending-request cleanup remain unchanged, and other browser hosts retain their existing eviction behavior.

You can add or adjust custom eligibility rules. Learn more.

@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 21.5 KiB 21.5 KiB 0 B (0.0%) 24.4 KiB ✅
Codex Live turn messages 1 1 0 (0.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.9 KiB 21.9 KiB 0 B (0.0%) 24.4 KiB ✅
Claude Live turn messages 1 1 0 (0.0%) 8 ✅

Other scenarios

All 64 scenario measurements match main.

All 64 scenario measurements
Provider Scenario Metric Main baseline This PR Impact PR ceiling
Codex short turn resume WebSocket decoded 10.5 KiB 10.5 KiB 0 B (0.0%) 12.7 KiB ✅
Codex short turn resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Codex long turn running, cold open HTTP decoded 281.7 KiB 281.7 KiB 0 B (0.0%) 302.7 KiB ✅
Codex long turn running, cold open rows 75 75 0 (0.0%) 75 ✅
Codex long turn running, history page HTTP decoded 92.4 KiB 92.4 KiB 0 B (0.0%) 107.4 KiB ✅
Codex long turn running, history page rows 75 75 0 (0.0%) 75 ✅
Codex long turn finished, cold open HTTP decoded 381.5 KiB 381.5 KiB 0 B (0.0%) 410.2 KiB ✅
Codex long turn finished, cold open rows 151 151 0 (0.0%) 151 ✅
Codex long turn finished, socket cold open WebSocket decoded 381.6 KiB 381.6 KiB 0 B (0.0%) 410.2 KiB ✅
Codex long turn finished, socket cold open WebSocket messages 2 2 0 (0.0%) 2 ✅
Codex long turn finished, socket cold open rows 151 151 0 (0.0%) 151 ✅
Codex long turn finished, work page HTTP decoded 186.2 KiB 186.2 KiB 0 B (0.0%) 200.2 KiB ✅
Codex long turn finished, work page rows 150 150 0 (0.0%) 150 ✅
Codex idle thread resume WebSocket decoded 65 B 65 B 0 B (0.0%) 200 B ✅
Codex idle thread resume WebSocket messages 1 1 0 (0.0%) 1 ✅
Codex fleet shell snapshot HTTP decoded 42.4 KiB 42.4 KiB 0 B (0.0%) 53.7 KiB ✅
Codex fleet shell snapshot rows 27 27 0 (0.0%) 27 ✅
Codex fleet shell subscribe WebSocket decoded 65 B 65 B 0 B (0.0%) 200 B ✅
Codex fleet shell subscribe WebSocket messages 1 1 0 (0.0%) 1 ✅
Codex fleet shell resume check HTTP decoded 18 B 18 B 0 B (0.0%) 100 B ✅
Codex fleet shell resume check rows 0 0 0 0 ✅
Codex stale shell reload HTTP decoded 42.4 KiB 42.4 KiB 0 B (0.0%) 53.7 KiB ✅
Codex stale shell reload rows 27 27 0 (0.0%) 27 ✅
Codex stale shell resume WebSocket decoded 140 B 140 B 0 B (0.0%) 400 B ✅
Codex stale shell resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Codex stale todo list cold open HTTP decoded 72.4 KiB 72.4 KiB 0 B (0.0%) 83.0 KiB ✅
Codex stale todo list cold open rows 75 75 0 (0.0%) 75 ✅
Codex stale todo list cold open nodes 2 2 0 (0.0%) 4 ✅
Codex stale todo list cold open subagents 2 2 0 (0.0%) 4 ✅
Codex stale todo list cold open plans 1 1 0 (0.0%) 2 ✅
Codex stale todo list cold open providerTurns 1 1 0 (0.0%) 2 ✅
Codex stale todo list cold open checkpoints 1 1 0 (0.0%) 2 ✅
Claude short turn resume WebSocket decoded 10.6 KiB 10.6 KiB 0 B (0.0%) 12.7 KiB ✅
Claude short turn resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Claude long turn running, cold open HTTP decoded 283.3 KiB 283.3 KiB 0 B (0.0%) 302.7 KiB ✅
Claude long turn running, cold open rows 75 75 0 (0.0%) 75 ✅
Claude long turn running, history page HTTP decoded 93.4 KiB 93.4 KiB 0 B (0.0%) 107.4 KiB ✅
Claude long turn running, history page rows 75 75 0 (0.0%) 75 ✅
Claude long turn finished, cold open HTTP decoded 384.2 KiB 384.2 KiB 0 B (0.0%) 410.2 KiB ✅
Claude long turn finished, cold open rows 151 151 0 (0.0%) 151 ✅
Claude long turn finished, socket cold open WebSocket decoded 384.3 KiB 384.3 KiB 0 B (0.0%) 410.2 KiB ✅
Claude long turn finished, socket cold open WebSocket messages 2 2 0 (0.0%) 2 ✅
Claude long turn finished, socket cold open rows 151 151 0 (0.0%) 151 ✅
Claude long turn finished, work page HTTP decoded 188.1 KiB 188.1 KiB 0 B (0.0%) 200.2 KiB ✅
Claude long turn finished, work page rows 150 150 0 (0.0%) 150 ✅
Claude idle thread resume WebSocket decoded 66 B 66 B 0 B (0.0%) 200 B ✅
Claude idle thread resume WebSocket messages 1 1 0 (0.0%) 1 ✅
Claude fleet shell snapshot HTTP decoded 43.0 KiB 43.0 KiB 0 B (0.0%) 53.7 KiB ✅
Claude fleet shell snapshot rows 27 27 0 (0.0%) 27 ✅
Claude fleet shell subscribe WebSocket decoded 66 B 66 B 0 B (0.0%) 200 B ✅
Claude fleet shell subscribe WebSocket messages 1 1 0 (0.0%) 1 ✅
Claude fleet shell resume check HTTP decoded 18 B 18 B 0 B (0.0%) 100 B ✅
Claude fleet shell resume check rows 0 0 0 0 ✅
Claude stale shell reload HTTP decoded 43.0 KiB 43.0 KiB 0 B (0.0%) 53.7 KiB ✅
Claude stale shell reload rows 27 27 0 (0.0%) 27 ✅
Claude stale shell resume WebSocket decoded 140 B 140 B 0 B (0.0%) 400 B ✅
Claude stale shell resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Claude stale todo list cold open HTTP decoded 73.4 KiB 73.4 KiB 0 B (0.0%) 83.0 KiB ✅
Claude stale todo list cold open rows 75 75 0 (0.0%) 75 ✅
Claude stale todo list cold open nodes 2 2 0 (0.0%) 4 ✅
Claude stale todo list cold open subagents 2 2 0 (0.0%) 4 ✅
Claude stale todo list cold open plans 1 1 0 (0.0%) 2 ✅
Claude stale todo list cold open providerTurns 1 1 0 (0.0%) 2 ✅
Claude stale todo list cold open checkpoints 1 1 0 (0.0%) 2 ✅

Baseline: 5cbdf00 · PR result: 6fb4635 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@coderabbitai

coderabbitai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The broker now retains the in-process server-browser connection when an eligible request times out. A concurrent-request test verifies that another request can succeed after an evaluate request times out.

Changes: Server-browser timeout retention

Layer / File(s) Summary
Preserve the server-browser connection on timeout
apps/server/src/mcp/PreviewAutomationBroker.ts, apps/server/src/mcp/PreviewAutomationBroker.test.ts
The timeout path skips disconnecting and completing the host stream when the connection is the in-process server browser. A test verifies that a concurrent waitFor request succeeds after evaluate times out.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: Minimal · up to 6fb46

A timed-out request on the shared server-browser connection no longer disconnects other threads' requests. Timeout behavior for other hosts is unchanged. No actionable merge-blocking risk is identified.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title states the main change: a timed-out preview call no longer disconnects (evicts) the shared server browser host. It follows the conventional commit form and matches the raw summary's change t…
Linked Issues check Passed Issue #16921's core requirement is that a slow operation on a healthy shared host must not disrupt unrelated requests. The reviewed diff addresses the mechanism in PreviewAutomationBroker.invoke. On a…
Out of Scope Changes check Passed The whole-PR diff against the merge base 9b6dde9 changes only the timeout branch in apps/server/src/mcp/PreviewAutomationBroker.ts (+5/-1 in the reviewed diff). It add…
Description check Passed The description covers the problem, the change, scope, and verification. It explains that a timeout eviction on the shared server browser failed unrelated in-flight calls, and that the fix skips evict…
Full details: Title check

Explanation

The title states the main change: a timed-out preview call no longer disconnects (evicts) the shared server browser host. It follows the conventional commit form and matches the raw summary's change to invoke's timeout path, which skips disconnect for the server browser.

Full details: Linked Issues check

Explanation

Issue #16921's core requirement is that a slow operation on a healthy shared host must not disrupt unrelated requests. The reviewed diff addresses the mechanism in PreviewAutomationBroker.invoke. On a request timeout, the broker called disconnect(..., true) for every connection. The head now skips that eviction when connection.clientId equals SERVER_BROWSER_AUTOMATION_CLIENT_ID. The timed-out request still returns PreviewAutomationTimeoutError. Eviction for other hosts is unchanged. The unit test 'keeps another thread's request alive when one request times out on the shared server browser' is described as failing on the base. The live two-thread reproduction was not rerun, and the PR evidence section is still pending. The issue also asks that mutations not be replayed automatically. The broker code keeps the updateCurrentTab !== false guard, so that behavior is unchanged. The recovery-state reporting clause is not separately addressed by this diff. The issue states that the snapshot stall cause is unknown and that the Cloudflare verification link is unproven. Those items are not treated as requirements here, per the linked issue objectives.

Full details: Out of Scope Changes check

Explanation

The whole-PR diff against the merge base 9b6dde9 changes only the timeout branch in apps/server/src/mcp/PreviewAutomationBroker.ts (+5/-1 in the reviewed diff). It adds a server-browser exclusion to the existing eviction condition and a comment. The raw summary also lists a supporting test in PreviewAutomationBroker.test.ts, which covers the same objective. No unrelated changes were identified. The PR's 'Not in this change' items are documented exclusions and are not code changes.

Full details: Description check

Explanation

The description covers the problem, the change, scope, and verification. It explains that a timeout eviction on the shared server browser failed unrelated in-flight calls, and that the fix skips eviction only for the server browser. It links the issue (#16921) and gives a reproduction on main, a unit test that fails on base, and test and check results. It also names the tools and models used, as the template asks. The template's Verification section asks for focused tests and observed results; the author supplies both. Remaining gaps are minor: the live two-thread repro was not rerun on the fixed commit in the PR text, the before/after evidence links are external and may not persist, and the Windows case is not exercised. These do not make the description largely incomplete. The Scope section links an issue that describes a bug, so it meets the bug-fix requirement.

Finishing Touches

Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 5cbdf00 and 6fb4635.

Files selected for processing: 2

  • apps/server/src/mcp/PreviewAutomationBroker.test.ts
  • apps/server/src/mcp/PreviewAutomationBroker.ts

Limit details: You’ve used all 10 included reviews currently available.

@juliusmarminge
juliusmarminge merged commit 713731f into main Nov 13, 2026
36 checks passed
@juliusmarminge
juliusmarminge deleted the factory/issue-16921-a34970 branch November 13, 2026 17:30
haroutB5 added a commit to haroutB5/personal-bots that referenced this pull request Nov 13, 2026
Upstream pingdotgg#36215 decodes recorder frames with Uint8Array.fromBase64. The
fork holds playwright-core at 1.60, whose Chromium lacks it, so every
server browser recording failed. The page now falls back to atob.

Upstream pingdotgg#36241 stops the broker evicting the shared server browser on a
timeout. The fork test that expected a re-register now expects the host
to stay on its first registration.

The reader/writer statement test returns to upstream's form: the fork's
writer worker posts its statements through worker_threads, where the
recorder sees them. The $0 cap expectation gains the fork's fullReadAt.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XS 0-9 changed lines (additions + deletions). vouch:trusted Author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Windows snapshot/evaluate timeouts evict the shared server-browser host and abort another open

1 participant