Show which model actually answered - #2
Closed
android6 wants to merge 1 commit into
Closed
Conversation
Claude Code switches models behind OpenCode's back in several ways and the
proxy dropped every signal, so OpenCode kept showing the requested model:
* Safety-classifier fallback. When a request is blocked on Fable 5 the CLI
re-runs it on Opus (4.8 for cyber, 5 for bio) and emits
'system/model_refusal_fallback'; with scope 'session' the session model
stays on Opus for every later turn. Map it (and
'model_refusal_no_fallback') to a notice naming both models, the refusal
category, the scope, the CLI's own banner text and - when the API
provides one - the refusal explanation, which Claude Code's UI does not
show.
* Any other switch. 'assistant.message.model' is the model that produced an
answer. The baseline is the model OpenCode asked for (the user's picker
choice); a differing init.model means the CLI kept the session latched on
an earlier fallback and is announced too ('[model] session is on
claude-opus-4-8 (selected: fable)'). Track the model the user was last
told is answering per bridge and announce a differing answer model once:
'[model] answered by claude-opus-4-8 (session model: claude-fable-5)'.
Ids are compared prefix/date-insensitively; a bare alias ('opus') matches
its pinned versions so ambiguity never raises an alarm.
* CLI notices. 'system/notification' mirrors the interactive REPL's
notification area (deprecations, limits, account state). Forward medium
and higher priorities as '[notice] ...' in the reasoning trace;
low-priority tips stay silent.
Model-identity notices go into the answer content as a Markdown callout
(blockquote, ahead of the model's text) so they are visible regardless of
how the host renders reasoning traces - hosts commonly collapse or hide
them. OPENCODE_CLAUDE_MODEL_NOTES=reasoning keeps them in the thinking
trace like the compact and rate-limit notes. All notices are mirrored to
the warn log.
Field names follow the SDK wire shape (snake_case, as built by the CLI's
SDK serializer); the CLI's internal camelCase shape of the same events is
accepted too.
Tests: unit coverage for the formatters, modelsMatch, the notice channel
and the callout renderer (incl. a fixture mirroring real
model_refusal_fallback events from claude CLI 2.1.197-2.1.237), plus proxy
round-trips with mocked SDK streams for the fallback case (no duplicate
notice, notifications stay in reasoning), the quiet-switch case (notice
exactly once, buffered and streaming, both channels) and a session latched
on a fallback model.
android6
force-pushed
the
feat/model-fallback-note
branch
2 times, most recently
from
August 20, 2026 21:06
ae22093 to
c81642e
Compare
Author
|
Closing in favour of #1 (reopened). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Claude Code can switch models mid-conversation: when the safety classifiers refuse a request on Fable 5, the CLI silently re-runs it on Opus. OpenCode keeps displaying the model the user picked, so there is no way to tell.
This adds a short callout at the end of the answer whenever the answering model differs from the selected one:
model switched- from the SDKmodel_refusal_fallbackevent (models, safeguard category, scope).model refused-model_refusal_no_fallback: the turn ended with an error and no retry ran.model mismatch-init.modelorassistant.message.modeldiffers from the user's pick; catches switches the CLI doesn't announce.One note per leg, always after the text so it can't land inside a code fence.
OPENCODE_CLAUDE_MODEL_NOTES=reasoningmoves it into the thinking trace instead. CLIsystem/notificationevents (medium+) are forwarded to the reasoning trace as[notice].Seen live with CLI 2.1.237: bio -> claude-opus-5, cyber -> claude-opus-4-8.