Skip to content

[Bug]: A slow typed navigation blocks all input in an environment-hosted browser tab for 15 seconds #16641

Description

@josephv123

Before submitting

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

Area

apps/server

Summary

In an environment-hosted browser tab, when an address typed in the address bar is slow to respond, everything else you do in that tab waits for it. Clicks, scrolling, keystrokes, Back, Reload, and a corrected address all queue for up to 15 seconds before anything happens. A normal browser cancels the slow load as soon as you do something else.

Steps to reproduce

  1. In a thread on an environment without an attached desktop app, open the Browser panel. The tab reports runtime: "server".
  2. Type an address whose host never answers (for example https://10.255.255.1/) and press Enter.
  3. Right away, type https://example.com/ and press Enter, or press Back, or click and scroll the page.

Expected behavior

The new address loads immediately, and the slow load is abandoned, as in Chrome.

Actual behavior

Nothing happens for about 15 seconds. Then the queued input runs all at once. If you do nothing, the tab shows Loading until Chromium's own connect timeout, about 2 minutes on Linux (133 s measured), and only then shows ERR_CONNECTION_TIMED_OUT.

Root cause

Every viewer input runs inside SessionControl.human (call site). SessionControl serializes actions: each one starts only after the previous one settles.

The viewer's navigate action awaits page.goto(url, VIEWER_NAVIGATION_OPTIONS) inside that queue. VIEWER_NAVIGATION_OPTIONS is { waitUntil: "commit", timeout: 15_000 }. history and reload work the same way. So a navigation that never commits holds the queue for the full 15 seconds. When it finally times out, the TimeoutError is swallowed by the viewer input handler's catch. Chromium keeps the request alive, so the tab stays Loading.

I confirmed the queuing directly against SessionControl. While a human action awaits a 3 s promise, standing in for the hung goto, the next human action, standing in for a click, ran after 3004 ms.

Impact

Minor bug or occasional failure

Version or commit

T3 Code 0.0.46-nightly.20261006.2735. Code references are at 72d5c32ba6.

Environment

t3 serve on Ubuntu 26.04, using chrome-headless-shell 154.0.8037.92. The client is the T3 Code (Nightly) desktop app on macOS 26.

Activity

  1. juliusmarminge commented on Oct 7, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Confirmed on current main (517188b).

    Root cause. All human input for a tab goes through one serialized queue, and viewer navigations wait for the commit inside that queue:

    • apps/server/src/preview/SessionControl.ts:37-44 (enqueue) chains every action on tail, so each one starts only after the previous one settles (plus any pending work registered through track, :47-53). human() (:100-102) → action() (:65-74) → enqueue.
    • apps/server/src/preview/ServerBrowser.ts:2112-2114 runs every viewer message inside tab.control.human(viewer.id, () => dispatchViewerInput(...)). The surrounding catch (:2116-2119) swallows the failure.
    • In dispatchViewerInput (ServerBrowser.ts:1790), these all await VIEWER_NAVIGATION_OPTIONS = { waitUntil: "commit", timeout: 15_000 } (:119-120):
      • navigate awaits tab.page.goto(url, VIEWER_NAVIGATION_OPTIONS) (:1884-1889)
      • history awaits goBack/goForward (:1890-1894)
      • a soft reload awaits page.reload (:1895-1902)

    A typed URL whose host never answers holds the queue for the full 15s. Clicks, scrolls, keys, Back and a corrected URL wait behind it. When goto times out, the TimeoutError is dropped and Chromium keeps the request alive, so the tab keeps showing Loading.

    Suggested fix direction. A new human navigation should replace a slow one instead of waiting behind it:

    • Don't await the commit inside the human queue. Start the navigation and return, as the agent path does with readiness: "none" (ServerBrowser.ts:1185). Note that track() would still make later actions wait, so the viewer path should not register the navigation as pending. Alternatively, race it against the next viewer input.
    • When a new viewer navigate/history/reload arrives, call Page.stopLoading (or start a new goto, which cancels the old one in Chromium) first, so the old load is abandoned as it would be in Chrome.
    • Optionally surface a navigation timeout or net error to the viewer instead of swallowing it.

    Related. #16567 is the same SessionControl queue with no per-action deadline, triggered there by an agent preview_snapshot that never settles. A per-action deadline in SessionControl would help both issues, but this one also needs the human-navigation change above, because a 15s hold is "within deadline". I didn't find an open PR that touches SessionControl or the viewer navigation path.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 7, 2026
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

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions