What happened
Version: 0.0.46-nightly.20261005.2702 (Linux, systemd user service).
-
Thread runs on Claude (claude_proxy). Its Claude provider session stays alive.
-
The user switches the thread to Codex (codex_proxy, shared session). Codex attaches to the thread. prepareMcpSession(thread, codex_proxy) revokes the thread's credential and issues a Codex-scoped one into the per-thread slot.
-
The user switches back to Claude. The old Claude provider session is still in sessions and still attached to the thread, so ensureThreadAttached does not call prepareMcpSession. The Claude process starts (--resume) with the token from the slot. That token is still scoped to codex_proxy.
-
Every orchestration call that checks the live caller fails:
delegate_task: parent_not_active: Delegated tasks require an active run owned by this MCP provider session.
t3_queue_cancel: parent_not_active: The calling provider no longer owns an active thread run.
The active run is correct (providerInstanceId: claude_proxy, rootNodeId set). The mismatch is scope.thread.providerInstanceId (codex_proxy).
Evidence: orchestration_v2_projection_provider_session_bindings lists both provider-instance:claude_proxy:thread:<id>:… and provider-instance:codex_proxy:shared for the thread. The trace shows no McpSessionRegistry.issue when the thread went back to Claude.
Expected
When a run starts on a provider, the thread's MCP credential is scoped to that provider. If the slot holds a credential for another provider, it is rotated, even when the provider session is reused.
Workaround
Restart the T3 service. The next run opens a fresh Claude session, which issues a correct token.
Possible fix
On each run start (or provider process spawn), call prepareMcpSession(threadId, run.providerInstanceId), not only when the thread is newly attached. Or keep one slot per thread and provider, not one per thread.
Related: #15173 (also about session reuse and delegated runs).
What happened
Version: 0.0.46-nightly.20261005.2702 (Linux, systemd user service).
Thread runs on Claude (
claude_proxy). Its Claude provider session stays alive.The user switches the thread to Codex (
codex_proxy, shared session). Codex attaches to the thread.prepareMcpSession(thread, codex_proxy)revokes the thread's credential and issues a Codex-scoped one into the per-thread slot.The user switches back to Claude. The old Claude provider session is still in
sessionsand still attached to the thread, soensureThreadAttacheddoes not callprepareMcpSession. The Claude process starts (--resume) with the token from the slot. That token is still scoped tocodex_proxy.Every orchestration call that checks the live caller fails:
delegate_task:parent_not_active: Delegated tasks require an active run owned by this MCP provider session.t3_queue_cancel:parent_not_active: The calling provider no longer owns an active thread run.The active run is correct (
providerInstanceId: claude_proxy,rootNodeIdset). The mismatch isscope.thread.providerInstanceId(codex_proxy).Evidence:
orchestration_v2_projection_provider_session_bindingslists bothprovider-instance:claude_proxy:thread:<id>:…andprovider-instance:codex_proxy:sharedfor the thread. The trace shows noMcpSessionRegistry.issuewhen the thread went back to Claude.Expected
When a run starts on a provider, the thread's MCP credential is scoped to that provider. If the slot holds a credential for another provider, it is rotated, even when the provider session is reused.
Workaround
Restart the T3 service. The next run opens a fresh Claude session, which issues a correct token.
Possible fix
On each run start (or provider process spawn), call
prepareMcpSession(threadId, run.providerInstanceId), not only when the thread is newly attached. Or keep one slot per thread and provider, not one per thread.Related: #15173 (also about session reuse and delegated runs).