Reproduced on current main at 8acbefefc5: Linux web dev server, Claude Opus 5.5 running a foreground sleep 90, then provider.switch to a second Claude instance. The turn immediately failed with the reported unknown stream error. Runtime transcript. Existing draft fix #22754 is being rechecked under the revised triage brief.
Switching a thread's provider instance during a running turn kills the turn with "provider event stream closed unexpectedly"#22748
What happened
I run two accounts per harness as separate provider instances (personal claudeAgent / codex, work claude-work / a managed Codex instance). Turns randomly fail with "The provider event stream closed unexpectedly", on whichever provider happened to be running. It looked like a provider or account problem.
Diagnosis
The failures are caused by T3 itself: switching a thread's provider instance while the thread has a run in progress tears down the live session mid-turn. The turn then ends as a generic unknown failure instead of being refused, deferred, or reported clearly.
In my case an orchestrator agent was moving a set of worker threads from claude-work back to claudeAgent with t3_thread_configure (one call per thread). Several of those workers were mid-turn, and the orchestrator also switched its own thread. Every running thread it touched failed within ~150 ms.
Code path at v0.1.2-nightly.20261021.3457 (unchanged on main as of today):
t3_thread_configure(apps/server/src/mcp/toolkits/thread/handlers.ts:182) dispatchesprovider.switchwhen the instance changes (modelSelectionCommandType).ProviderSwitchService(apps/server/src/orchestration-v2/ProviderSwitchService.ts:180) puts every live session from another instance inreleaseProviderSessionIdsfor acreate_with_handofftransition. It does not check whether a run is active.- The decider (
apps/server/src/orchestration-v2/Orchestrator.ts:3640-3712) emitsprovider-session.detachedand queues aprovider-session.detacheffect with detail"Provider or model selection changed.". There is no active-run guard, unlikethread.settle, which refuses with "is still running. Stop it before settling." (Orchestrator.ts:2835-2850). ProviderSessionManager.detachcloses the session (query.closegoes to the Claude SDK while the model is still producing tool calls). The run's subscribers get a stream error, and the turn is recorded asclass: "unknown"/ "The provider event stream closed unexpectedly. Retry the turn; if it keeps failing, check the provider and server logs."
The detail string that explains the failure exists in the outbox, but the user never sees it.
Expected, any of:
- refuse
provider.switch/thread.model-selection.seton a thread with an active run (like settle), or - defer the switch until the current run ends, or
- at minimum, interrupt the run cleanly and report "Provider was switched while this turn was running" rather than a generic stream failure.
Steps to reproduce
- Configure two provider instances of the same driver (e.g.
claudeAgentand a secondclaudeAgentinstance with anotherhomePath). - Start a long turn on a thread using instance A (e.g. ask it to run several tool calls).
- While it runs, switch the thread to instance B, either from another agent via the T3 MCP tool
t3_thread_configurewithmodelSelection.instanceId = B, or by having the running agent call it on its own thread. - The running turn fails immediately with "The provider event stream closed unexpectedly".
Seen 8 out of 8 times in my logs: every switch issued against a running thread failed its turn. A switch in the same batch against an idle thread (07:42:15) caused no error.
Version
0.1.2-nightly.20261021.3457 (desktop app, nightly)
Environment
macOS 26 (Darwin 25.6.0) arm64, Node 26.8.2, Claude Code 2.1.306, codex-cli 0.163.0
Evidence
| 1 | # effect outbox: every "stream closed" failure has a matching detach from an MCP provider.switch |
| 2 | provider-session.detach | Provider or model selection changed. | mcp:1bffb82f-… | 2026-10-21T07:40:19.508Z |
| 3 | provider-session.detach | Provider or model selection changed. | mcp:f167cd1a-… | 2026-10-21T07:42:12.129Z |
| 4 | provider-session.detach | Provider or model selection changed. | mcp:85ebf963-… | 2026-10-21T07:42:12.922Z |
| 5 | provider-session.detach | Provider or model selection changed. | mcp:5db90f4e-… | 2026-10-21T07:42:14.059Z |
| 6 | provider-session.detach | Provider or model selection changed. | mcp:3bbdd80f-… | 2026-10-21T07:42:16.336Z |
| 7 | provider-session.detach | Provider or model selection changed. | mcp:378614ad-… | 2026-10-21T07:42:17.531Z |
| 8 | provider-session.detach | Provider or model selection changed. | mcp:c044a900-… | 2026-10-21T07:44:04.767Z # orchestrator switching its own thread |
| 9 | provider-session.detach | Provider or model selection changed. | mcp:541eab28-… | 2026-10-21T07:51:57.508Z |
| 10 | |
| 11 | # server trace, same instant |
| 12 | 07:42:12 orchestrationV2.dispatch.once {"command_type":"provider.switch","thread_id":"mcp:670e053c-…"} |
| 13 | 07:42:12 ProviderSessionManager.detach -> ProviderSessionManager.closeScopeWithin |
| 14 | |
| 15 | # provider event log for that thread: model mid-turn, then T3 closes it |
| 16 | [07:42:08.391Z] incoming system/status "requesting" |
| 17 | [07:42:12.144Z] outgoing {"type":"query.close"} |
| 18 | |
| 19 | # resulting turn item |
| 20 | {"title":"…","failure":{"class":"unknown","message":"The provider event stream closed unexpectedly. Retry the turn; if it keeps failing, check the provider and server logs."}} |
Related issues
#15135 is the same failure shape for thread.archive (self-archive detaches the live session without an active-run guard). This one is provider.switch / thread.model-selection.set, a different command and code branch, so a fix for archive alone won't cover it. #21369 covers the poor reporting after this error in general.
Fix applied or workaround
None applied on the machine. Workaround: only switch a thread's provider instance when it has no active run, and never let an agent switch its own thread mid-turn.
Filed by
Claude Code (claude-opus-5-5) via t3 triage
Fix #22754 now refuses disruptive provider switches while a turn is active. Same-driver Claude runtime verification preserved the running turn; idle switches and next-turn selections remain available. On merged head 896c0e10d3, 109 focused tests, server typecheck and targeted lint passed. Both independent reviews found no blocking defects. Evidence and limits.
Confirmed again on 0.1.8-nightly.20261104.4179, Linux x64 / CachyOS, desktop app with its local server. This occurrence is a cross-provider self-switch from Claude (claudeAgent, claude-opus-5-5) to Codex (codex, gpt-6.1-sol), rather than switching accounts within the same provider.
The agent called this during its active turn:
| 1 | {"modelSelection":{"instanceId":"codex","model":"gpt-6.1-sol"}} |
The switch succeeded, but T3 terminated the current turn before it could finish its response. Correlated persisted events, effect-outbox records, and native provider logs show:
| 1 | 2026-11-06, UTC |
| 2 | 23:24:42.310 thread.provider-switched -> codex |
| 3 | 23:24:42.310 provider-session.detached |
| 4 | reason: "Provider or model selection changed." |
| 5 | 23:24:42.321 t3_thread_configure returns success |
| 6 | 23:24:42.331 outgoing Claude SDK {"type":"query.close"} |
| 7 | 23:24:42.332 active run and attempt marked failed |
| 8 | "The provider event stream closed unexpectedly. Retry the turn; |
| 9 | if it keeps failing, check the provider and server logs." |
The matching provider-session.detach outbox effect succeeded, with detail Provider or model selection changed. The command receipt is an accepted provider.switch. The last Claude rate-limit status was allowed_warning, not a blocking rate-limit result. This was intentional session teardown being presented as a transport failure.
I inspected the installed server bundle and source at release commit 9e85538535c8553901351112ab02dd1dd62db5ee, tag v0.1.8-nightly.20261104.4179:
ProviderSwitchServiceselects old live sessions for release on acreate_with_handofftransition.- The thread mutation dispatch schedules
provider-session.detachwhile the source turn is still active. - For the last attached thread of a non-shared session,
ProviderSessionManager.detachcallsreleaseEntrywithmanual_shutdown. releaseEntrycallsfailSubscribers, which constructsProviderAdapterEventStreamError, then closes the adapter scope. This matches the observedquery.closeand failed turn.
For agent self-switches, saving the selection for the next turn and deferring source-session teardown until the current turn finishes would preserve the final response. A clear refusal of disruptive active-turn switches would also avoid the failure. Suppressing the error alone would leave the current turn without a terminal result.
The earlier comments describe a fix verified for same-driver switches. I have not established whether that change shipped in this nightly or whether this is a missed cross-driver path; this evidence confirms the behavior is still present in the installed build above.
No runtime patch, database write, app restart, or second live reproduction was performed. Evidence checks were read-only; no application tests were run. Workaround remains switching providers only after the current turn ends.
Investigated with Codex (gpt-6.1-sol) using the t3 triage playbook. Triage CLI and affected server both reported 0.1.8-nightly.20261104.4179.