Fable 5 safety classifier fires model_refusal_fallback on a bare `hello` — scores the static request preamble, not the user message

Open 💬 4 comments Opened Jun 9, 2026 by famulare

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 any ls/read tool ran). So whatever the classifier keys on, it is not the IAQ files.
  • Not the same as #66643. That issue is the claude-api skill trigger self-firing on the /model banner'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

  1. Exclude the static system prompt / tool & skill schemas / deferred_tools_delta from the input-safety score, or score only the user-authored turn(s) — a bare hello should never trip.
  2. Populate apiRefusalCategory / apiRefusalExplanation so the fallback is debuggable instead of a null with a vague banner.
  3. Fix the banner copy: "flagged this message" is inaccurate when the trigger is the preamble, not the message.
  4. 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.

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗