One read of Claude Developer Platformapi-20261112T160709Z
52 pages moved out of 904 read.
What this read moved
1-25 of 52, page 1 of 3This capture is too large to show at once. Changes 1-25 of 52 are below, significant first; the rest are on the following screens.
about-claude/models/optimizing-for-cost-and-intelligence Changed · +6 / -6 lines
agents-and-tools/tool-use/advisor-tool Changed · +8 / -9 lines
agents-and-tools/tool-use/computer-use-tool Changed · +10 / -12 lines
agents-and-tools/tool-use/tool-use-with-prompt-caching Changed · +10 / -10 lines
api/errors Changed · +16 / -13 lines
api/rate-limits Changed · +42 / -22 lines
### Ramp limits
build-with-claude/batch-processing Changed · +18 / -18 lines
build-with-claude/cache-diagnostics Changed · +104 / -71 lines
build-with-claude/compaction-on-demand Changed · +21 / -18 lines
build-with-claude/compaction-thinking-blocks Changed · +43 / -41 lines
## When your code writes the summary
build-with-claude/context-editing Changed · +5 / -3 lines
build-with-claude/context-windows Changed · +14 / -4 lines
build-with-claude/effort Changed · +40 / -18 lines
build-with-claude/effort-per-message Changed · +19 / -20 lines
build-with-claude/extended-thinking Changed · +6 / -6 lines
build-with-claude/message-threads Changed · +3 / -3 lines
build-with-claude/mid-conversation-system-messages Changed · +96 / -63 lines
build-with-claude/preserved-thinking/change-tools-or-instructions New page · 295 lines, new page
## Send assistant turns back exactly ## Change instructions ## Text that other people can edit ## Update the date, files, or other context ## Add, remove, or turn off tools ## Trim or summarize older turns ## Save, resume, or fork a conversation ## Remove secrets, personal data, or injected text ## Technical details ### Ways code changes assistant turns ### Leave out thinking ### Your instructions in a tool loop ### Write the tool call yourself ### Your user’s words, and updated values, in a tool loop ### Models that don’t accept system messages ### Send a reminder for one turn ### Which block and beta header a tool change needs ### Tools with `defer_loading`, and the 400 error that names it ### Shorten or clear old results and images ### Files you send by URL ### If your own code summarizes or cuts older turns ### Libraries, proxies, and gateways ## Next steps
A whole new page. There's nothing to diff it against, so here is what it says.
---
title: Change tools or instructions without failing the prefix check
url: https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions
description: Send an instruction, tool, reminder, or summary, resume a saved conversation, or pass on other people's requests, so that Claude keeps its earlier thinking.
featureMetadata:
status: stable
supportedModels:
- claude-fable-5-2
- claude-mythos-5-2
- claude-fable-5-1
- claude-mythos-5-1
- claude-opus-5-5
- claude-sonnet-5-5
- claude-haiku-5-5
supportedPlatforms:
Claude API: stable
Amazon Bedrock: stable
Amazon Bedrock (bedrock-runtime): stable
Claude Platform on AWS: stable
Claude Models on Google Cloud (Anthropic managed): stable
Claude Models on Google Cloud (Google managed): stable
Microsoft Foundry: stable
---
To update tools, instructions, or other context, leave everything you sent as it was, and add the update at the end, where it stays. On models with preserved thinking, changing what you sent can make the thinking after it invalid, and [the request can fail](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#outcomes).
The models with preserved thinking are Claude Fable 5.2, Claude Mythos 5.2, Claude Fable 5.1, Claude Mythos 5.1, Claude Opus 5.5, Claude Sonnet 5.5, and Claude Haiku 5.5.
## Send assistant turns back exactly
Send back the whole `content` array of each response, in order, including thinking blocks whose text is empty. A block with empty text is complete, because its `signature` holds the reasoning, encrypted. A turn that holds a [`fallback` block](https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback#continuing-the-conversation) has its own rule.
[Serializers, filters, and stream parsers](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#append-assistant-turns-exactly-as-returned-details) can break this.
## Change instructions
Keep `system` the same on every request. Put your own instructions in a `role: "system"` message at the end, and send one only when something has changed. Claude treats it as an instruction from you, the operator. Leave it for the rest of the conversation, because deleting or rewording it later makes the thinking after it invalid. [Your user’s words](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#user-words) have their own rule.
A reminder that must come with every turn is different: [send one that clears itself](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#per-turn-reminders).
The system message goes after the newest user message, even one with [tool results](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#new-instructions-details). A system message with text or tool blocks gets a 400 error right after Claude’s turn or right before a user message. So when Claude’s turn is last, wait until the next user message is in place.
A system message doesn’t guarantee that Claude follows an instruction, so apply limits such as read-only mode in the code that runs your tools. Some models [don’t accept system messages](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages).
## Text that other people can edit
Ask whether your user would expect Claude to trust a file, such as an `AGENTS.md` or `CLAUDE.md` that other people can edit, or stored memory. If not, send its text only in a `tool_result` block, the first version included. For why, see [Indirect prompt injection](https://platform.claude.com/docs/en/test-and-evaluate/strengthen-guardrails/mitigate-jailbreaks#indirect-prompt-injection).
Don’t put such text in a system message or in the text of a user message, and in new conversations, don’t put it in `system`.
When you load such a file or it changes, tell Claude to read it with a tool, and don’t include its text. Or, after the next user message, append the tool call and its result yourself: an `assistant` message with a `tool_use` block, then a `user` message whose `tool_result` block holds the text. For the steps, see [Write the tool call yourself](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#write-the-tool-call).
A file that your user expects Claude to follow can go in a user message. An instruction file that only you control can go in a system message: load it once, and append each updated version.
## Update the date, files, or other context
Build `system` and the first message once, and send them unchanged on every request.
Put an updated value in its own text block of the user message you’re about to send, [after any tool results](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#user-words):
```json
{
"role": "user",
"content": [
{ "type": "text", "text": "Environment update: the current branch is now release-2." },
{ "type": "text", "text": "Run the tests again." }
]
}
```
State it only when it changes, and leave it there.
Store any context that your code adds to a message with that message. Otherwise the next request sends something different before the same thinking.
## Add, remove, or turn off tools
Keep `tools` the same on every request. Adding, changing, or removing a tool there, other than [a deferred one](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#deferred-tools), makes the thinking already in the conversation invalid, because `tools` comes before all of it. One reworded sentence in a description is enough.
To add or remove a tool, append a system message with a `tool_addition` or `tool_removal` block and [the beta header for your case](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#tool-changes-details). Do the same when an MCP server or plugin connects.
To turn tools off for one request, set `tool_choice: {"type": "none"}`, and keep `tools` as it was.
## Trim or summarize older turns
To keep recent turns with their thinking, let the API write the summary with [on-demand compaction](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#request-a-summary), under the [conditions for kept thinking](https://platform.claude.com/docs/en/build-with-claude/compaction-thinking-blocks#conditions-for-kept-thinking-to-stay-valid). Start `messages` with the compaction block it returns, and remove the messages it summarizes.
If your own code writes the summary, or cuts older turns without one, [remove the kept turns’ thinking or send `"drop_block"`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#server-side-trimming-details). Each new cut makes the kept turns’ thinking invalid again, so cut rarely, in large steps. To clear old tool results instead, [use context editing](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#clear-old-content).
## Save, resume, or fork a conversation
Store what you sent and what you received, and send exactly that. A resumed conversation is an ordinary next request: `system`, `tools`, and earlier `messages` must have the values they had. For a fork, put the branch’s instruction in its first new user message, or in a system message after that. Thinking can’t move to a conversation with a different history: start that conversation from a summary instead.
A release that changes your system prompt or tools is a change in conversations already running. The fix that makes your code append-only can be one too. Keep sending each running conversation the `system` and `tools` of its last successful request, because its newest thinking was produced after that version. Going back to an earlier version would make that thinking invalid.
Use the updated `system` and `tools` in new conversations. If the release removes a tool or changes an instruction for safety, apply it to running conversations too. Append [a `tool_removal` block](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#tool-changes) or [a system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#new-instructions).
## Remove secrets, personal data, or injected text
Safety and privacy come before keeping thinking valid. If a message holds a secret or personal data you must delete, remove that text. Remove it from every message and summary that repeats it, too.
For a tool result with injected instructions, replace its content with a short note such as “Removed.” Keep the `tool_result` block itself, because its `tool_use` needs one. If a [compaction block](https://platform.claude.com/docs/en/build-with-claude/compaction) holds the text, delete the block, and start from your own summary.
Then delete every `thinking` and `redacted_thinking` block after the first place the text appeared. If it was in `system` or `tools`, that’s all of them. Each block can repeat the text, and you can’t read or edit what’s inside. Sending `"drop_block"` isn’t enough here, because it leaves those blocks in your stored history. The conversation carries on without that reasoning.
After an injected result, later turns might already have acted on it. Check the tool calls made since, and undo what you need to.
## Technical details
### Ways code changes assistant turns
* A serializer drops block types it doesn’t know, or reorders, merges, or splits blocks. That changes what came before every later block.
* Code skips thinking blocks that look empty. If it skips all of them, Claude loses that reasoning. If it skips only some, the gap makes the thinking after it invalid.
* A stream parser of your own drops a thinking block that got no text. The SDK’s stream helpers keep it.
### Leave out thinking
Claude loses the reasoning you leave out. Leave out the oldest blocks, the newest, or all of them, but none in between. Each block records the one it was produced after, so a gap makes the blocks after it invalid. Thinking blocks with no other block between them in a turn are checked together, so leave out all of them or none. Once a block is out, keep it out.
While a tool call is open, [send that turn’s thinking back](https://platform.claude.com/docs/en/build-with-claude/thinking#preserving-thinking-blocks), so that Claude finishes the round with its reasoning.
### Your instructions in a tool loop
The messages go in this order:
```json
[
{
"role": "assistant",
"content": [
{ "type": "thinking", "thinking": "", "signature": "..." },
{
"type": "tool_use",
"id": "toolu_01",
"name": "run_tests",
"input": { "path": "tests/" }
}
]
},
{
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "2 failed, 48 passed" }
]
},
{
"role": "system",
"content": "Plan mode is off. You can edit files."
}
]
```
### Write the tool call yourself
Declare a tool that reads files on the first request, or [add it later in a system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#tool-changes), and have your code answer Claude’s calls to it. After the next user message, append an `assistant` message with a `tool_use` block for that tool. Directly after it, append a `user` message whose `tool_result` block has the matching `tool_use_id` and holds the file’s text. Claude answers after that:
```json
[
{ "role": "user", "content": "Run the tests again." },
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_app_agents_md_1",
"name": "read_file",
"input": { "path": "AGENTS.md" }
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_app_agents_md_1",
"content": "The file's text"
}
]
}
]
```
Leave both in place, and append a new pair, with a new `id`, for each new version.
### Your user’s words, and updated values, in a tool loop
If you pass on what your user typed while Claude was working, add it before you send the user message that carries the `tool_result` blocks. Make it a text block after the last `tool_result`.
A value that comes from you can go in a system message instead. After tool results, Claude Sonnet 5.5 and Claude Haiku 5.5 can ignore a user’s words in a system message as a prompt injection. Claude Sonnet 5.5 can do the same with your own instructions and reminders if they come after every tool result.
The user message with the tool results must hold only `tool_result` blocks when the turn also called a [server tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls) that has no result yet, or used programmatic tool calling. Then add the text to the next user message.
### Models that don’t accept system messages
On the Claude API, every model with preserved thinking accepts system messages. For a model that doesn’t, see [When a model doesn’t accept system messages](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#unchosen-switches-details).
### Send a reminder for one turn
Send a reminder as a [turn-scoped system message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#turn-scoped-system-messages): set `clear_at: "next_user_message"`, and send the beta header `mid-conversation-system-clear-at-2026-08-21`. It goes after the user message that carries the `tool_result` blocks:
```json
{
"role": "system",
"clear_at": "next_user_message",
"content": "Run the linter before you report that a task is done."
}
```
At the next user message, even one with only `tool_result` blocks, the API clears it, so Claude stops reading it. On Claude Sonnet 5.5, [don’t send one after every tool result](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#user-words). Keep every reminder in your `messages`, cleared or not, unless it [holds something you must remove](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#remove-sensitive-content). Removing or rewording one makes the thinking after it invalid.
Without the header, send a plain system message and leave it in place. It never clears, so reminders pile up. On a model that takes no system messages, use a text block in the user message.
### Which block and beta header a tool change needs
For the platforms that take each header, see [Mid-conversation system messages and tool changes](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages).
* **If you know every tool at the start:** Declare them all on the first request, with `defer_loading: true` on those you offer later. Then name the tool in a `tool_addition` or `tool_removal` block of a system message. Beta header: `inline-tools-2026-09-15` or `mid-conversation-tool-changes-2026-07-01`.
* **If you learn of a tool later, or have one whose schema changes:** Define the tool in a `tool_addition` block of a system message. Beta header: `inline-tools-2026-09-15`.
* **If you learn of a tool later, on a platform without the `inline-tools-2026-09-15` header:** Add the tool to the end of `tools` with `defer_loading: true`, which isn’t a change, then offer it by name. Beta header: `mid-conversation-tool-changes-2026-07-01`.
* **If you connect an MCP server through the MCP connector late, on the Claude API:** Define its `mcp_toolset` in a `tool_addition` block. Beta headers: `inline-tools-2026-09-15` and `mcp-client-2026-09-15`.
If an MCP server’s tool list can change, see [Pin an MCP server’s tool list](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector#pin-mcp-tool-list).
This system message offers a declared tool by name and defines another, which needs `inline-tools-2026-09-15`:
```json
{
"role": "system",
"content": [
{
"type": "tool_addition",
"tool": { "type": "tool_reference", "name": "search_tickets" }
},
{
"type": "tool_addition",
"tool": {
"type": "tool_definition",
"definition": {
"name": "get_weather",
"description": "Get the current weather in a city.",
"input_schema": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}
}
]
}
```
### Tools with `defer_loading`, and the 400 error that names it
A tool with `defer_loading: true` stays out of Claude’s context and the prefix check until a message or tool search result refers to it. Until then, adding, updating, or removing it isn’t a change. Afterward it is. Turning `defer_loading` on or off counts as changing `tools`, too.
Declare at least one tool that isn’t deferred, or the request can get a 400 error that names `defer_loading`.
### Shorten or clear old results and images
Shorten or downscale a tool result or image before you first send it. If your code clears, shortens, or re-encodes something it already sent, the thinking after it becomes invalid. To clear old tool results later, including any images inside them, use context editing. Send `context_management` with an edit of type `clear_tool_uses_20250919`, and the beta header `context-management-2025-06-27`. For the full request and its options, see [Context editing](https://platform.claude.com/docs/en/build-with-claude/context-editing#tool-result-clearing).
### Files you send by URL
For a file by URL, the API compares the bytes it fetched. So when a signed URL expires, a fresh one for the same file is fine. If the content behind a URL can change, send a `file_id` or base64 from the first request.
### If your own code summarizes or cuts older turns
After your own summary or cut, a kept turn’s thinking fails the [prefix check](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#prefix-check), because the messages it followed are gone. Remove that thinking, or [send `"drop_block"`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#preserved-thinking-controls). Claude still reads the turn without it.
### Libraries, proxies, and gateways
If other people’s requests pass through your code, your rewrites change their history, and they might not be able to see or fix those changes. Don’t hide the 400 error. If you recover for the caller, say so, because their history still holds the change.
* Pass `anthropic-beta` values and `thinking.block_binding` through as sent. If your gateway allows only known fields, add `thinking.block_binding` and the header value `thinking-binding-controls-2026-08-01`, or your users can’t choose `"drop_block"`.
* Don’t remove the beta header and forward the setting. That request gets a 400 error.
* Return thinking blocks whose text is empty, and the `input_transformations` array, to the caller.
* Leave a `role: "system"` message where it is. Moving it into `system` makes the thinking already in the conversation invalid.
* Test with [`prefix_mismatch_behavior`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#preserved-thinking-controls) set to `"error"`, which gives the error on every model with preserved thinking.
## Next steps
<CardGroup cols={2}>
<Card title="Preserved thinking" icon="book" href="https://platform.claude.com/docs/en/build-with-claude/preserved-thinking">
What preserved thinking is, what a thinking block is tied to, and switching models or accounts.
</Card>
<Card title="Fix a thinking block bound to a different conversation" icon="wrench" href="https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation">
Recover a failing conversation, and see what dropping thinking costs.
</Card>
<Card title="Mid-conversation system messages and tool changes" icon="message" href="https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages">
Where system messages and tool changes go, and which models and platforms take them.
</Card>
</CardGroup>
build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation New page · 543 lines, new page
## Error: The block is bound to a different conversation ## What dropping thinking costs ## Find what your code changed ## Test your code for prefix check failures ## Watch production, and find reasoning lost with no error ## Technical details ### What else the error message can say ### The error still asks for the header, or says `Extra inputs are not permitted` ### Remove the invalid thinking by hand ### Errors that look like this one ### When you can stop sending `"drop_block"` ### Handle the error in code ### You have no error, or one that doesn’t say what changed ### Five steps for a test that can fail, and a program that runs them ### What each `input_transformations` entry means, and which are yours to fix ### Batches and token counting ## Next steps
A whole new page. There's nothing to diff it against, so here is what it says.
---
title: Fix a thinking block bound to a different conversation
url: https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation
description: Get a conversation going again after the 400 error that says a thinking block is bound to a different conversation, find what your code changed, and test that the change is gone.
featureMetadata:
status: stable
supportedModels:
- claude-fable-5-2
- claude-mythos-5-2
- claude-fable-5-1
- claude-mythos-5-1
- claude-opus-5-5
- claude-sonnet-5-5
- claude-haiku-5-5
supportedPlatforms:
Claude API: stable
Amazon Bedrock: stable
Amazon Bedrock (bedrock-runtime): stable
Claude Platform on AWS: stable
Claude Models on Google Cloud (Anthropic managed): stable
Claude Models on Google Cloud (Google managed): stable
Microsoft Foundry: stable
---
Sending the same request again can’t work, because your history still holds what your code changed. Send `"drop_block"` to [stop the errors now](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#prefix-mismatch-error). The long-term fix is to make your code [append-only](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#prefix-check).
## Error: The block is bound to a different conversation
The thinking block failed the [prefix check](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#prefix-check): something before it changed after Claude produced it (see [What counts as a change](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#what-counts-as-an-edit)), or a thinking block between others was left out or moved. The error looks like this, and can go on to [name what changed](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#prefix-mismatch-error-details):
```text wrap
messages.1.content.0: Invalid `signature` in `thinking` block. The block is bound to a different conversation. Remove the block, or set `thinking.block_binding.prefix_mismatch_behavior` to "drop_block".
```
“A different conversation” means different content before the block: the [prefix check](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#prefix-check) doesn’t compare sessions or users. The error message says to remove the block, but [removing only that block isn’t enough](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#remove-by-hand). [Other errors](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#similar-errors) can look similar.
If the app isn’t yours, start a new conversation and tell its maintainer. If you [removed a secret, personal data, or injected text](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#remove-sensitive-content), or your requests [turn thinking off](https://platform.claude.com/docs/en/build-with-claude/thinking#turning-thinking-off), [remove the thinking by hand](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#remove-by-hand) instead of step 1.
<Steps>
<Step title="Stop the errors now">
Set `thinking.block_binding.prefix_mismatch_behavior` to `"drop_block"`, and send the [beta header](https://platform.claude.com/docs/en/api/beta-headers#beta-features-on-other-platforms) `anthropic-beta: thinking-binding-controls-2026-08-01`. Do this on every later request in the conversation, because the API doesn’t store the setting between requests. A saved conversation still holds the change, even after you fix your code, so store the setting with it. Set `type` to `"adaptive"`, as shown:
```json
{
"thinking": {
"type": "adaptive",
"block_binding": {
"prefix_mismatch_behavior": "drop_block"
}
}
}
```
In the SDK, call the beta Messages method and pass the header value, as [the test program](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#how-to-tell-whether-your-integration-is-impacted-details) does. If the error [still asks for the header](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#setting-not-received), the header didn’t arrive.
That stops the errors by dropping the invalid thinking, but it [can cost more](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#drop-block-cost) and can affect output quality and latency. The next request succeeds, and its `input_transformations` array lists each block the API dropped.
If your requests don’t set the `thinking` parameter, adding it changes nothing else, because [models with preserved thinking](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#outcomes) think adaptively by default.
</Step>
<Step title="Long-term fix">
Make your integration append-only:
1. Find the change. The error can name it. To find where your code makes it, run `claude update`, then run `/claude-api preserved-thinking-migration` in Claude Code in your code’s repository. Or [look by hand](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#find-the-change-details).
2. Stop making the change, or new conversations can get the error too. [Send an update at the end instead](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions).
3. Keep sending each running conversation the `system` and `tools` of [its last successful request](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#saved-sessions).
</Step>
</Steps>
If the change was recent, you can undo it instead: put back what your code changed. The thinking from before the change is valid again. Thinking produced since then fails, so remove it or keep sending `"drop_block"`.
## What dropping thinking costs
Claude can spend more output tokens working out reasoning it can no longer read. Dropping the blocks also invalidates the prompt cache from the first dropped block onward.
When something changes on every turn, every turn loses the thinking from earlier turns, so the increase repeats. Run evals on your own workload before you rely on `"drop_block"`.
<Frame>

</Frame>
Use `"drop_block"` to keep existing conversations going while they hold a change you’re fixing, and for a planned drop, such as [after your own summary](https://platform.claude.com/docs/en/build-with-claude/compaction-thinking-blocks#own-summary). Don’t leave it on to hide a change your code keeps making. You can [stop sending it](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#drop-block-cost-details) once the invalid blocks are out of the history.
## Find what your code changed
The change is before the first block that the API names. The 400 error can say what changed, such as `system`, `tools`, a missing thinking block, or the first message that differs. With no error, or one that doesn’t say, [start from what you have](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#find-the-change-details), such as a `prefix_binding_mismatch` entry or more output tokens.
## Test your code for prefix check failures
You can trust a test once you’ve seen it fail. First, through your own code, run a session that makes Claude think, and change something on purpose. Send `"drop_block"` for this step, so the response lists every invalid block instead of stopping at the first, as [five steps and a program](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#how-to-tell-whether-your-integration-is-impacted-details) show.
Then take that change out, and run your real sessions. On every request, send the beta header `thinking-binding-controls-2026-08-01`, and set [`prefix_mismatch_behavior`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#preserved-thinking-controls) to `"error"`, so a change your code makes in those sessions fails the build with the 400 error.
Test sessions that compact, add tools late, change instructions, [switch models](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#switching-models), or resume: plain turns don’t reach that code.
A clean run has no `prefix_binding_mismatch` entry and no error. It shows only that the blocks it checked are valid, in the sessions you ran. It proves nothing if:
* the session had no thinking blocks
* your code removes all thinking, or the model doesn’t have preserved thinking
* you didn’t send the beta header, and [the model might let failing blocks through](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#outcomes) without an error
## Watch production, and find reasoning lost with no error
Log each 400 error, and [handle it in code](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#handle-the-error-in-code) so the conversation carries on. If you leave `prefix_mismatch_behavior` unset instead, [watch production traffic](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#watch-production-traffic) for failing thinking that reached Claude.
Reasoning can be lost with no error through `"drop_block"`, a batch item, a model switch, a move to [another account](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#account-bound-thinking), or code that sends no thinking back. With the beta header, each leaves an entry except the last. For that one, check that your requests hold `thinking` blocks.
## Technical details
### What else the error message can say
The error message goes on to name what changed, where the API can tell, such as `system`, `tools`, the first message or block that differs, content that is missing or new, or an earlier thinking block that is missing or out of order. That sentence’s wording can change, so don’t match on it. If the request had no beta header, the error message adds this sentence:
```text wrap
That setting requires the `thinking-binding-controls-2026-08-01` value in the `anthropic-beta` header.
```
### The error still asks for the header, or says `Extra inputs are not permitted`
If the error still asks for the header, the header didn’t reach the API: a proxy or library might have removed it. A different error, ending `block_binding: Extra inputs are not permitted`, means the same, or that thinking is off.
### Remove the invalid thinking by hand
Use this where you can’t send the setting, or in one of these cases:
* After you removed a secret, personal data, or injected text, because `"drop_block"` leaves the thinking in your stored history.
* When thinking is off (`"disabled"` or `"between_tools"`), because `"drop_block"` needs `"adaptive"`, which would turn thinking on and add output tokens.
1. Remove the block the error names, and every `thinking` and [`redacted_thinking`](https://platform.claude.com/docs/en/build-with-claude/thinking#redacted-thinking-blocks) block after it, or the same error then names the next invalid one. But if your history holds `role: "system"` messages, or two messages of the same role in a row, remove every `thinking` and `redacted_thinking` block. The error counts such a message as part of the message before it, so the position it names can be lower than the index in your array. The `path` of an `input_transformations` entry counts every message separately.
2. Keep every other block, and send the request again. Don’t put the removed blocks back.
### Errors that look like this one
These errors don’t say that the block is bound to a different conversation, and setting `prefix_mismatch_behavior` might not clear them:
* A message that stops after “Invalid `signature` in `thinking` block”, says “cannot be modified”, or is about `budget_tokens`: [Troubleshooting thinking](https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting)
* A message about `temperature` or a prefilled reply: [Limits and feature compatibility](https://platform.claude.com/docs/en/build-with-claude/thinking#limits-and-feature-compatibility)
### When you can stop sending `"drop_block"`
To stop sending `"drop_block"`, remove the blocks it drops and keep the thinking produced since. If nothing else has changed, the conversation then passes with `"error"`.
### Handle the error in code
To handle the error in code, send the beta header and `"error"` on every request, so a failure is a 400 error on every model. When it comes, switch that conversation alone to `"drop_block"` for every later request, store that choice with it, send again once, and log the error. If the same error comes back, the setting didn’t reach the API: see [The error still asks for the header, or says `Extra inputs are not permitted`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#setting-not-received).
Your code has to recognize this error by its `message` field, the exception to the general advice not to match an error’s text. Every 400 error has the type `invalid_request_error`, and this one has no `error.details.error_code`. Match only on “The block is bound to a different conversation”, because the rest of the message can change.
### You have no error, or one that doesn’t say what changed
* **The 400 error, and it names no change:** Compare the requests yourself. One cause is a compaction block sent without removing the messages it summarizes.
* **A `prefix_binding_mismatch` entry:** Compare `system`, `tools`, and `messages` up to the first entry’s `path` with the conversation as it was when that block was produced. If every block is listed, start with `system` and `tools`.
If nothing differs, look for [an image or document URL whose content changed](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#files-by-id), an MCP server’s changed tool list (see [Pin an MCP server’s tool list](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector#pin-mcp-tool-list)), or a thinking block left out, moved, or put back. Without request logs, send the request again with `"error"`, and read the sentence that says what differs, if there is one.
* **A proxy or library that isn’t yours, in between:** Compare what it sends with what you sent, and point its maintainer to [Libraries, proxies, and gateways](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#libraries-proxies-gateways).
* **Only a suspicion, and no Claude Code:** Capture a conversation’s request bodies, one with a compaction or a tool change. Compare each with the one before, leaving out its new turns.
### Five steps for a test that can fail, and a program that runs them
1. In your own code, send the beta header and `"drop_block"` on each request of a session, to a [model with preserved thinking](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#outcomes).
2. Use prompts that make Claude think, and check that the first response has a `thinking` block. With adaptive thinking, Claude can skip thinking on an easy prompt.
3. Fail only on an `input_transformations` entry whose reason is `prefix_binding_mismatch`. A test that wants an empty array also fails after a model switch, because entries with `model_binding_mismatch` or `organization_binding_mismatch` are expected then.
4. Change something on purpose, such as adding a `system` prompt on the third request only.
5. Check that `input_transformations` lists the thinking blocks that sit after the change. If it lists none, check that the history holds a thinking block.
This program does steps 1, 2, 4, and 5. It prints how many thinking blocks and `input_transformations` entries each response has, then each entry, which is what step 3 checks:
<CodeGroup exclude="shell:cURL, shell:CLI">
<CodeGroupItem language="cURL">
This workflow needs more logic than a single shell command. Use the [Anthropic SDK](https://platform.claude.com/docs/en/cli-sdks-libraries/overview) to run it.
</CodeGroupItem>
<CodeGroupItem language="CLI">
This workflow needs more logic than a single shell command. Use the [Anthropic SDK](https://platform.claude.com/docs/en/cli-sdks-libraries/overview) to run it.
</CodeGroupItem>
```python Python
client = anthropic.Anthropic()
user_turns = [
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many have exactly 8?",
"How many have exactly 10?",
]
messages = []
for turn, user_turn in enumerate(user_turns, start=1):
messages.append({"role": "user", "content": user_turn})
response = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=16000,
thinking={
"type": "adaptive",
"block_binding": {"prefix_mismatch_behavior": "drop_block"},
},
# Only the last request adds a system prompt, which changes the prefix on purpose
system="Answer briefly." if turn == len(user_turns) else anthropic.omit,
messages=messages,
betas=["thinking-binding-controls-2026-08-01"],
)
messages.append({"role": "assistant", "content": response.content})
thinking_blocks = sum(block.type == "thinking" for block in response.content)
entries = response.input_transformations or []
print(f"thinking blocks: {thinking_blocks}, entries: {len(entries)}")
for entry in entries:
if entry.type == "thinking_dropped":
print(entry.type, entry.path, entry.reason)
```
```typescript TypeScript
const client = new Anthropic();
const userTurns = [
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many have exactly 8?",
"How many have exactly 10?"
];
const messages: Anthropic.Beta.BetaMessageParam[] = [];
for (const [turnIndex, userTurn] of userTurns.entries()) {
messages.push({ role: "user", content: userTurn });
const response = await client.beta.messages.create({
model: "claude-opus-5-5",
max_tokens: 16000,
thinking: {
type: "adaptive",
block_binding: { prefix_mismatch_behavior: "drop_block" }
},
// Only the last request adds a system prompt, which changes the prefix on purpose
system: turnIndex === userTurns.length - 1 ? "Answer briefly." : undefined,
messages,
betas: ["thinking-binding-controls-2026-08-01"]
});
messages.push({ role: "assistant", content: response.content });
const thinkingBlocks = response.content.filter((block) => block.type === "thinking");
const entries = response.input_transformations ?? [];
console.log(`thinking blocks: ${thinkingBlocks.length}, entries: ${entries.length}`);
for (const entry of entries) {
if (entry.type === "thinking_dropped") {
console.log(entry.type, entry.path, entry.reason);
}
}
}
```
```csharp C#
using Anthropic.Models.Beta;
using Anthropic.Models.Beta.Messages;
using Model = Anthropic.Models.Messages.Model;
AnthropicClient client = new();
string[] userTurns =
[
"How many positive integers below 500 have exactly 6 positive divisors?",
"How many have exactly 8?",
"How many have exactly 10?",
];
List<BetaMessageParam> messages = [];
foreach (var (turnIndex, userTurn) in userTurns.Index())
{
messages.Add(new BetaUserMessageParam { Content = userTurn });
var response = await client.Beta.Messages.Create(
new()
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 16000,
Thinking = new BetaThinkingConfigAdaptive
{
BlockBinding = new()
{
PrefixMismatchBehavior = BetaThinkingPrefixMismatchBehavior.DropBlock,
},
},
// Only the last request adds a system prompt, which changes the prefix on purpose
System = turnIndex == userTurns.Length - 1 ? new("Answer briefly.") : null,
Messages = messages,
Betas = [AnthropicBeta.ThinkingBindingControls2026_08_01],
}
);
messages.Add(new BetaAssistantMessageParam
{
Content = response.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
});
var thinkingBlocks = response.Content.Count(block => block.TryPickThinking(out _));
var entries = response.InputTransformations ?? [];
Console.WriteLine($"thinking blocks: {thinkingBlocks}, entries: {entries.Count}");
foreach (var entry in entries)
{
if (entry.TryPickThinkingDropped(out var dropped))
{
Console.WriteLine($"{dropped.Type} {dropped.Path} {dropped.Reason.Raw()}");
}
}
}
```
Cut at 300 lines. The page has the rest.
build-with-claude/preserved-thinking/prefix-checks New page · 446 lines, new page
## Who the prefix check affects ## What must stay the same, and why ## If your code changes the system prompt, tools, or history ## What each model does when the prefix check fails ## Handle both cases ## Technical details ### How the prefix check works ### Which models run the prefix check ### What counts as a change ### What doesn’t count ### Set `prefix_mismatch_behavior` ### Watch production traffic ### Check which case you get ## Next steps
A whole new page. There's nothing to diff it against, so here is what it says.
---
title: Prefix checks on thinking blocks
url: https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks
description: 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.
featureMetadata:
status: stable
supportedModels:
- claude-fable-5-2
- claude-mythos-5-2
- claude-fable-5-1
- claude-mythos-5-1
- claude-opus-5-5
- claude-sonnet-5-5
- claude-haiku-5-5
supportedPlatforms:
Claude API: stable
Amazon Bedrock: stable
Amazon Bedrock (bedrock-runtime): stable
Claude Platform on AWS: stable
Claude Models on Google Cloud (Anthropic managed): stable
Claude Models on Google Cloud (Google managed): stable
Microsoft Foundry: stable
---
Each request that sends thinking blocks back to a [model with preserved thinking](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#outcomes) 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](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#outcomes), so [handle both cases](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#handle-either-outcome).
## Who the prefix check affects
You have nothing to change if one of these is true:
* Claude Code, claude.ai, or Claude Managed Agents manages your conversation history.
* The Claude Agent SDK manages the history, and the system prompt you give the SDK stays the same.
* Your code follows [the three habits of an append-only integration](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#prefix-check), and nothing rewrites its requests on the way.
* You call no [model with preserved thinking](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#outcomes).
If your requests pass through a library, proxy, or gateway, [compare what it sends with what you sent](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#find-the-change-details). If you run one, see [Libraries, proxies, and gateways](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#libraries-proxies-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](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#prefix-check-details) 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](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#changing-context).
<Frame>

</Frame>
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 files | Stop changing `system`. [Append your own instructions as a system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#new-instructions), a `role: "system"` entry in `messages`, and [state new values in the newest turn](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#changing-context). For text that other people can edit, see [Text that other people can edit](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#untrusted-text) instead. |
| Adds, removes, or rewords a tool, or gains tools when an MCP server connects | Stop changing `tools`, and [add or remove tools in a system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#tool-changes). |
| Trims, collapses, or summarizes older turns, and keeps the latest ones unchanged | [Let the API write the summary, or remove the kept turns’ thinking](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#server-side-trimming). |
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](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions). To find yours, [have an agent check your code](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#hand-to-an-agent).
## What each model does when the prefix check fails
On the Claude API and [some cloud platforms](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#feature-availability), the result of failing the prefix check with [`prefix_mismatch_behavior`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#preserved-thinking-controls) unset depends on the model:
| Model with preserved thinking | Result |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Claude Fable 5.2 | 400 error |
| Claude Mythos 5.2 | 400 error |
| Claude Mythos 5.1 | 400 error |
| Claude Fable 5.1 | [Possible 400 error](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#possible-400-error) |
| Claude Opus 5.5 | [Possible 400 error](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#possible-400-error) |
| Claude Sonnet 5.5 | [Possible 400 error](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#possible-400-error) |
| Claude Haiku 5.5 | [Possible 400 error](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#possible-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](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#check-which-case), but [handle both cases](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#handle-either-outcome).
<Tip>
Code that follows [the three habits of an append-only integration](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#prefix-check) doesn’t cause prefix check failures.
</Tip>
[Message Batches items](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#batches) 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.
* **Append-only:** with [the three habits](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#prefix-check), your code doesn’t make a request fail the prefix check.
* **The setting:** with `"error"` on every request, each failure is a 400 error, and you [catch it in code](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#handle-the-error-in-code).
* **Both behaviors:** if you leave the setting unset, catch the 400 error, and [watch for failing thinking that reached Claude](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#watch-production-traffic).
After a 400 error, send `"drop_block"` on that conversation’s later requests to recover. It has a cost: see [What dropping thinking costs](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#drop-block-cost). 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](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#feature-availability). It checks thinking blocks that a [model with preserved thinking](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#outcomes) 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](https://platform.claude.com/docs/en/build-with-claude/compaction-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](https://platform.claude.com/docs/en/build-with-claude/compaction-thinking-blocks#conditions-for-kept-thinking-to-stay-valid). For a file sent by URL and, on the Claude API, [an MCP tool list](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector#pin-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](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/change-tools-or-instructions#leave-out-thinking), 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 count | Because |
| ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `model`, `effort`, `max_tokens`, `tool_choice`, `metadata`, `thinking.display`, and other parameters | Only `system`, `tools`, and messages are compared |
| JSON formatting or key order | The API compares parsed JSON, not its text |
| `cache_control` markers | They say where to cache, and aren’t content that Claude reads |
| A tool with `defer_loading: true` that nothing has referred to | It isn’t in Claude’s context until something refers to it |
### Set `prefix_mismatch_behavior`
The [beta header](https://platform.claude.com/docs/en/api/beta-headers#beta-features-on-other-platforms) `thinking-binding-controls-2026-08-01` adds the setting `thinking.block_binding.prefix_mismatch_behavior` and the response’s `input_transformations` array.
| Value | If 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](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/fix-bound-to-a-different-conversation#drop-block-cost) |
| Not set, or `null` | [Depends on the model](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking/prefix-checks#outcomes) |
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:
<CodeGroup>
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2026-10-21" \
-H "anthropic-beta: thinking-binding-controls-2026-08-01" \
-H "content-type: application/json" \
-d '{
"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?"
}
]
}'
```
```bash CLI
ant beta:messages create --beta thinking-binding-controls-2026-08-01 <<'YAML'
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?
YAML
```
```python Python
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 [])}")
```
```typescript TypeScript
const client = new Anthropic();
const response = await 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 (const block of response.content) {
if (block.type === "text") {
console.log(block.text);
}
}
console.log(`Input transformations: ${response.input_transformations?.length ?? 0}`);
```
```csharp C#
using Anthropic.Models.Beta;
using Anthropic.Models.Beta.Messages;
using Model = Anthropic.Models.Messages.Model;
AnthropicClient client = new();
var response = await client.Beta.Messages.Create(
new()
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 16000,
Thinking = new BetaThinkingConfigAdaptive
{
BlockBinding = new()
{
PrefixMismatchBehavior = BetaThinkingPrefixMismatchBehavior.DropBlock,
},
},
Messages =
[
new BetaUserMessageParam
{
Content = "What is the greatest common divisor of 1071 and 462?",
},
],
Betas = [AnthropicBeta.ThinkingBindingControls2026_08_01],
}
);
foreach (var block in response.Content)
{
if (block.TryPickText(out var textBlock))
{
Console.WriteLine(textBlock.Text);
}
}
Console.WriteLine($"Input transformations: {response.InputTransformations?.Count ?? 0}");
```
```go Go
client := anthropic.NewClient()
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 16000,
Thinking: anthropic.BetaThinkingConfigParamUnion{
OfAdaptive: &anthropic.BetaThinkingConfigAdaptiveParam{
BlockBinding: anthropic.BetaThinkingBlockBindingParam{
PrefixMismatchBehavior: anthropic.BetaThinkingPrefixMismatchBehaviorDropBlock,
},
},
},
Messages: []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("What is the greatest common divisor of 1071 and 462?")),
},
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaThinkingBindingControls2026_08_01},
})
if err != nil {
log.Fatal(err)
}
for _, block := range response.Content {
if textBlock, ok := block.AsAny().(anthropic.BetaTextBlock); ok {
fmt.Println(textBlock.Text)
}
Cut at 300 lines. The page has the rest.