# Switching a thread's provider instance during a running turn kills the turn with "provider event stream closed unexpectedly" · Issue \#22748 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/issues/22748)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: backlog

Priority: 0

## Description

[Discussion and activity](https://git.cafe/pingdotgg/t3code/issues/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):

1. `t3_thread_configure` (`apps/server/src/mcp/toolkits/thread/handlers.ts:182`) dispatches `provider.switch` when the instance changes (`modelSelectionCommandType`).
2. `ProviderSwitchService` (`apps/server/src/orchestration-v2/ProviderSwitchService.ts:180`) puts every live session from another instance in `releaseProviderSessionIds` for a `create_with_handoff` transition. It does not check whether a run is active.
3. The decider (`apps/server/src/orchestration-v2/Orchestrator.ts:3640-3712`) emits `provider-session.detached` and queues a `provider-session.detach` effect with detail `"Provider or model selection changed."`. There is no active-run guard, unlike `thread.settle`, which refuses with "is still running. Stop it before settling." (`Orchestrator.ts:2835-2850`).
4. `ProviderSessionManager.detach` closes the session (`query.close` goes 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 as `class: "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.set` on 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

1. Configure two provider instances of the same driver (e.g. `claudeAgent` and a second `claudeAgent` instance with another `homePath`).
2. Start a long turn on a thread using instance A (e.g. ask it to run several tool calls).
3. While it runs, switch the thread to instance B, either from another agent via the T3 MCP tool `t3_thread_configure` with `modelSelection.instanceId = B`, or by having the running agent call it on its own thread.
4. 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

```shell
# effect outbox: every "stream closed" failure has a matching detach from an MCP provider.switch
provider-session.detach | Provider or model selection changed. | mcp:1bffb82f-… | 2026-10-21T07:40:19.508Z
provider-session.detach | Provider or model selection changed. | mcp:f167cd1a-… | 2026-10-21T07:42:12.129Z
provider-session.detach | Provider or model selection changed. | mcp:85ebf963-… | 2026-10-21T07:42:12.922Z
provider-session.detach | Provider or model selection changed. | mcp:5db90f4e-… | 2026-10-21T07:42:14.059Z
provider-session.detach | Provider or model selection changed. | mcp:3bbdd80f-… | 2026-10-21T07:42:16.336Z
provider-session.detach | Provider or model selection changed. | mcp:378614ad-… | 2026-10-21T07:42:17.531Z
provider-session.detach | Provider or model selection changed. | mcp:c044a900-… | 2026-10-21T07:44:04.767Z   # orchestrator switching its own thread
provider-session.detach | Provider or model selection changed. | mcp:541eab28-… | 2026-10-21T07:51:57.508Z

# server trace, same instant
07:42:12 orchestrationV2.dispatch.once {"command_type":"provider.switch","thread_id":"mcp:670e053c-…"}
07:42:12 ProviderSessionManager.detach -> ProviderSessionManager.closeScopeWithin

# provider event log for that thread: model mid-turn, then T3 closes it
[07:42:08.391Z] incoming system/status "requesting"
[07:42:12.144Z] outgoing {"type":"query.close"}

# resulting turn item
{"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`

