Repository navigation
[Bug]: Resumed Bob thread cannot navigate or close its own preview tab after the 30-minute idle stop #23021
Description
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.
- Start a Bob thread in the desktop app and open an agent-owned preview tab. Retain the explicit
tabId. - Verify that navigation works and the tab is owned by the caller. Finish the agent turn and leave the tab open.
- 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.
- Send a new prompt in the same T3 thread and let Bob load its previous conversation.
- Ask the resumed agent to navigate the old explicit
tabIdto the local page. - Ask it to close that same
tabIdwitht3_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
McpProviderSessionsfor 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
- SessionControl.ts:93-106 compares
agentIdto the caller's new key and returnsagentMismatch. - ServerBrowser.requireTab:1674-1703 rejects actions by that caller before the page operation starts.
- ServerBrowser close:1873-1886 requires the old agent-session key too.
- previewControls/tools.ts:40-44 describes close as a thread-owned operation.
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
- 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.
- Use a shortened idle budget only in a test fixture; do not require a 30-minute CI wait.
- 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.
- 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.
- Verify that a stale credential cannot regain access after transfer/revocation, and that stale cleanup cannot revoke the replacement's credential or tabs.
- 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.
- 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