Fable 5 silently substituted in VS Code extension while the same account runs Fable 5 in the desktop app
Summary
On the same machine, same account, same subscription (Max 5x, no API key anywhere — subscription auth only), Fable 5 works in the Claude desktop app but is silently swapped for Sonnet 5 in the VS Code extension. The extension's model picker reports Fable as active before sending, then switches after the first message. There is no API error — the substitution is entirely client-side.
Steps to reproduce
- In the VS Code extension, select Fable 5.
/modelconfirms:
````
Current model: Fable 5 (session override from plan mode)
Base model: Opus 5 (effort: xhigh)
- Send any message.
- Get:
Switched to Sonnet 5 for this session · Fable 5 requires usage credits · /model to change
Expected vs actual
Expected: either Fable runs, or the UI tells me up front that it can't — before I've written a prompt and hit send.
Actual: the UI reports Fable as the active model, then silently downgrades on send. Without reading the system notice you can work for a long time convinced you're on Fable while you're on Sonnet.
Three separate problems
1. Inconsistent entitlement across first-party clients
Same account, same moment in time:
| Client | Fable 5 |
|---|---|
| Claude desktop app (spawns claude-code 2.1.221 with --model claude-fable-5) | works |
| Extension binary 2.1.222, headless -p / stream-json | works |
| VS Code extension 2.1.222, interactive | blocked |
| claude.ai in the browser | works |
If the account genuinely lacks entitlement, the desktop app should not be serving Fable either. If it does have entitlement, the extension should not be blocking.
2. Consenting to the dialog does not work
With the model set at spawn time, the extension shows the real consent dialog ("Fable 5 requires usage credits … Buy usage credits on claude.ai / Switch to the default model and continue") instead of substituting silently. But pressing consent still ends in a fallback — the transcript records model_consent_fallback with choice: "consent", and the telemetry outcome is overage_enable_deferred. So the consent path is a dead end: it offers an agreement that cannot succeed.
Related: client state reports spend.can_purchase_credits: false and spend.can_toggle: false for a personal Max org, so the "Buy usage credits" button may also be a dead end for this account type.
3. Client and server disagree
The API serves the model without complaint — the same extension binary returns a normal completion from claude-fable-5 with service_tier: standard — while simultaneously returning the header:
anthropic-ratelimit-unified-overage-disabled-reason: org_level_disabled
The client stores that header value and gates on it. Notably the gate's accept-list contains org_level_disabled_until (with the _until suffix), while the server sends org_level_disabled — so a stored, previously granted consent is treated as not granted. This looks like a string mismatch between the server's value and the client's expected set, but I can only observe it from outside.
Environment
- Extension:
anthropic.claude-code-2.1.222-darwin-arm64, macOS 15 (Darwin 25.5.0), arm64 - Desktop app: Claude 1.25927.0, bundling claude-code 2.1.221
- Plan: Max 5x,
billingType: stripe_subscription, personal org (UUID redacted), created 2026-08-03 - No
ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKENset anywhere (env, shell profiles,terminal.integrated.env)
Things that do not help
Restarting VS Code, /logout + /login, clearing the cached server state in ~/.claude.json (it is re-fetched identical — the reason arrives as a response header, so it is not a cache), and claudeCode.selectedModel (which appears to be unread by extension 2.1.222 — 0 occurrences in extension.js/webview).
The smallest useful fix
Even leaving the entitlement question aside: don't show a model as active if sending will substitute it. Gate at selection time, with the reason, instead of after the prompt is written.