Skip to content

Preview tab control returns to agent while a human is using it #627

Open

Description

@lukemaj

What: A human taking control of an agent-opened preview tab keeps losing control back to the agent, though the agent sends no actions.
Why: Unknown root cause; control ownership on agent-opened tabs appears to be reclaimed automatically.
So what: A Chromeria worker reproduces and fixes it; a human must be able to keep control of a tab until they release it (logins, security checks).

Evidence (2026-10-26, bestia)

  • Agent opened https://www.messenger.com/ with preview_open (Default profile, reuseExistingTab=false) so the user could log in.
  • User reported the agent "constantly taking control" of the tab; they could not finish the login.
  • preview_status on that tab afterwards: control: {owner: "agent", ownedByCaller: true, generation: 8}. The opening agent made only one preview_status call and no actions, so 8 generations of control changes came from the human taking control and something handing it back.
  • Workaround used: closed the agent tab; the user opens a tab themselves.

Acceptance

  • After a human takes control of an agent-opened tab, control stays human until the human releases it or an agent explicitly requests it and the human grants it; passive reads (preview_status, snapshots, watches) never reclaim it.
  • Regression test covering take-control followed by idle and read-only agent calls.

Elon record

  • Requirements and who asked: Lukasz needs to log into Messenger in the shared browser without the agent grabbing the tab back.
  • Deleted: No new control modes; fix the reclaim only.
  • Bottleneck: Messenger outreach is blocked on this login.
  • Checked myself: preview_status shows generation 8 with owner agent after a single opener call and no agent actions.

Activity

  1. lukemaj commented on Oct 26, 2026

    @lukemaj
    ContributorAuthor

    What: Reproduced human control returning to the opening agent when its viewer disconnects; the fix now retains human control.
    Why: ServerBrowser.attachViewer finalization calls SessionControl.disconnect, which called release, setting its owner to null; status derives the opening agent from that null owner.
    So what: This task's worker is adding regression proof and preparing one PR for review; merge and installation remain blocked on Lukasz's approval.

    Workspace: /home/luke/.t3/worktrees/chromeria/t3-9aac4304, branch t3/preserve-preview-control, starting base 3ec7a5dd84753d18902a39ca3390f95879b25528 (origin/main). No writes or checkout operations in the live install.

    Reproduction on unchanged implementation plus regression tests:
    node_modules/.bin/vp test run apps/server/src/preview/ServerBrowser.test.ts -t 'an agent.*tab.*human'
    Both tests failed: immediately after closing the viewer scope, expected human, received agent; snapshot of an agent's own human-controlled tab failed with PreviewAutomationControlInterruptedError, reason humanControl.

    The test opens through the real automation broker, takes control through the viewer input handler, and closes the real viewer scope. Chromium/CDP are faked at the existing external boundary. This deterministically reproduces the owner transition, not the original Messenger renderer's reason for reconnecting. No browser, private account, login, live install or customer data was used.

    Source trace and negative results:

    • preview_status reads statusWithTitle; it does not change ownership.
    • Snapshot/wait reads do not reclaim ownership, but an unnecessary opener identity condition routes the opener's reads through the agent action guard. Removing that condition permits passive reads during human control.
    • preview_watch_read reads retained PageWatchStore data, outside the live browser controller. watchStop stops retained watches; watchStart requires current agent control and refuses during human control. None grants agent ownership.
    • Human navigation and page focus do not call release. Viewer stream closure does; the web surface recreates that stream when its effect dependencies change (including viewport cap, resource, visibility, access), and stream transport reconnects also run viewer finalization. The precise trigger on the reported Messenger tab remains unobserved.
    • Renderer/browser-host restart can destroy the in-memory tab entirely; that is distinct from returning an existing tab's control and is outside this change.

    Upstream first: searched pingdotgg/t3code Issues and PRs with preview control, "taking control", and "SessionControl"; checked upstream main's SessionControl.disconnect and its recent history. Upstream main still calls release on disconnect. Read related #19420 (mobile input/connectivity), #20283 (frame backlog), and PR #17081 (optional automatic browser control, which explicitly retains disconnect release). No matching upstream fix found in that boundary. An upstream fetch raced another fetch while updating the shared remote ref; inspected the successfully updated ref 210b2e7c6a8be11441b7afc94742ce3c76860111, without modifying another checkout.

    The smallest fix retains a disconnected human reservation in SessionControl, invalidates/drains old connection input, and lets a human explicitly take control again and release it. Existing stream protocol reports no connected controller so web and mobile keep Take control available; automation status remains human and agent actions remain refused. No new control mode, timer, preference, or permission is added. This preserves shared control for Promachos and Drafters under the current Objective.

    Requirements and who asked: Lukasz requested sticky human control, passive read safety, failing-before proof, a PR and independent exact-head review for #627.
    Deleted: Automatic release on viewer disconnect; no new mode, lease timeout, retry loop, or browser automation setup.
    Bottleneck: The server control owner conflates a disconnected human with permission for the opening agent to resume.
    Checked myself: Ran both failing-before tests and traced broker status, snapshots, viewer finalization, navigation and watch paths; initial fixed control/browser tests passed 68/68.

  2. lukemaj commented on Oct 26, 2026

    @lukemaj
    ContributorAuthor

    What: PR #628 contains the reproduced fix, passing focused proof and independent review at 7c6abcdf82a80c0468c4329707cb893dfbcb4b3a.
    Why: Disconnect used to release human ownership; snapshots also incorrectly required agent control on the opener's own tab.
    So what: Independent review passed with no blocking findings; the parent can assess PR #628, while merge and installation remain for Lukasz's approval.

    Canonical handoff:

    • Outcome/acceptance: Preview tab control returns to agent while a human is using it #627, one PR fix(preview): keep human control across viewer disconnects #628.
    • Exclusive workspace /home/luke/.t3/worktrees/chromeria/t3-9aac4304; branch t3/preserve-preview-control; base 3ec7a5dd84753d18902a39ca3390f95879b25528; pushed head 7c6abcdf82a80c0468c4329707cb893dfbcb4b3a. Worktree clean, no live-install writes.
    • Proof: 81/81 focused tests across SessionControl.test.ts, ServerBrowser.test.ts, ServerBrowserStream.test.ts; server package typecheck passed; scoped lint passed (one pre-existing regex warning), formatting and diff checks passed, final committed fork-stack check passed. Exact commands and baseline red tests are in the PR body.
    • Review: delegated through Prism reviewer role, request chromeria-627-review-7c6abcdf82-r1, Codex / gpt-6.1-sol; review/independent=success verified on exact head, posted by lukemaj. Findings: fix(preview): keep human control across viewer disconnects #628 (comment). Verdict: PASS, no blocking findings; source review only, no independent test rerun. No second writer or browser process was started.
    • Version: unchanged per repo policy. Delivery: implemented, committed, pushed and PR opened; independently reviewed; not merged, released, installed or live-verified.
    • Limits: real broker/viewer/session lifecycle with existing fake Chromium boundary; original Messenger renderer reconnect trigger unknown. Negative searches and source trace are in Preview tab control returns to agent while a human is using it #627 (comment).
    • Next action: parent assesses PR fix(preview): keep human control across viewer disconnects #628 and obtains Lukasz's approval before any merge or installation. Implementation and independent review are complete. Keep branch/worktree until human disposition.
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