Skip to content

[Bug]: Resumed Bob thread cannot navigate or close its own preview tab after the 30-minute idle stop #23021

Open

Description

@sankalpmukim

Summary

After Bob's normal 30-minute idle stop, resuming the same T3 thread can leave its existing desktop browser tab owned by the previous MCP credential. The resumed thread can no longer navigate or close that tab. It gets PreviewAutomationControlInterruptedError, reason agentMismatch, as if another agent owned the page.

This was observed on the official 0.1.2-nightly.20261021.3457 build, commit e64766c56ac6fc1678e6107052feb0047a35337f, macOS arm64, Bob 2.0.8 via acpRegistry_bob.

This is separate from the background screenshot/capture-lock failure reported in #23022. The ownership rejection returns in about a millisecond and happens before a browser action. No stalled screenshot is needed to explain it.

Before submitting and scope

  • Searched existing reports and read the related issue and maintainer replies.
  • Included the real provider lifecycle sequence, browser errors, exact source path, and a small control-layer test.
  • This follows the closing comment on #17180, which states that a later session's inability to close an earlier session's tab remained open and asks for a new issue for that part.
  • This issue is only about ownership across the same thread's idle stop/resume. It is not about the earlier report's wake lock, idle-tab sweep, generic-error masking, human-opened tab policy, or cross-thread access.
  • Current error reporting already exposes agentMismatch. The remaining problem is that the same authorized thread loses its old tab after credential rotation.

Area

apps/server: provider session lifetime, MCP identity, and preview ownership.

Expected behavior

A resumed authorized provider in the same thread should have a supported way to continue using or close that thread's existing browser tab. If ownership must be transferred, that transfer should be explicit and safe.

Cross-thread and human-control protections must remain in force. This is not a request to ignore agentMismatch or let one thread operate another thread's tabs.

The current t3_preview_close description says it closes a tab “owned by this thread”, not only one owned by the current short-lived MCP credential.

Actual behavior and impact

  • A tab was opened and used successfully by an agent in one thread.
  • T3 closed the Bob runtime after 30 minutes of inactivity.
  • The user sent a new prompt to that same thread. Bob loaded the same native conversation.
  • Navigation on the existing tab was rejected with agentMismatch.
  • A later request to close it was also rejected with agentMismatch.
  • A new tab worked, but the old one remained outside the resumed agent's control.

Impact: major degradation for resumed browser work. It forces a new tab, can lose page/form state, and prevents the agent from completing a user request to close its thread's tabs. This report does not claim that the browser cookies were lost or that the old renderer crashed.

Environment

Component Verified value
T3 Code Official Nightly 0.1.2-nightly.20261021.3457
Installed source e64766c56ac6fc1678e6107052feb0047a35337f
OS macOS 15.8.1, build 24H32, arm64
Provider Bob 2.0.8, ACP registry instance acpRegistry_bob
Browser runtime Desktop-backed preview in the same local environment
App runtime Electron 44.4.5, Chromium 152.0.7977.130, Node 24.21.0
Bundled Playwright 1.60.0

No app patch, database write, restart, or Slack account change was performed during diagnosis. No new 30-minute end-to-end test was run; the lifecycle sequence below is from retained provider and tool records. The ownership comparison was separately checked with the exact installed SessionControl code.

Reproduction steps

Use a disposable local page; no Slack account is required for the ownership check.

  1. Start a Bob thread in the desktop app and open an agent-owned preview tab. Retain the explicit tabId.
  2. Verify that navigation works and the tab is owned by the caller. Finish the agent turn and leave the tab open.
  3. Leave the provider idle until T3 performs the default 30-minute session stop. Do not manually hand the tab to another agent or a human.
  4. Send a new prompt in the same T3 thread and let Bob load its previous conversation.
  5. Ask the resumed agent to navigate the old explicit tabId to the local page.
  6. Ask it to close that same tabId with t3_preview_close.

Observed result: both calls are rejected as another agent's tab. A newly opened tab in the resumed thread is accepted.

These steps describe the observed lifecycle. The exact cause of every provider restart is not inferred from the error alone; in this incident the provider log records the 30-minute stop and subsequent session/load.

Sanitized original timeline

All times are UTC on 21 October 2026, on the same T3 process and environment. The same thread is called Thread B and its old tab is called tab_2; UUID suffixes, page URLs, user text, and credentials are omitted.

Time Recorded event
15:03:47.730 Bob session/new succeeds
15:04:03.794 tab_2 open returns owner: agent, ownedByCaller: true
15:04–15:37 Agent successfully navigates and uses that tab
15:37:07.241 Last earlier session/prompt succeeds
16:07:07.263 session/close starts, 30 minutes after the last prompt completed
16:07:08.109 session/close succeeds
17:31:49.958 New Bob runtime sends session/load for the same native conversation
17:31:50.705 session/load succeeds
17:31:59.150 Broker navigation request on old tab_2 starts
17:31:59.151 Broker rejects it in 0.897 ms with agentMismatch
17:38:38.881 Broker close request on old tab_2 is rejected in 0.324 ms with agentMismatch

The T3 orchestration provider-session identity was reused on resume. The MCP credential's providerSessionId is a separate value. Confusing those two identities hides this failure: a stable orchestration session does not make the tab's MCP-derived owner key stable.

Representative user-facing navigation error:

This browser tab belongs to another agent session or a human. Open your own tab.

The close result contains this more specific information, with identifiers removed:

{
  "_tag": "PreviewAutomationControlInterruptedError",
  "operation": "close",
  "clientId": "server-browser",
  "timeoutMs": 15000,
  "remoteTag": "PreviewAutomationControlInterruptedError",
  "reason": "agentMismatch",
  "cause": {
    "_tag": "PreviewAutomationControlInterruptedError",
    "message": "This tab belongs to another agent session. You can read it, but open your own tab to act.",
    "detail": "agentMismatch"
  }
}

This is a returned ownership error, not a 15-second timeout. timeoutMs is request context in the structured error.

Source analysis at the installed commit

1. The provider's idle stop clears its MCP credential

  • ProviderSessionManager.ts:56 sets DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000.
  • 1219-1223 schedules releaseIfStillIdle.
  • 1066-1090 clears the stopped runtime's recorded MCP credential when it is not held by a live replacement.
  • 499-511 revokes that credential and clears McpProviderSessions for the thread.

2. Resume issues a new MCP credential session ID

prepareMcpSession:435-497 can reuse a still-valid credential, but there is no credential to reuse after the normal idle cleanup above.

McpSessionRegistry.issue:98-111 creates a new random providerSessionId. This value is also used in the credential's invocation scope.

3. That credential ID is part of the browser owner key

PreviewAutomationBroker.ts:170-175 builds the following key (shown here in equivalent form, with the zero-character separator explicit):

const hostAssignmentKey = (scope) => [
  scope.environmentId,
  scope.thread.providerSessionId,
  scope.thread.threadId,
].join(String.fromCharCode(0));

The thread ID is stable, but the middle field changes with the credential. It is sent to the browser host as agentSessionId at 623-634.

ServerBrowser.ts:1789-1799 records that key as automationOwner when opening a tab. The tab's SessionControl retains it.

4. The old tab correctly rejects the new key, but no same-thread transfer occurs

The desktop-backed tab can survive the provider stop. The idle-tab sweep at ServerBrowser.ts:1278-1287 excludes tabs where tab.desktop !== null, so that sweep does not automatically remove this old desktop-backed owner.

Small exact-code check

The installed SessionControl implementation was extracted without changing it. This test holds environment and thread constant while changing only the MCP credential ID used in the production owner key:

const original = 'test-environment\0old-mcp-credential-id\0same-thread';
const resumed  = 'test-environment\0new-mcp-credential-id\0same-thread';
const control = new SessionControl(original);

await control.agent(original, async () => 'allowed');
// => 'allowed'

await control.agent(resumed, async () => 'allowed');
// => rejects: reason='agentMismatch', message='This tab belongs to another agent.'

This confirms the owner-key comparison, not a fresh full provider idle/resume test. The real idle/resume occurrence is supported separately by the lifecycle and tool records above.

Suggested acceptance checks

  1. Open a tab, finish a turn, let the provider reach its idle stop, resume the same thread, and verify navigation and close on the old tab.
  2. Use a shortened idle budget only in a test fixture; do not require a 30-minute CI wait.
  3. Keep the authorization boundary stable across credential rotation, or provide an explicit transfer from an old owner to the resumed authorized owner of the same thread/provider instance.
  4. Do not fix this by ignoring ownership. Verify that another thread, another provider instance without authority, and a caller while human control is active still cannot act.
  5. Verify that a stale credential cannot regain access after transfer/revocation, and that stale cleanup cannot revoke the replacement's credential or tabs.
  6. Cover a provider process restart and idle stop separately from a whole server restart. This report does not ask for persistent browser tabs across server shutdown.
  7. Align t3_preview_close's thread-owned contract with the actual allowed lifecycle.

Workaround

Open a fresh tab in the resumed thread. Close the old tab through the human UI if needed. The agent cannot repair the old owner key by retrying navigation or close. Increasing a tool timeout cannot change an immediate owner mismatch.

Privacy and limits

Raw transcripts, full logs, database backups, cookies, credential values, private Slack content, and local personal paths are not attached. The public evidence contains only generic tab labels, sanitized lifecycle events, source links, and the synthetic key comparison.

This occurrence was in Bob/ACP. The owner-key design is shared, but no claim is made that every other provider rotates credentials in the same way. The screenshot and shared-host timeout faults are separate; they must not be used as evidence that this authorization rejection is a renderer failure.

Investigated and drafted with OpenAI GPT-6.1 Sol through OpenCode in T3 Code, at the user's request. No application fix is claimed.

Activity

  1. added
    needs more infoInitial triage showed no bug. Awaiting more info
    and removed
    needs more infoInitial triage showed no bug. Awaiting more info
    on Oct 22, 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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions