Refusals now return HTTP OK — and your try-except misses them

v0.108–v0.109 adds refusal responses at HTTP 200, a new stop_reason literal, and removes retired model strings from typed enums.

Refusals now return HTTP OK — and your try-except misses them
Share

Anthropic's official Python SDK shipped four releases in one week — three of them within eight hours on June 9 — and none carries a formal "breaking" label. That label is missing because the library is pre-1.0, not because your code is safe.

Is v0.109.2 Safe to Adopt? The Annotated Changelog

Mostly yes — but read the diff before you bump. The anthropic Python SDK is on the 0.x track, so every entry is tagged Features, Bug Fixes, or Chores rather than "Breaking" . Yet the June 9–15 window introduced three distinct ways to silently mishandle a production response. Test against each before upgrading; the absence of a tag is a versioning artifact, not a compatibility guarantee.

Quick Answer: v0.109.2 is the current PyPI release and is adoptable, but it strips retired model identifiers from typed enums — so codebases still referencing deprecated model strings can hit mypy or runtime errors. Three releases on June 9 (v0.108.0–v0.109.1) add refusal and fallback behavior you must handle explicitly.

Here is the annotated sequence, all confirmed on both GitHub Releases and PyPI :

VersionReleased (UTC)TypeWhat it changes
v0.108.0Jun 9, 16:37FeaturesAdds claude-fable-5 and claude-mythos-5, a server-side refusal fallback path, and a client-side retry wrapper
v0.109.0Jun 9, 20:04FeaturesManaged Agents lifecycle interface; configured endpoints grow 106 → 116; adds beta.deployments and beta.deployment_runs
v0.109.1Jun 9, 23:55Bug FixesAdds frontier_llm to the RefusalStopDetails.category Literal union
v0.109.2Jun 15ChoresRemoves retired model identifiers from typed enums and lists

v0.108.0 (commit 6b76649) is the consequential one. It exports BetaFallbackState, BetaRefusalFallbackMiddleware, RetryableError, OverloadedError, and RequestTooLargeError, and ships an examples/fallbacks.py for both server-side and client-side fallback on refusal . The point is not new types — it is new model behavior those types describe.

Three places where an upgrade can break quietly:

  • HTTP-200 refusals: Fable 5 refusals return 200 with stop_reason: refusal, not an error your try/except will catch .
  • A fourth refusal category: v0.109.1 adds frontier_llm to the category union — type-breaking for exhaustive matchers written against v0.108.0 .
  • Stripped model strings: v0.109.2 removes retired identifiers, the entry most likely to produce immediate mypy or runtime failures .

The following sections annotate each of these line by line — what the diff says, and what it does to code you already shipped .

When an HTTP Success Response Carries a Refusal Category

Refusals now return HTTP OK — and your try-except misses them

A Fable 5 refusal is not an exception — it is a successful response. The API returns HTTP 200 with stop_reason: "refusal" and a stop_details.category field naming the classifier that declined, rather than raising an error your transport layer would catch . Four categories are defined: cyber, bio, frontier_llm, and reasoning_extraction .

This is the migration hazard. Any path that watches only for APIStatusError and assumes every 200 ends with end_turn or max_tokens will route the refusal straight into your normal success branch — empty or truncated output, no signal surfaced. A try/except around the call never fires, because nothing was thrown .

"Claude Fable 5 refusals return HTTP 200 with stop_reason: refusal; the response reports which classifier declined the request." — Anthropic API documentation (source: commit 6b76649)

The billing model softens the cost of getting this wrong. Refused calls are not billed before output is generated, and the server-side fallback credit avoids paying prompt-cache costs twice when an automatic retry to Claude Opus 4.8 fires . Anthropic documents that this fallback triggers in under 5% of sessions on average at launch — rare enough that untested refusal handling can ship and look healthy for a long time .

Two interface constraints come bundled with the new models and will surface as errors if your client carries assumptions from older Claude versions:

  • Adaptive thinking is always on. Sending thinking.type: "disabled" raises a validation error — there is no thinking-off path on Fable 5 or Mythos 5 .
  • Raw chain-of-thought is never returned. You receive summarized blocks only, gated by thinking.display; code that parsed raw reasoning text will read nothing .

One audience is excluded outright. Fable 5 and Mythos 5 are Covered Models with 30-day data retention and are not offered under zero data retention, so ZDR-constrained callers cannot adopt them regardless of how their refusal handling is written . For everyone else, the practical fix is small but mandatory: branch on stop_reason before treating any 200 as a completed turn, and handle refusal as a first-class outcome.

A Fourth Refusal Category in v0.109.1 — Impact on Strict Type Unions

v0.109.1, released on June 9, 2026, is a one-line type change with outsized blast radius: it widens the refusal category union from Literal['cyber', 'bio', 'reasoning_extraction'] to Literal['cyber', 'bio', 'frontier_llm', 'reasoning_extraction'] on both RefusalStopDetails.category and BetaRefusalStopDetails.category . The new frontier_llm value tells you a request was declined by the frontier-model classifier — the same safeguard that routes some sessions to Opus 4.8. If your refusal handler predates this release, it does not know that value exists.

That gap surfaces differently depending on how strict your code is:

  • Exhaustive match statements written against the three-value union fall through to the default arm — a silent miss, since Python does not enforce exhaustiveness at runtime.
  • TypeGuard branches that enumerate the old categories return False for frontier_llm, misrouting a genuine refusal as an unknown response.
  • Pydantic discriminated unions generated from the v0.108 schema reject the value outright with a ValidationError — a hard failure at parse time.

The same-failure-different-symptom split is the trap. Loosely typed code drops the case quietly; strictly typed code raises. Neither is what you want in a refusal-aware path.

A subtler hazard: mypy will not flag the mismatch for you if a stale schema object is cached in your environment. Pydantic models generated from the v0.108 schema must be regenerated after the bump — static analysis sees whatever union your local artifact still encodes, not the one shipped in the SDK . Upgrading the package without regenerating leaves you type-checking against the wrong contract.

As Simon Willison has put it about LLM tooling, "the documentation is the source code" — and here the commit history is where the real contract lives, not the patch-level version string. A change tagged as a bug fix can still invalidate every assumption baked into your stop-details parsing.

The fix is mechanical but must land before you deploy any handler that branches on refusal:

  • Pin anthropic to ≥v0.109.1 .
  • Add frontier_llm to every exhaustive match arm and TypeGuard branch.
  • Regenerate Pydantic models so the category Literal includes all four values.

How v0.109.2 Strips Deprecated Identifiers from Typed Enums

Refusals now return HTTP OK — and your try-except misses them

v0.109.2, released June 15, 2026, removes retired model identifier strings from the SDK's typed Literal enums . The breakage is not theoretical: a caller still passing a removed name fails one of two ways. If the string flows through a typed parameter, mypy or pyright raises a type error against the narrowed union. If it is resolved dynamically, you get a runtime lookup failure when the identifier no longer exists. This is the change in the June 9–15 burst most likely to break working code.

The deprecation did not arrive without warning. Claude Opus 4.1 was already marked deprecated back in v0.106.0, and v0.109.2 completes the removal . Teams that read the earlier deprecation notice and migrated lose nothing on the bump. Teams that ignored it are now broken — the grace window between "deprecated" and "removed" has closed.

ReleaseDateStatus of retired model IDs
v0.106.0earlierClaude Opus 4.1 marked deprecated (still usable)
v0.109.22026-06-15Retired identifiers removed from typed enums

One practical limitation: the release notes do not enumerate every removed identifier . To find your exposure, grep for model= string literals across your codebase and cross-check each against the v0.109.2 CHANGELOG diff rather than trusting that the upgrade is silent.

A safe migration order avoids surprises at deploy time:

  • Audit first. Grep every model= literal and config-driven model string, including values loaded from environment variables and feature flags.
  • Replace before bumping. Swap each deprecated identifier for its current equivalent while still on your pinned version, so behavior is unchanged.
  • Bump, then verify. Upgrade to v0.109.2 and run mypy in strict mode to surface any remaining violations before deployment.
  • Watch dynamic paths. Strict typing will not catch model names assembled at runtime — add a smoke test that issues one real request per model your app references.

The pattern across this release window holds here too: a low patch number does not mean a low blast radius. Treat v0.109.2 as a removal, not a chore.

v0.109.0 Extends the Library With a Lifecycle Control Interface

Refusals now return HTTP OK — and your try-except misses them

v0.109.0, shipped June 9, 2026 at 20:04 UTC , is the one release in this window with no blast radius for ordinary Messages callers. It adds Managed Agents deployment support and environment-variable credentials . If you only call messages.create, nothing here touches you. If you run agents on Anthropic's beta deployment API, it replaces hand-written HTTP with typed Python.

The commit lifts configured_endpoints from 106 to 116 — ten new lifecycle verbs — and introduces beta.deployments and beta.deployment_runs resources . The split maps cleanly to lifecycle control:

  • beta.deployments — create, retrieve, update, list, archive, pause, run, and unpause.
  • beta.deployment_runs — retrieve and list.

Eight verbs on the deployment object, two on its runs. The release also lands the supporting type surface: schedule, resource, vault, networking, and environment-variable-credential types that together describe the full Managed Agents configuration contract . That is enough to provision, schedule, and tear down an agent deployment without leaving the SDK.

Treat this contract as in motion, not settled. v0.107.0, three days earlier on June 6, had already shipped what the changelog calls:

"small updates to Managed Agents types" — anthropic-sdk-python CHANGELOG (source: CHANGELOG.md)

So the v0.107-to-v0.109 span shows the Managed Agents shape changing across consecutive minors . If your tooling depends on these types staying stable, pin to a specific minor rather than tracking the head of the 0.x line. The release notes do not document constructor options or retry semantics beyond the type definitions, so read commit 47633bf directly when you need exact shapes .

v0.108.0 Additions Suspended After a Government Order

The v0.108.0 fallback machinery now ships against models you cannot call. On June 12, 2026 at 5:21 PM ET, a US government export-control directive required suspending Claude Fable 5 and Mythos 5 access for any foreign national — including foreign-national Anthropic employees . Rather than build selective per-user gating, Anthropic disabled both models for all customers, leaving every other model unaffected .

This is the practical sting for anyone who upgraded for the new fallback path. The SDK code — BetaFallbackState, BetaRefusalFallbackMiddleware, and examples/fallbacks.py — is present in the installed library, but the API will reject calls to these model IDs . You can import and instantiate the middleware; you just have nothing live to point it at.

Anthropic did not frame this as agreement. The launch article was updated on June 12 to note the suspension , and Anthropic stated publicly that the directive lacked specific details, that it understood the concern to be a narrow, non-universal jailbreak, and that it disagreed a recall was justified.

"We disagree that a recall is warranted and understand the concern to be a narrow, non-universal jailbreak," — Anthropic, suspension statement (source: commit d3a806b).

The government's underlying evidence is not public in the reviewed sources . Two constraints persist regardless of any resume date:

  • Mythos 5 never reached general availability. It was limited to Project Glasswing at launch, so most teams never had production access to lose .
  • ZDR exclusion stands. Both variants are Covered Models with 30-day retention and are not offered under zero data retention .

The concrete takeaway as of June 19, 2026: treat v0.108.0's refusal-fallback features as code you can adopt but not yet exercise. The HTTP-200 refusal handling and the frontier_llm category are worth wiring in now so you are ready if access returns — but do not pin a production path to claude-fable-5 until Anthropic confirms the suspension is lifted .

Frequently asked questions

Will my existing try-except block catch a Fable 5 refusal?

No. A Claude Fable 5 refusal returns HTTP 200 with stop_reason: "refusal" inside a normal Message response — not an exception . So APIStatusError and similar handlers never fire. Check message.stop_reason after every call, then read message.stop_details.category to identify which classifier declined.

Which model strings does v0.109.2 remove, and how do I find them in my codebase?

v0.109.2 (June 15, 2026) removed retired models from the API and SDKs . The notes do not list every removed name, but Claude Opus 4.1 — deprecated since v0.106.0 — is confirmed gone . Grep for model= string literals and run mypy in strict mode against v0.109.2; missing Literal members surface as type errors before runtime.

Can I call claude-fable-5 in production as of June 2026?

No. A US government export-control directive issued at 5:21pm ET on June 12, 2026 led Anthropic to suspend all Claude Fable 5 and Claude Mythos 5 access for every customer, while leaving other models unaffected . The SDK code exists, but the API rejects these model IDs while suspended. Monitor Anthropic's status updates for reinstatement.

Do I need to upgrade to v0.109.0 if I am not using Managed Agents?

Not for that release alone. v0.109.0 only adds Managed Agents deployment APIs and environment-variable credentials . But continuing through v0.109.1 (the frontier_llm refusal category) and v0.109.2 (retired-identifier removal) is independently recommended. The minimum safe pin for refusal-aware code is ≥v0.109.1 .

How do I correctly handle a refusal response in v0.108.0 and later?

After each API call, check message.stop_reason. On "refusal", read message.stop_details.category — one of cyber, bio, frontier_llm, or reasoning_extraction — to decide whether to retry with a fallback model or surface the refusal to the caller . Refused requests are not billed before output is generated .