Default model in settings.json ("claude-fable-5[1m]") is not honored at session start; /model does not reliably switch either
Title: Default model in settings.json ("claude-fable-5[1m]") is not honored at session start — sessions launch on a different model, and in-session /model does not reliably fix it
Summary:
My global ~/.claude/settings.json has had "model": "claude-fable-5[1m]" set for several days, but sessions in my main project keep launching on claude-sonnet-5 instead. In-session /model claude-fable-5[1m] (the exact picker ID, brackets included) does not reliably switch either — it has returned Kept model as Sonnet 5 / Kept model as Opus 5 on multiple occasions without switching, and once returned "Model 'Fable 5' not found" when I used the display name instead of the exact ID.
Impact:
On 2026-07-28 this caused a full day of multi-agent/CI work to be silently produced under the wrong model (Sonnet 5) while I believed I was running Fable 5, because there was no reliable way to verify which model was actually active. That work had to be discarded and redone. As of 2026-07-30 the problem is still unresolved — sessions are still launching on Sonnet 5 despite the correct default in settings.json.
Steps to reproduce:
- Set
"model": "claude-fable-5[1m]"in~/.claude/settings.json. - Start a new Claude Code session in a project.
- Session reports itself as
claude-sonnet-5(visible in the system/environment context, e.g. "You are powered by the model named Sonnet 5"). - Run
/model claude-fable-5[1m](exact ID copied fromadditionalModelOptionsCachein~/.claude.json). - Response:
Kept model as Sonnet 5— no switch occurs.
What I've verified is NOT the cause (checked 2026-07-30):
~/.claude/settings.json→"model": "claude-fable-5[1m]"(correct)~/.claude.json→ nolastModelfield; no per-project model override for the active project path;orgModelDefaultCacheis empty~/.claude.json→additionalModelOptionsCacheconfirms the correct picker value isclaude-fable-5[1m](label "Fable")~/.claude/settings.local.json→ nomodelkey- Environment variables
ANTHROPIC_MODEL/CLAUDE_MODEL→ unset ~/.claude.json→projects["C:/Users/Asus"].lastModelUsageshows onlyclaude-sonnet-5andclaude-haiku-4-5-20251001usage — no Fable usage recorded at all despite the settings.json default being Fable for days
Since none of the on-disk config sources explain the mismatch, the "which model actually runs" state appears to be tracked somewhere else (client-side persisted UI state?) that overrides settings.json's default and isn't fixable by editing config files.
Environment:
- OS: Windows 11 Home Single Language, build 10.0.26200
- Shell: PowerShell 5.1 (also using Git Bash)
- Client: Claude Code CLI (
claude.exe), launched from a terminal (PowerShell/cmd) — not the desktop app, not a VS Code/IDE extension - Claude Code version: 2.1.220
Request:
- Please confirm whether
settings.json's"model"field is expected to be authoritative at session start, and if so, why it's being overridden. - Please confirm whether
/model <exact-id>is expected to always succeed when the ID matchesadditionalModelOptionsCacheexactly, and if not, what the reliable way to switch/verify the active model is (the "Kept model as X" confirmation text has been unreliable/misleading in my testing — the highlighted option in/modelopened with no argument seems to be the only trustworthy signal so far).
Showing cached comments. Read the full discussion on GitHub ↗
6 Comments
Corroborating with a second variant, plus what looks like the root cause and a regeneration loop. CC 2.1.220, macOS.
Same config value, different silent landing model. With
"model": "claude-fable-5[1m]"in~/.claude/settings.json, every interactive session launched asclaude-opus-5— verified from the"model"field in ~30 session transcript.jsonlfiles (transcripts, not flags, are the reliable check). The only surface that reveals it is the/modelbanner fine print: "settings.json pins Opus 5 (1M context)". So the same value lands on Sonnet 5 for you and Opus 5 (1M) for us — presumably whatever the resolver falls back to.The value itself appears to be the root cause:
claude-fable-5is natively 1M context (the maximum is the default), so a[1m]variant of Fable doesn't exist — the suffixed id is invalid and gets silently resolved to a different model family instead of being rejected.The value regenerates: after we deleted the key, a later
/model"save as default" rewroteclaude-fable-5[1m]into settings.json — the picker itself writes the invalid id, so users may never have hand-typed it, and manual correction doesn't stick. Setting the bare id"claude-fable-5"behaves correctly.Expected: persist/resolve the bare
claude-fable-5, reject the[1m]form for models with no such variant, or warn at session start when the configured string resolves to a different model family.Feedback ID (submitted via /bug): ec1d3551-326c-4156-83c6-34f953b225f4
Follow-up with new evidence: the invalid
[1m]value is written on normal/modeluse - no save-as-default step required.After my previous comment I reset the key to a bare
claude-fable-5and avoided save-as-default entirely. The next day it rewrote itself twice anyway (CC 2.1.220, macOS - times below are file mtimes on~/.claude/settings.json, local):"model": "claude-fable-5"(manually set, clean)"model": "opus[1m]"- after switching the running session to Opus via/model"model": "claude-fable-5[1m]"- no save chosen; the only/modelinteraction around then was checking which model the session was onSo the picker persists a
<family>[1m]id as a side effect of selection/interaction, not just through the explicit save-as-default flow. I can't pin the exact UI action that triggers the write - there was no deliberate save either time. Two consequences:/modeluse re-poisons it.opus[1m]too). The silent-mismatch case is stillclaude-fable-5[1m]: Fable is natively 1M, the suffix isn't a valid variant there, and the loader lands on Opus 5 (1M) while everything except the/modelfine print still says Fable.Ground truth that made this diagnosable: the per-request
"model"field in the session transcript JSONL - e.g. one of yesterday's sessions loggedclaude-opus-5on 352 of 352 requests while settings claimed Fable. Suggest the fix cover selection-persistence too, not just save-as-default: either don't persist on selection, or validate before writing (reject[1m]where no such variant exists).Reporter here. New evidence that changes the diagnosis in my original post: the silent
fallback also happens on a path that never touches
settings.jsonand never involves the[1m]suffix. So there appear to be two distinct defects in this thread, and for myaccount the
[1m]invalid-id explanation (@DelanoJoey's, which reproduces cleanly on hismachine) is not what keeps Fable from running.
Environment: CC 2.1.220, Windows 11, Max 5x. Entitlement present.
Repro that isolates it from config
Spawn a subagent with an explicit model override, from a session running Opus 5:
Then read the ground truth — the per-request
"model"field in that subagent's transcript(
~/.claude/projects/**/agent-<id>.jsonl), same method @DelanoJoey used:Requested Fable. Served Sonnet 5. No error, no warning, no notice anywhere in the UI.
This path reads no
modelkey fromsettings.jsonand constructs no[1m]id.Control (the part that makes the above diagnostic)
The obvious alternative reading is "the
modeloverride is simply ignored for subagents,and
general-purposejust runs Sonnet 5 by default". Ran the identical spawn with theoverride removed, same session, same agent type:
With no override the subagent inherits the parent session's model (Opus 5). So
general-purposedoes not default to Sonnet 5, and the override is not being dropped — ifit were, the Fable spawn would have landed on Opus 5 too. The Fable spawn landed on a model
that is neither the one requested nor the one inherited, which leaves one reading: the
request is routed as Fable, refused, and silently substituted.
Side benefit: this is a ~2-second, config-free check for "is Fable serving yet?" that
doesn't require restarting a session or editing
settings.json.Scope on this account
Counting
message.modelacross all 499 transcripts on this machine (at time of writing):| model | messages |
|---|---|
|
claude-sonnet-5| 18,305 ||
claude-opus-4-8| 15,572 ||
claude-opus-5| 14,976 ||
claude-fable-5| 0 |Fable has never served a single message here, by any route.
Client-side state is fully permissive (so this isn't entitlement or a kill switch)
From
~/.claude.json,cachedGrowthBookFeaturesAt= today:clientDataCacheSlots[*].data.cedar_lagoon.claude-fable: truetengu-fable-off-switch: {"activated": false}tengu_usage_overage_included_models: ["Fable", "Fable 5"]additionalModelOptionsCache[0]: {value: "claude-fable-5[1m]", label: "Fable"}A diagnostic trap worth fixing on its own
lastShownEmergencyTipstill reads:It is a last-shown record with no timestamp, so it persists indefinitely and reads as
current state. Mine dates from the June 12–30 export-control window; those controls were
lifted at the end of June, and the string is still there weeks later. It sent my own
diagnosis down the wrong path twice — once by making a stale condition look current, and
once by making a genuinely current condition look like it was already explained. Either
clear it when the condition clears, or timestamp it.
Asks, in priority order
the one that caused real damage: a full day of multi-agent work in my case was produced
under Sonnet 5 while every surface — including the assistant's own system context — said
Fable 5, so nothing in the session could flag it. A silent fallback is recoverable; a
silent fallback plus a false identity is not.
emit a one-line notice naming the model actually used. Right now the only way to find out
is to parse transcript JSONL after the fact.
[1m]persistence/validation issue as described in the comments above — it is areal second defect even though it is not what blocks Fable here.
Happy to supply the raw transcript excerpts or re-run the probe on request.
Ran your probe on my setup - opposite result, which helps scope defect #2.
Environment: macOS 26.5.2, CC 2.1.220 (same build as yours). Same account as my comments above.
Probe:
Caveat on the control side: my parent session was already on Fable 5 (10/10 assistant messages in the parent transcript are claude-fable-5), so unlike your A/B pair this spawn can't distinguish "override honored" from "override dropped and inherited". For the serving question it's decisive either way: a request routed to Fable was served claude-fable-5. A wider sweep of this machine's transcripts shows claude-fable-5 as the served model across sessions and subagents on Jul 30, Jul 31, and Aug 1.
Same client build, opposite serving outcome. That points at server-side per-account gating for your defect #2, not client code - consistent with your GrowthBook flags being permissive; the client isn't the layer making the call.
Defect #1 fired end-to-end here today, in one exchange. /model -> Fable 5, "saved as your default for new sessions". That save wrote
back into settings.json (the save path re-adds the suffix, same as my comments above). The same dialog then printed:
So with the file literally reading fable-5[1m], the product's own banner describes the pin as Opus 5 (1M). The UI displays the pin post-resolution. Save Fable, file says fable with the suffix, banner says it pins Opus, restart serves Opus. I reset the file to bare "claude-fable-5", which behaves.
Your stale-tip find reproduces here. My ~/.claude.json carries the identical lastShownEmergencyTip string, dating from the same June window. +1 to clearing it when the condition clears, or timestamping it.
One note for anyone grepping transcripts for the served model: tool_use inputs are structural JSON in the parent transcript, so the spawn's own model: "fable" argument matches unescaped greps. Filter to assistant entries (message.model is the response's model, i.e. what actually served) or read the subagent transcript like you did.
Your asks 1 and 2 would have caught both defects on both machines.
That's the control I couldn't run on my own machine — thank you for running it, and for flagging the limitation of your own probe before anyone had to ask. That kind of caveat is exactly what makes the rest of your data easy to trust.
Same build, same probe, opposite serving outcome is about as clean a scoping result as this thread could get: whatever is deciding is not in the client. Your reading looks right to me.
One more data point that separates defect #2 from defect #1 on my account
Since my last comment my
settings.jsonhas read a bare, valid"model": "claude-opus-5"— no[1m]suffix, and no Fable string anywhere in my config. Fable still never serves. So on this machine the two are now cleanly independent: there is no invalid id left for the loader to resolve wrongly, and the fallback behaves exactly as it did before. Your[1m]persistence finding stands entirely on its own — my case just isn't downstream of it.Refreshed count (same method you used:
message.modelon assistant entries)All transcripts on this machine, re-counted today:
| model | messages | last served |
|---|---|---|
|
claude-sonnet-5| 18,305 | 2026-08-01 ||
claude-opus-4-8| 15,572 | 2026-07-24 ||
claude-opus-5| 15,367 | today ||
claude-fable-5| 0 | never |501 transcripts. Opus 5 served me ~40 minutes before writing this, so the account is serving normally — just never Fable, by any route: session default,
/model, or subagent override.Your grep caveat deserves to be the headline for anyone auditing this
I'd put it even more strongly than you did, because it's the crux of the whole diagnosis: in the parent transcript, a spawn's own
model: "fable"argument is structural JSON inside thetool_useinput, so a plain search forfablematches the request, not the response. Onlymessage.modelontype: "assistant"entries records what actually served. Someone checking the obvious way would conclude Fable was working fine.Why asks 1 and 2 still stand, even if the gating is server-side
If the decision is made per account on the server, the client still doesn't have to describe it incorrectly: the response that comes back carries the model that produced it. The mismatch is observable at the moment it happens, client-side, with no server change required — my own session context said Fable 5 while every response in that transcript said Sonnet 5.
So the part that actually caused damage isn't gated on whatever is doing the gating:
Between the two machines in this thread, those two would have caught both defects on both platforms, without either of us having to parse JSONL after the fact. Thanks again for the cross-check — it turned two separate anecdotes into something with a shape.
Also submitted through
/bug, so this thread and the internal pipeline are linked for whoever triages it:Feedback ID
b1db0568-4a6f-41ac-82bd-fb91e4fdddde— Windows 11, CC 2.1.220, Max 5x.Same substance as my comments here: the subagent probe with its control (override honored → inherits Opus 5; override set to
fable→ served Sonnet 5), the 501-transcript count withclaude-fable-5at zero, and the scoping against @DelanoJoey's opposite outcome on the same build.Both affected accounts in this thread now have an internal feedback ID attached — @DelanoJoey's is
ec1d3551-326c-4156-83c6-34f953b225f4(macOS, served Fable) and mine is the one above (Windows, never served Fable). Same client version on both, which should make the two IDs useful as a matched pair rather than two separate reports.