Claude Platform Docs
MessagesPreserved thinking

Prefix checks on thinking blocks

What the prefix check compares, which models run it, what happens when a request fails it, and how to handle the 400 error and the case with no error.

Each request that sends thinking blocks back to a model with preserved thinking gets a prefix check. A prefix check fails if system, tools, or an earlier message changed after Claude produced a block. It also fails if a thinking block between others was left out or moved.

Failing the prefix check might or might not result in a 400 error, depending on the model, so handle both cases.

Who the prefix check affects

You have nothing to change if one of these is true:

If your requests pass through a library, proxy, or gateway, compare what it sends with what you sent. If you run one, see Libraries, proxies, and gateways for what to pass through.

What must stay the same, and why

Make your integration append-only. Three habits do it:

  1. Send each assistant turn back exactly as the API returned it, including thinking blocks whose text is empty.
  2. Build system and tools once for each conversation, and send that copy every time.
  3. Put every update in a new message at the end, and leave it there.

Each thinking block’s reasoning is about exactly what came before it: system, tools, and every earlier message. That content is the block’s prefix, and it’s what the prefix check compares.

The prefix check is similar to prompt caching, except that a change makes the thinking after it invalid instead of only missing the cache.

Take an app that puts today’s date in its system prompt. At midnight the date changes, and so does the prefix of the thinking already in the conversation. That thinking becomes invalid, and the next request can fail. An append-only app keeps the system prompt as it was, and states the new date in a message at the end.

Add an update at the end
systemtools
Change turn 2’s message
systemtools
Change system or tools
systemtools
your message
thinking, valid
changed or added
thinking, invalid

A change makes only the thinking that already follows it invalid. The thinking before it and the thinking Claude produces afterward stay valid.

If your code changes the system prompt, tools, or history

If your code…What to do
Rebuilds the system prompt with the date, a mode, or instruction filesStop changing system. Append your own instructions as a system message, a role: "system" entry in messages, and state new values in the newest turn. For text that other people can edit, see Text that other people can edit instead.
Adds, removes, or rewords a tool, or gains tools when an MCP server connectsStop changing tools, and add or remove tools in a system message.
Trims, collapses, or summarizes older turns, and keeps the latest ones unchangedLet the API write the summary, or remove the kept turns’ thinking.

Other causes, such as skipping thinking blocks whose text is empty or rebuilding a saved conversation, have fixes of their own: see Change tools or instructions without failing the prefix check. To find yours, have an agent check your code.

What each model does when the prefix check fails

On the Claude API and some cloud platforms, the result of failing the prefix check with prefix_mismatch_behavior unset depends on the model:

Model with preserved thinkingResult
Claude Fable 5.2400 error
Claude Mythos 5.2400 error
Claude Mythos 5.1400 error
Claude Fable 5.1Possible 400 error
Claude Opus 5.5Possible 400 error
Claude Sonnet 5.5Possible 400 error
Claude Haiku 5.5Possible 400 error

Where the table says “Possible 400 error,” there are two cases: a request that fails the prefix check might get a 400 error or no error. When no error is returned, the failing blocks reach the model. Claude reads them like any other input, you’re billed for them, and Claude works from reasoning about content that has since changed.

On those models, you can check which case a request gets, but handle both cases.

Message Batches items that leave prefix_mismatch_behavior unset get no error, and the API can drop the failing blocks.

Handle both cases

For one result on every request, make your integration append-only or set prefix_mismatch_behavior. Otherwise, your code must handle both behaviors.

After a 400 error, send "drop_block" on that conversation’s later requests to recover. It has a cost: see What dropping thinking costs. Don’t send it on all your conversations to hide errors.

Technical details

How the prefix check works

The models with preserved thinking record a fingerprint of each thinking block’s prefix in its signature. The API reference calls the prefix check the conversation check, and the 400 error says that a failing block is bound to a different conversation.

Which models run the prefix check

The prefix check runs on the Claude API and on some cloud platforms. It checks thinking blocks that a model with preserved thinking produced, on requests to one of those models. On the Claude API, other models, such as Claude Opus 5, don’t run it, and it doesn’t cover their blocks.

What counts as a change

  • Each block is checked against its own record. The API compares what’s before a block in this request with what its signature recorded when Claude produced it. Your last request plays no part. The prefix check looks only at content, not at sessions, IDs, or users. The same content in a new request or a fork passes it.
  • The prefix check compares what you sent, not what Claude read. Compaction at a token threshold and context editing change only what Claude reads, so they never count. After a threshold compaction block, the API compares only the messages from that block on, plus system and tools. On-demand compaction has its own conditions: see Conditions for kept thinking to stay valid. For a file sent by URL and, on the Claude API, an MCP tool list, the API compares what it fetched instead.
  • The thinking you send must be an unbroken run. Thinking blocks aren’t compared as content. But each one you send, apart from the first, must have the same previous thinking block as when Claude produced it. So you can leave out the oldest, the newest, or all of them, but not one in between.

A change can be as small as one word of a tool description. Changing, inserting, moving, or deleting an earlier message counts, whatever its role. So does changing text before a thinking block in its own message.

What doesn’t count

This doesn’t countBecause
model, effort, max_tokens, tool_choice, metadata, thinking.display, and other parametersOnly system, tools, and messages are compared
JSON formatting or key orderThe API compares parsed JSON, not its text
cache_control markersThey say where to cache, and aren’t content that Claude reads
A tool with defer_loading: true that nothing has referred toIt isn’t in Claude’s context until something refers to it

Set prefix_mismatch_behavior

The beta header thinking-binding-controls-2026-08-01 adds the setting thinking.block_binding.prefix_mismatch_behavior and the response’s input_transformations array.

ValueIf a block fails the prefix check
"error"The API rejects the request with a 400 error that names the first invalid block
"drop_block"The API drops the invalid blocks, and the request succeeds. You aren’t billed for those blocks, but dropping them can cost more
Not set, or nullDepends on the model

Blocks that fail the prefix check stay invalid on every later request that sends them, while the change is in place. The setting acts on the one request that carries it. With thinking off ("disabled" or "between_tools"), sending it is a 400 error on every model.

The following request sets prefix_mismatch_behavior to "drop_block". On a first turn there’s no thinking to send back, so input_transformations comes back empty:

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-opus-5-5",
    max_tokens=16000,
    thinking={
        "type": "adaptive",
        "block_binding": {"prefix_mismatch_behavior": "drop_block"},
    },
    messages=[
        {
            "role": "user",
            "content": "What is the greatest common divisor of 1071 and 462?",
        }
    ],
    betas=["thinking-binding-controls-2026-08-01"],
)

for block in response.content:
    if block.type == "text":
        print(block.text)

print(f"Input transformations: {len(response.input_transformations or [])}")

Watch production traffic

With prefix_mismatch_behavior unset, a request that fails the prefix check might get no error on some models. The thinking blocks that failed it then reach the model. To find those blocks in production traffic, send the thinking-binding-controls-2026-08-01 beta header, leave the setting unset, and log each response’s input_transformations array. The header adds that array and doesn’t change what the model receives.

Each of those blocks gets its own thinking_mismatch_allowed entry in that array. One entry looks like this:

{
  "input_transformations": [
    {
      "type": "thinking_mismatch_allowed",
      "path": "messages.1.content.0",
      "reason": "prefix_binding_mismatch"
    }
  ]
}

To find the change, see Find what your code changed.

Check which case you get

On some models, a request that fails the prefix check might get the 400 error or no error. This test shows which one a request gets:

  1. Take a conversation that holds a thinking block from one of those models. Change or add the system prompt (system). The block then fails the prefix check, because the system prompt changed after Claude produced the block.
  2. Send it to the same model with the thinking-binding-controls-2026-08-01 beta header, and leave prefix_mismatch_behavior unset.

The response is one of these:

  • A 400 error that says bound to a different conversation: The request got the 400 error.
  • A 200 response whose input_transformations array has thinking_mismatch_allowed entries: The request got no error. Each entry names a thinking block that failed the prefix check and reached Claude.
  • A 200 response whose input_transformations array is empty: No block failed the prefix check, so the test shows nothing. Check that the conversation holds a thinking block from that model.

The result describes that one request and promises nothing about later ones, so write your code to handle both cases.

Next steps

What to send instead of each change your code makes to a conversation.

Recover a failing conversation, find what your code changes, and test that it’s gone.

What preserved thinking is, what a thinking block is tied to, and switching models or accounts.

Was this page helpful?