Claude Code 2.1.312, released on 27 October, extends its automatic model switch after a flagged message to model names it doesn't recognise, which is what an Amazon Bedrock application inference profile ARN or a gateway deployment name looks like to it. On 2.1.311 a flagged request on such a name ended with the refusal. On 2.1.312 the session moves to Opus 4.8, built from your region prefix, and the next user turn goes there without trying your profile again.

The switch doesn't know what's behind the name. A recognised Sonnet 5.5 ID falls back to Sonnet 5 at the same list price. An ARN that fronts Sonnet 5.5 falls back to Opus 4.8, which Anthropic lists at $5 per million input tokens and $25 output against Sonnet's $2 and $10. The two-line fix is a modelOverrides entry that tells Claude Code which model the ARN is.

The same release also sends those unrecognised names the shorter system prompt. In my runs the first request on an ARN went from 27,204 characters of system prompt and 46,609 characters of tool definitions on 2.1.311 to 5,917 and 30,326 on 2.1.312. A deployment name with the word "sonnet" in it still got the full prompt.

Two lines in the changelog

The 2.1.312 changelog has two entries about model names Claude Code doesn't recognise. One says Bedrock, Vertex, Foundry and Mantle now use the shorter system prompt for them, "even when they front Sonnet or Haiku", with CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT=0 to keep the full prompt. The other says automatic model switches after a flagged message now also cover them, with CLAUDE_CODE_DISABLE_REFUSAL_FALLBACK=1 to turn that off.

The switch itself isn't new. Fable, Opus 5.5, Sonnet 5.5 and Opus 5 run with safety classifiers, and when one declines a request the API returns a normal response with stop_reason: "refusal" and a category. Claude Code re-runs the request on a fallback model for that category and keeps the session there until you run /model. The documentation for Bedrock, Google Cloud's Agent Platform and Foundry said that only worked when Claude Code could identify the model that refused. An application inference profile ARN, the thing AWS administrators hand out so nobody calls a foundation model directly, wasn't identifiable. That's what changed.

What I ran

I pointed both binaries at a local HTTP server that speaks the Bedrock invoke protocol, with ANTHROPIC_BEDROCK_BASE_URL and CLAUDE_CODE_SKIP_BEDROCK_AUTH=1, and a second mode that speaks the Anthropic protocol through ANTHROPIC_BASE_URL. The server answers most models with a short text reply. For a model name matching a pattern I set per run, it answers with a refusal and a stop_details.category of my choosing, the same shape the real classifiers produce. It logs every request.

Each run was headless, claude -p with --output-format stream-json, in an empty project directory with its own config directory. I ran 34 of them across 2.1.311 and 2.1.312. The session model was an ARN of the form arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123xyz, a plain deployment name, or a recognised ID for comparison.

What a flagged turn does now

On both versions, a cyber refusal on the ARN produced two requests to the ARN. The second one carries a note that the previous response was stopped by a safeguard, a same-model retry Claude Code makes before it considers a switch. On 2.1.311 the turn then ended with the refusal text and exit code 1.

On 2.1.312 a third request went to us.anthropic.claude-opus-4-8. The stream carried a model_refusal_fallback event with original_model set to the ARN, fallback_model set to the Opus ID, direction: retry and scope: session. When I sent a second user message in the same session, it went to Opus 4.8 directly, and the ARN was never called again.

The Opus ID is constructed from the region. With AWS_REGION=eu-central-1 the fallback was eu.anthropic.claude-opus-4-8. My fake inference profile listing was empty, so Claude Code didn't find the ID in the account; it built it and sent it anyway. Whether that cross-region profile is enabled in your account, and whether your policy allows it, is now a question the session answers for you.

The category decides the target, and the mapping for an unrecognised name is the one used for Fable:

Refusal category 2.1.311 2.1.312
cyber refusal stands us.anthropic.claude-opus-4-8
bio refusal stands us.anthropic.claude-opus-5
frontier_llm refusal stands us.anthropic.claude-opus-5
general_harms refusal stands refusal stands

A deployment named my-sonnet-deployment behaved the same way as the ARN: Opus 4.8. On the Anthropic protocol, a gateway model named my-gateway-claude fell back to the literal ID claude-opus-4-8, which your gateway has to know how to serve. For comparison, a session on the recognised ID claude-sonnet-5-5 fell back to claude-sonnet-5 on both versions, as documented.

If the fallback model refuses too, the turn ends. With everything refusing, the ARN got two requests, Opus 4.8 got one, and the result was "Opus 4.8 can't help with this. Start a new session to continue."

The prompt change

The second changelog line is easier to measure. The first request of a session on the ARN sent 27,204 characters of system prompt and 46,609 characters of tool definitions on 2.1.311. On 2.1.312 it sent 5,917 and 30,326. Setting CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT=0 on 2.1.312 brought back 27,204 and 46,609.

The recognised IDs claude-sonnet-5-5 and us.anthropic.claude-sonnet-5-5 sent 5,968 and 30,326 by default, so for a Sonnet 5.5 deployment the ARN now gets the same size of prompt the model would get under its own name. The exception is a name Claude Code can partly read. my-sonnet-deployment on Bedrock sent 27,102 and 46,609 on 2.1.312, because the prompt selection looks for family words in the model string and treats "sonnet" as a model that wants the full prompt.

The controls, tested

Six settings changed the outcome, and one that looks like it should didn't.

  • modelOverrides: {"claude-sonnet-5-5": "<ARN>"} in settings made the ARN a recognised Sonnet 5.5. The fallback became us.anthropic.claude-sonnet-5, the system prompt became Sonnet's 5,968 characters, and starting the session with either the Anthropic ID or the ARN gave the same result. This is the fix if you want the switch and want it to land on the right family.
  • ANTHROPIC_DEFAULT_OPUS_MODEL: set to a second ARN, the fallback went to that ARN instead of the constructed cross-region ID. This is the fix if every call has to stay inside your profiles.
  • availableModels: an allowlist containing only the ARN blocked the switch. The refusal stood, as documented.
  • switchModelsOnFlag: false: in a headless session the refusal stood, as documented. Interactive sessions get a prompt instead.
  • CLAUDE_CODE_DISABLE_REFUSAL_FALLBACK=1: the refusal stood, but the ARN still received the same-model retry, two requests per flagged turn.
  • CLAUDE_CODE_DISABLE_REFUSAL_RETRY=1 on top of that: one request, one refusal, done. This variable is in the binary and not in the environment variable reference; a documentation issue from 17 October asked about it.

The one that didn't: ANTHROPIC_DEFAULT_SONNET_MODEL set to the ARN, with the session started as sonnet. The request went to the ARN, the fallback was still Opus 4.8, and the prompt was still the unrecognised 5,917. The pin resolves the alias; it doesn't tell the fallback logic what the model is. Only modelOverrides did that.

What to do

If you run Claude Code on Bedrock, Vertex or Foundry through administrator-managed profiles, decide before updating to 2.1.312 whether a flagged request may leave the profile. The default now is yes, to an Opus 4.8 or Opus 5 ID in your region that you didn't configure, for the rest of the session.

If the answer is no, put the profile ARNs in availableModels or set CLAUDE_CODE_DISABLE_REFUSAL_FALLBACK=1 in managed settings, and add CLAUDE_CODE_DISABLE_REFUSAL_RETRY=1 if one refusal should mean one request. If the answer is yes, map each ARN to its Anthropic model ID in modelOverrides so a Sonnet profile falls back to Sonnet, and set ANTHROPIC_DEFAULT_OPUS_MODEL to an Opus profile you own.

Either way, watch the modelUsage field in headless results and the model_refusal_fallback events in stream-json output. In my runs the ARN and the Opus ID each appear there with their own token counts. A session that quietly costs 2.5 times more per token after one flagged message will show up in that field before it shows up on the bill.

All of this ran against a fake endpoint on my laptop. The refusals were ones I scripted, and no AWS account was involved, so I can say what Claude Code sends and not how often a real classifier fires on your repository.