Fable 5 safety classifier fires model_refusal_fallback on a bare `hello` — scores the static request preamble, not the user message
Fable 5 safety classifier fires model_refusal_fallback on a bare hello — trigger is the static request preamble, not user content
Summary
In Claude Code, Fable 5's input safety classifier emits a model_refusal_fallback (silent switch to Opus 4.8) on the first turn of essentially every session on my account — including a session whose only user input is the word hello!. No repo content, no tool calls, and no file reads are in context when it fires.
Because a content-free hello! cannot itself be a cybersecurity or biology query, the signal must come from the static request preamble that Claude Code sends with every request (system prompt + tool/skill schemas + the deferred_tools_delta / skill_listing attachments). The likely contributors are (a) the cybersecurity safety boilerplate in the Claude Code system prompt and (b) global-health MCP server names in deferred_tools_delta. This matches the banner's exact wording: "flagged this message for cybersecurity or biology topics."
Net effect: the account is silently downgraded from Fable 5 to Opus 4.8 on basically every conversation, defeating the model selection.
Environment
- Claude Code (CLI), macOS (Darwin 25.5.0)
- Default model: Fable 5 (
claude-fable-5) - Fallback observed: Opus 4.8 (
claude-opus-4-8) - Connected MCP servers include several Gates-Foundation / global-health tools (see Root Cause)
- Date of captured sessions: 2026-06-09, 19:44–20:23 UTC
The fallback event (verbatim)
{"type":"system","subtype":"model_refusal_fallback","level":"warning",
"trigger":"refusal","originalModel":"claude-fable-5[1m]",
"fallbackModel":"claude-opus-4-8",
"apiRefusalCategory":null,"apiRefusalExplanation":null}
Banner shown to the user:
Fable 5's safety measures flagged this message for cybersecurity or biology topics. They may flag safe, normal content as well. These measures let us bring you Mythos-level capability in other areas sooner, and we're working to refine them. Switched to Opus 4.8.
Note apiRefusalCategory and apiRefusalExplanation are both null, so the log gives no machine-readable reason — the only signal is the human banner's "cybersecurity or biology."
Reproduction — minimal case
Session fb1f5cb7 contains a single user turn and nothing else. Row-by-row from the session JSONL:
| row | timestamp (UTC) | event |
|-----|-----------------|-------|
| 3 | 20:00:07 | USER: hello! |
| 4 | 20:00:07 | attachment: deferred_tools_delta (tool/MCP names) |
| 5 | 20:00:07 | attachment: skill_listing |
| 6–7 | 20:00:12 | ASSISTANT (claude-opus-4-8): "Hello, Mike. What are we working on?" |
| 8 | 20:00:12 | SYSTEM model_refusal_fallback (fable → opus) |
There are no tool calls and no file reads anywhere in this session. Fable produced zero tokens; the only assistant output is from the Opus fallback. The entire request payload at trip time was: hello! + deferred_tools_delta + skill_listing + the (unlogged, client-built) system prompt and tool schemas.
Reproduced 6 / 6 real conversational openings on my account today (sessions 141b1986, 9fc39968, c890f0ae, cf63364b, eae32e64, fb1f5cb7), every one on the first turn.
What it is NOT
- Not repo content. A natural hypothesis was that this repo's domain — indoor air quality: airborne concentration decay, formaldehyde/TVOC/particulates, air-changes-per-hour ventilation, exposure-over-time kinetics — reads to a bio/chem classifier like indoor agent-release exposure modeling. That content was never in context at trip time (the
hello!-only session loaded no files at all, and in the other sessions the fallback fired before anyls/readtool ran). So whatever the classifier keys on, it is not the IAQ files.
- Not the same as #66643. That issue is the
claude-apiskill trigger self-firing on the/modelbanner's ANSI[1m]token — a client-side skill-dispatch matter. This is the server-side safety classifier (subtype: model_refusal_fallback,trigger: refusal). They only coincidentally share the[1m]string. Please don't merge them.
Attribution caveat (for anyone reproducing from a transcript)
The fallback is silent and seamless: the banner appears, then the conversation continues on Opus with no visible break. In a session where the user said "take a look around," the post-fallback Opus instance issues the ls/git/find calls, which in the rendered transcript look like they preceded the fallback. They did not. The JSONL message.model field and the timestamps are the only reliable disambiguator: in session 9fc39968, the refusal is stamped 20:23:40 while the first tool call is 20:23:47, and every assistant row is claude-opus-4-8. Anyone diagnosing this from the UI alone will mis-attribute which model did what.
Likely root cause (inference — I cannot see classifier scores)
Since hello! is inert, the trigger is in the static preamble. Two high-density candidates, both present on every request:
Cybersecurity — the security policy block in the Claude Code system prompt:
"authorized security testing, defensive security, CTF challenges… destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion… Dual-use security tools (C2 frameworks, credential testing, exploit development)… pentesting engagements, CTF competitions, security research"
This is the densest concentration of security-threat vocabulary in the payload.
Biology — MCP server names surfaced in the deferred_tools_delta attachment:
IHME_Global_Burden_of_Disease_Historic_Projections,Knowledge_Management_Insights_WHI_Domain
"Global Burden of Disease" is a literal epidemiology phrase; WHI is a health domain.
The disjunction in the banner ("cybersecurity or biology") is consistent with the security preamble supplying the first and the global-health MCP names supplying the second. This phrase-level attribution is a hypothesis — the log's null apiRefusalCategory means I can't confirm which span scored highest.
Deterministic vs. probabilistic
Consistent with deterministic firing: 6/6 first turns tripped, including the content-free hello!, implying the static payload alone scores above threshold. Caveat: I have no observed non-trip and only one fully content-free sample, so I can't formally distinguish "always fires" from "fires with very high probability."
Impact
- The user's chosen model (Fable 5) is silently replaced by Opus 4.8 on virtually every conversation, including trivial ones.
- The classifier appears to score the static system/tooling preamble, not the user's actual message, despite the banner saying "flagged this message" — which is misleading.
- Users with benign-but-keyword-dense MCP tooling (here: global-health / disease-burden servers) or the standard security boilerplate may be permanently downgraded with no actionable explanation (
apiRefusalCategory: null).
Asks
- Exclude the static system prompt / tool & skill schemas /
deferred_tools_deltafrom the input-safety score, or score only the user-authored turn(s) — a barehelloshould never trip. - Populate
apiRefusalCategory/apiRefusalExplanationso the fallback is debuggable instead of a null with a vague banner. - Fix the banner copy: "flagged this message" is inaccurate when the trigger is the preamble, not the message.
- If MCP server names are being scored, treat them as identifiers, not natural-language content.
Attachments / evidence available
- Full session JSONL for the
hello!-only reproduction (fb1f5cb7) and the others on request. - Row tables above are extracted directly from those logs.
Note: the logs contain account-identifying details (connected MCP server names, employer context). Redact before sharing publicly if desired.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗