Skip to content

fix(server): a Claude model change no longer blocks the next provider switch - #36641

Closed
jalay-mantys wants to merge 1 commit into
pingdotgg:mainfrom
jalay-mantys:fix/claude-model-change-echo-background-work
Closed

jalay-mantys wants to merge 1 commit into
pingdotgg:mainfrom
jalay-mantys:fix/claude-model-change-echo-background-work

Conversation

@jalay-mantys

Copy link
Copy Markdown

Problem

After changing the model inside a Claude thread (Opus 5.5 → Fable 5.2 on the same instance), pressing Stop, and then switching to another provider (Codex 6.1 Sol), the send is refused:

The previous provider is still running background agents or commands, and switching would end them. Wait for them to finish, or press Stop, then send the message again.

Nothing was running. The thread's pendingBackgroundTasks was empty, there were no subagents or background commands, and the Claude process had exited. Retrying, or pressing Stop again, gave the same refusal.

Cause

On a live model change the adapter calls set_model before it attaches the new turn. Claude Code answers with a replayed user frame:

{"type":"user","message":{"role":"user","content":"<local-command-stdout>Set model to `claude-fable-5-2`</local-command-stdout>"},"parent_tool_use_id":null,"isReplay":true}

No turn is active yet, so the frame goes to bufferWakeMessage, which treats any user frame as wake evidence. The buffered user frame makes hasPendingBackgroundWork return true. An ordinary interrupt closes the query but does not clear the wake buffer, so the provider-switch guard in Orchestrator.ts keeps refusing.

Change

bufferWakeMessage ignores replayed <local-command-stdout> user frames. They are output echoed back from a command the adapter sent itself, not background work. Turns that are active still handle these frames as before, for example /compact's "Compacted" echo.

Scope

A small bug fix with one root cause. Related: #36450 and #35240 report the same refusal. #36596 lets Stop clear background state that only the provider's probe sees; this PR removes one source of that false state, so Stop isn't needed in this case.

Verification

  • Found from a real occurrence on 0.1.10-nightly.20261114.4669 (macOS desktop). The provider event log shows the Set model to replay at the model change, then query.interrupt → result: error_during_execution → query.close, with no task_started or background frames at any point. The server trace shows every later message.dispatch failing with the refusal above.
  • New test in ClaudeAdapterV2.test.ts, "does not count a live model change's stdout echo as background work". It fails on main (hasPendingBackgroundWork is true) and passes with the fix.
  • ClaudeAdapterV2.test.ts (425 tests), ClaudeReplayFixtures.integration.test.ts, ProviderSwitch.integration.test.ts and BackgroundWorkStop.integration.test.ts (109 tests) all pass. The compact fixtures that replay <local-command-stdout>Compacted are among them. vp run check:changed passes.
  • Not checked: an end-to-end run against a real Claude CLI on a dev server.

Made with Claude Opus 5.5 in Claude Code.

… switch

Claude Code replays set_model's "<local-command-stdout>Set model to …" as a user
frame before the next turn attaches. The adapter buffered it as wake evidence, so
hasPendingBackgroundWork stayed true and switching provider was refused after Stop.

Made with Claude Opus 5.5 in Claude Code.
@github-actions github-actions Bot added vouch:unvouched Author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Nov 14, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 14, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 4db40b7

Macroscope's review found this PR approvable — Replayed Claude local-command output is excluded from wake buffering so changing models no longer blocks the next provider switch. The filter targets a specific echo format while preserving other wake-message handling.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Nov 14, 2026

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

ClaudeAdapterV2 now skips replayed user frames that begin with <local-command-stdout> when buffering wake messages. A regression test checks that a replayed set_model stdout echo does not cause hasPendingBackgroundWork to return true.

Changes: Claude local-command echo filtering

Layer / File(s) Summary
Filter replayed local-command echoes
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
isClaudeLocalCommandEcho identifies replayed user frames with string content that begins with <local-command-stdout>. bufferWakeMessage skips matching frames. The regression test checks that a replayed set_model stdout echo does not report pending background work.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: juliusmarminge, t3dotgg, yash-singh1

Merge Risk: Minimal · up to 4db40

Changing a Claude model and pressing Stop no longer leaves the thread looking as if it has background work, so the next provider switch should no longer be refused. The change is small and covered by a regression test. The only unchecked item is an end-to-end run against a real Claude CLI, which does not block merging.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title names the main change: a Claude model change no longer leaves pending background work that blocks the next provider switch. The changeset adds a filter in bufferWakeMessage that ignores repl…
Description check Passed The description follows the template. It has Problem, Change, Scope, and Verification sections, and each contains specific content. The Problem section gives the reproduction steps and expected behavi…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: a Claude model change no longer leaves pending background work that blocks the next provider switch. The changeset adds a filter in bufferWakeMessage that ignores replayed <local-command-stdout> user frames, plus a regression test. The title uses the fix(server) conventional-commit form and is specific enough for a teammate scanning history.

Full details: Description check

Explanation

The description follows the template. It has Problem, Change, Scope, and Verification sections, and each contains specific content. The Problem section gives the reproduction steps and expected behavior. The Change section explains the root cause and the fix. The Scope section says this is a small bug fix and cross-references related issues. The Verification section lists focused tests, the observed results, and what was not checked. It also includes the model and harness line that the template asks for.

Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 13f89ae and 4db40b7.

Files selected for processing: 2

  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@juliusmarminge

Copy link
Copy Markdown
Member

Thanks for this. #34498 makes the same change and is the one being kept.

#34498 fixes the same replayed model-change echo being mistaken for background work. Its regression also drives a real model change through the adapter and verifies that an ordinary user frame still counts as wake evidence, which makes it the stronger version to keep.

Your report independently confirms the same cause. The broader missing Stop control and other sources of stale background state remain separate problems.

Closing as a duplicate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S 10-29 changed lines (additions + deletions). vouch:unvouched Author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants