/model switch silently ignored — session served Haiku (and other wrong models) despite explicit /model switches
Summary
A Claude Code session ran an entire 455-message conversation on claude-haiku-4-5-20251001,
even though the user issued multiple /model fable and /model sonnet commands mid-session
(each acknowledged by the UI: "Set model to Fable 5 / Sonnet 5 and saved as your default"), and
the project's ~/.claude/settings.json had "model" set to a Fable variant. The UI never
indicated anything was wrong — it silently kept serving Haiku.
Evidence
- Session transcript file (
<session-id>.jsonlunder~/.claude/projects/<project-path>/) records a
model field on every assistant message. In the affected session, all 455 assistant
messages carry "model":"claude-haiku-4-5-20251001" — including messages sent after
/model fable and /model sonnet were run and acknowledged.
- User-visible symptoms during the session: unusually fast/terse responses, shallow reasoning
not matching the requested model tier, and out-of-place apologetic phrasing — consistent with
a much smaller model than was requested/expected.
- We only discovered the mismatch by having a second, correctly-behaving session (confirmed via
its own transcript to be running Fable throughout) inspect the first session's transcript file
directly and compare the model field per message.
Suspected mechanism (unconfirmed — flagging as a hypothesis, not a diagnosis)
In a separate but possibly related incident the same evening, /remote-control in a different
session explicitly refused with:
"Remote Control requires a full-scope login token. Long-lived tokens (fromclaude setup-tokenorCLAUDE_CODE_OAUTH_TOKEN) are limited to inference-only for security reasons. Runclaude auth login."
That session had CLAUDE_CODE_OAUTH_TOKEN set in its environment (a long-lived setup-token
credential used to run a second account, rather than the standard interactive keychain login)./login in that window restored full scope and fixed Remote Control.
We suspect the Haiku-serving session may have been launched under a similarly-scoped auth
context, and that scope/entitlement difference silently affected which model /model actually
routes to — with /model's UI acknowledging the switch regardless of whether it took effect.
We were not able to confirm this: the affected session's shell/launch history wasn't available
to check its auth context directly.
Update — mechanism corroborated by transcript forensics
The affected session has been located and inspected (~/.claude/projects/<project-path>/), Claude Code
2.1.201. All 455 main-chain assistant messages carry "model":"claude-haiku-4-5-20251001",
including every message after each of the 10+ /model sonnet / /model fable commands issued
across the session (first user message in the session was /model sonnet).
New evidence supporting the auth-scope mechanism:
- The transcript itself contains the restricted-token fingerprints:
cc2(13×),setup-token
(6×), and CLAUDE_CODE_OAUTH (9×) — i.e. this session was the second-account / setup-token
workstream. A local wrapper script launches Claude Code with CLAUDE_CODE_OAUTH_TOKEN set to a
long-lived setup-token — the exact inference-only credential the refusal message names.
- The session's header records
type:"bridge-session"+permissionMode:"bypassPermissions",
consistent with a launcher that adds --dangerously-skip-permissions --remote-control; the
--remote-control refusal that evening is now read as a symptom of the same token-scope gap,
not a separate incident.
- Caveat on strength: the setup-token strings are present in the transcript but the transcript does
not dump the process env, so this corroborates rather than directly captures the env var. Combined
with the bridge-session marker, the total Haiku lock, and the same-evening remote-control refusal,
the auth-scope mechanism is well-supported.
Clean repro observed: a restricted-scope window stays pinned to its current model and/model cannot move it (UI still acknowledges each switch); opening a fresh normal-login window
and switching models there works immediately, and /resume continues the conversation on the
correct model. The stuck window is only fixable by relaunch//login (full scope), not by /model.
Scope note — this explains the severe case (total model lock under a restricted token). A
milder, separate /model unreliability was also observed in a normal, full-scope session
(session e70dc13c: /model sonnet produced zero Sonnet turns — 10 subsequent turns stayed on the
prior model — while a later /model opus in the same session did take effect). That milder case is
not explained by token scope and may be a distinct /model hot-swap latency/reliability issue.
Update — second failure signature (target-specific ignore) + self-masking system prompt
Forensic audit of message.model (assistant entries) vs /model command entries across 13
sessions / 30+ switches, over a ~3-day window, in local project transcript logs.
- Second, distinct failure signature — target-specific switch ignore in FULL-SCOPE windows.
The restricted-scope CLAUDE_CODE_OAUTH_TOKEN root cause above does not
explain these: sessions e70dc13c (3× /model sonnet ignored while /model opus and
/model fable in the same session took effect), 5cc2d13f (1× sonnet ignored, session
stayed on fable-5), e5e2f4cf (1× sonnet ignored). All in normal
full-scope windows. Switches targeting sonnet were intermittently ignored while switches to
fable/opus took effect — hypothesis: capacity/routing-dependent silent fallback, not token
scoping. Not confirmed. The client UI confirmed each switch ("Set model to...") regardless of
whether it took effect.
- Stale system-prompt model line makes the bug self-masking. In session
a89371ba,
/model fable did take effect (subsequent assistant messages carry
"model":"claude-fable-5"), but the system prompt's "You are powered by the model named Opus
4.8" line was rendered at session start and never re-rendered after the mid-session switch.
Asked to verify its own identity, the assistant confidently reported the wrong model by quoting
the stale system-prompt line — denying it was Fable while being served by Fable. Consequence:
neither the client banner (says a switch happened when it may not have) nor the model's
self-report from its system prompt (says the old model after a switch that did happen) can be
trusted. The only ground truth found is the transcript message.model field.
- Repro summary table over the audit window: most switches took (
dc2ee8bc2/2,
c89bffc2 5/5, e5e2f4cf 6/7, 363f6015 2/2, 163bc8c5 1/1, a89371ba 1/1); failures
cluster on sonnet-as-target as described in (1); the hard-lock case (4cf50265, 455
turns 100% claude-haiku-4-5, all switches ignored) remains the severe restricted-token
variant, unchanged.
What we'd like
/modelshould either genuinely switch the serving model, or hard-refuse with a clear error
(the same way /remote-control now does for token-scope mismatches) — never silently
acknowledge a switch that doesn't take effect.
/status(or some always-visible surface) should make the actual currently-serving model
impossible to misread, even under a restricted/token-based auth scope, so a mismatch like
this is caught in seconds rather than requiring a transcript-level forensic comparison.
Repro
Not isolated to a minimal repro — this is drawn from production usage. Session id available on
request if useful for investigation.