Skip to content

fix(server): switching the Claude model no longer blocks a later provider switch - #11

Merged
iXab3r merged 1 commit into
mainfrom
bugfix/claude-set-model-echo-wake-buffer
Nov 11, 2026
Merged

iXab3r merged 1 commit into
mainfrom
bugfix/claude-set-model-echo-wake-buffer

Conversation

@iXab3r

@iXab3r iXab3r commented Nov 11, 2026 •

Copy link
Copy Markdown
Owner

Problem

After changing the Claude model in a thread, every send to another provider is refused:

The previous provider is still running background agents or commands, and switching would end them.

Nothing is running and there is no Stop button. Only Force stop clears it.

T3 changes the model on the running CLI. The CLI answers with an echo, <local-command-stdout>Set model to …</local-command-stdout>, before the next turn starts. T3 kept that echo as the start of a background wake, and nothing cleared it.

flowchart TD
    A["Send with a new Claude model"] --> B["T3 calls setModel on the running CLI"]
    B --> C["CLI echoes: Set model to …"]
    C --> D["Before: kept as a background wake<br/>Now: dropped"]
    D --> E["Turn runs and ends"]
    E --> F["Send to another provider<br/>Before: refused<br/>Now: hand-off starts"]
    classDef changed fill:#fef3c7,stroke:#d97706,color:#78350f
    class D,F changed
Loading
Before Now
Echo of setModel, no turn active ❌ Kept as a background wake ✅ Dropped
Thread after the turn ends ❌ Reports background work until Force stop ✅ Idle
Send to another provider ❌ Refused ✅ Hand-off starts

Existing upstream fixes

The symptom is upstream issue pingdotgg/t3code#32296. This cause is not reported there. None of the open fixes removes the echo:

Upstream What it does This case
PR #33859 Ignores an empty task-notification result in the same check ❌ Echo still counts
PR #32508 A hand-off keeps a busy session instead of refusing ❌ Echo still counts; only the refusal is hidden
Branch MikaMcFlurry:mika/queue-busy-switch Counts root frames after the last buffered result ❌ The echo is such a frame

From reading their code, not from running those branches.

Change

In ClaudeAdapterV2.ts, a replayed local-command echo that arrives while no turn is active is dropped instead of buffered. Frames inside a turn are handled as before.

Other providers

Only Claude is affected. The others start a wake on an explicit turn-start event, not on any frame that arrives between turns:

Adapter Answer to a live model change What starts a wake between turns Affected
Claude user echo frame Any root user, assistant or result frame ❌ Yes
ACP (Grok, Copilot, Antigravity, Kiro) config_option_update or current_mode_update Agent output, user_message, state running ✅ No
Pi response agent_start ✅ No
Muse Request reply turn/started ✅ No
Codex, OpenCode — No frame buffer; tracked commands, tools and subagents ✅ No

From reading each adapter, not from running the providers.

Scope and approval

One bug, one adapter. Fork fix on main. The other stale-state cases in pingdotgg/t3code#32296 are not addressed.

Verification

Before is ClaudeAdapterV2.ts from main with this PR's test:

BEFORE
 × does not count the echo of a live model switch as background work 72ms
      Tests  1 failed | 348 skipped (349)

AFTER
 ✓ does not count the echo of a live model switch as background work 67ms
      Tests  1 passed | 348 skipped (349)

The test answers setModel with the echo, as the CLI does, and runs a turn on the new model. It also checks that a root user frame that is not an echo still counts; widening the fix to any user frame fails that assertion.

From the incident (0.1.7-nightly.20261106.30, Windows, Claude Code 2.1.316, UTC):

16:22:39.087 IN  result                     turn 1 ends
16:24:41.210 IN  user  "<local-command-stdout>Set model to `claude-opus-5-5`</local-command-stdout>"
16:24:41.211 OUT query.set_model claude-opus-5-5
16:24:41.305     prompt.offer               turn 2 starts
16:24:44.761 IN  result                     turn 2 ends
16:25:07, 16:25:20, 16:25:56                send to another provider refused
16:34:04.350     Force stop                 session released

The log has no task or subagent frames. All 6 live model switches in local logs produced this echo, each just before the CLI confirmed the switch.

vp run check:changed passed, including the full ClaudeAdapterV2 file (350 tests). The app with a real Claude turn, the full suites and the build were not run. Node 24.4.1, not the pinned 26.12.1.

Written with Claude Fable 5.2 in T3 Code.

🤖 Generated with Claude Code

…ider switch

The Claude CLI answers setModel with a replayed local-command user frame.
It arrives before the next turn attaches, so the adapter buffered it as the
start of a wake. Nothing drains that buffer on a user turn, so the session
reported background work until the process exited and every provider switch
was refused.

Drop that echo when no turn is active.

Co-Authored-By: Claude <noreply@anthropic.com>
@iXab3r
iXab3r merged commit 8f636d0 into main Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant