from line 11
1111
1212The model check applies to every account. The API enforces the prefix check by default for accounts created on or after August 31, 2026, 00:00 UTC. On older accounts, it enforces the prefix check only on requests that set `thinking.block_binding.prefix_mismatch_behavior`. **Later models will enforce the prefix check for all accounts**, so make your integration append-only now.
1313
14## Who needs to change anything
15
16Nothing changes for you if Claude Code, claude.ai, Claude Managed Agents, or the Claude Agent SDK builds your requests, or if your code keeps `system` and `tools` fixed for a session and only ever appends to `messages`. Claude Mythos 5.1 and models before Claude Fable 5.1 don't run the prefix check. If you never send thinking blocks back, the prefix check has nothing to reject, and the model gets none of its earlier reasoning.
17
18Check your integration if, between two requests in one conversation, it does any of the following. Each item links to what to do instead:
19
20* [Rebuilds the `system` prompt](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#new-instructions): the date, a mode flag, re-read project instructions, or a plugin or MCP server that connects after the first turn
21* [Re-renders the context in the first user message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#changing-context)
22* [Clears or shortens old tool results, or re-encodes old images](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#server-side-trimming)
23* [Summarizes or drops old turns on the client](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#custom-compaction-on-the-client) and keeps recent turns with their thinking
24* [Adds, removes, or edits entries in `tools`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#tool-changes)
25* [Adds a reminder to a user turn](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#per-turn-reminders) and removes or rewrites it later
26* [Drops some `thinking` blocks and keeps later ones](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#append-assistant-turns-exactly-as-returned), or removes them and [later puts them back](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#prefix-check)
27* [Rebuilds a saved session from templates](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#faq) instead of replaying what it sent
28
29On an older account, none of these produces an error unless the request sets `prefix_mismatch_behavior`, so a run with no errors on your own key doesn't show whether your code is affected. If people run your tool with their own API keys, those on newer accounts get the 400 error before you do. [Set `prefix_mismatch_behavior` in your tests](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#how-to-tell-whether-your-integration-is-impacted) to see what they see.
30
1431## Switching models mid-conversation
1532
1633Claude Fable 5.1 and Claude Mythos 5.1 read thinking blocks produced by each other and by earlier Claude models. No earlier model reads thinking blocks from Claude Fable 5.1 or Claude Mythos 5.1.
from line 67
5067
5168Request parameters outside those three fields, such as `effort`, `max_tokens`, `output_config`, `tool_choice`, and `metadata`, aren't part of the prefix check, and neither are `cache_control` markers. [What counts as an edit](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#what-counts-as-an-edit) has the full list.
5269
53Earlier thinking blocks aren't in the prefix, but each thinking block records which thinking block came before it, across turns. You can remove thinking blocks from the front of the history, oldest first. Removing one from the middle invalidates thinking blocks after it.
70Earlier thinking blocks aren't in the prefix, but each thinking block records which thinking block came before it, across turns. You can remove thinking blocks from the start of the history (oldest first), from the end, or all of them. What fails is a gap: the thinking blocks you keep must be an unbroken run of the original sequence, so removing one from the middle invalidates the thinking blocks after it. Once you remove a block, leave it out. Putting it back invalidates the thinking blocks produced while it was gone.
5471
5572Keep `system` and `tools` fixed for the session and treat `messages` as append-only. The same discipline keeps the prefix stable for [prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching): the edits that invalidate thinking are the edits that restart the cache.
5673
from line 78
6178* **`"error"` (the default):** the API rejects the request with a 400 `invalid_request_error` that names the first failing block.
6279* **`"drop_block"`:** the API drops each failing block and every thinking block after it, and the request succeeds. Dropped blocks aren't billed. The model answers that turn without using reasoning from dropped blocks, and the prompt cache restarts at the edit. The response lists each dropped block in `input_transformations` (on the `message_start` event when streaming) with `reason: "prefix_binding_mismatch"`.
6380
81`"drop_block"` keeps requests succeeding but doesn't fix the edit. Count the responses in each session whose `input_transformations` has a `prefix_binding_mismatch` entry, and alert on them. In the Message Batches API, an item that leaves the field unset drops failing blocks instead of erroring, so set `"error"` explicitly there if you want batch items to fail.
82
6483Both the field and the `input_transformations` array require the `thinking-binding-controls-2026-08-01` [beta header](https://platform.claude.com/docs/en/api/beta-headers). [Set the mismatch behavior and read `input_transformations`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#preserved-thinking-controls) shows the request in each SDK.
6584
6685The 400 message begins:
from line 94
7594That setting requires the `thinking-binding-controls-2026-08-01` value in the `anthropic-beta` header.
7695```
7796
78It usually ends with a sentence naming what changed, for example that the `system` prompt or the `tools` list differs from when the block was created. See [Troubleshooting thinking](https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting#error-thinking-block-signature) for every variant of this error.
97It usually ends with a sentence naming what changed, for example that the `system` prompt or the `tools` list differs from when the block was created. [Troubleshooting thinking](https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting#error-thinking-block-signature) describes what that sentence can name.
7998
80If you hit this 400 in production, retrying the same body fails the same way. Retry with the beta header and `prefix_mismatch_behavior: "drop_block"` and keep sending it for the rest of the session, or strip every `thinking` and `redacted_thinking` block from the history yourself and retry once. Then fix the edit that caused the mismatch. In the Message Batches API, an item that leaves the field unset drops failing blocks instead of erroring, so set `"error"` explicitly there if you want batch items to fail.
81
8299A tampered or undecryptable signature is a different failure. It always returns a 400 (``Invalid `signature` in `thinking` block`` with no sentence about the conversation), and `prefix_mismatch_behavior` doesn't apply to it.
83100
101#### Handle the error in code
102
103This is the 400 `invalid_request_error` shown earlier in this section. Don't resend the same body: it fails the same way every time. Retry once with the beta header and `prefix_mismatch_behavior: "drop_block"`, and store that choice with the session so every later request sends it too, including after a restart. If you can't send the beta header, remove every `thinking` and `redacted_thinking` block from the history once, leave them out, and continue. Then fix the edit that caused the mismatch.
104
84105### Set the mismatch behavior and read `input_transformations`
85106
86107The `thinking-binding-controls-2026-08-01` [beta header](https://platform.claude.com/docs/en/api/beta-headers) adds:
from line 109
88109* A top-level `input_transformations` array on every response
89110* A `block_binding` object on the `thinking` configuration, whose one field is `prefix_mismatch_behavior`
90111
91`block_binding` is accepted alongside `thinking.type: "adaptive"` and `thinking.type: "enabled"`. Sending it without the beta header returns a 400 error. Models that don't run the prefix check accept the object and report only model-check drops, so one request body works across models.
112`block_binding` is accepted alongside `thinking.type: "adaptive"` and `thinking.type: "enabled"`. Sending it without the beta header returns a 400 error whose message ends `block_binding: Extra inputs are not permitted`. Models that don't run the prefix check accept the object and report only model-check drops, so one request body works across models. The API reference calls the prefix check the conversation check.
92113
93114The following request opts into dropping rather than rejecting. On a first turn there's nothing to replay, so `input_transformations` comes back empty:
94115
from line 380
359380
360381To find out which group your account is in, take a Claude Fable 5.1 conversation that contains a thinking block, change something before that block, and send it to Claude Fable 5.1 without the beta header or the `block_binding` field. A 400 response that names the header means your account is enforced by default.
361382
362<Note>
363 If you maintain a tool or framework that people run with their own API key, your users on new accounts hit the check before you do, because your own key is likely on an older account. Test with `prefix_mismatch_behavior` set so you see what they see.
364</Note>
365
366383### What counts as an edit
367384
368385Each row compares two consecutive requests:
369386
370| Change between requests | Later thinking blocks |
371| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
372| Append messages at the end | Valid |
373| Add a tool with `defer_loading: true` that nothing has referenced yet | Valid |
374| Remove `thinking` blocks from the start of the history | Valid |
375| Change any request parameter outside `system`, `tools`, and `messages` (`effort`, `max_tokens`, `output_config`, `tool_choice`, `metadata`, and so on) | Valid |
376| Add, move, or remove `cache_control` markers | Valid |
377| A rotating signed URL that returns the same bytes | Valid |
378| Server-side compaction or context editing removes or replaces content | Valid (the check compares what you sent, not the server's edited copy) |
379| A cleared [turn-scoped system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#per-turn-reminders) left in place | Valid |
380| Edit, reorder, or delete any earlier `user`, `assistant`, or `system` message | Invalid |
381| Add a text block to an earlier user turn, or remove one you added last time | Invalid |
382| Change the top-level `system` string or blocks | Invalid |
383| Add, remove, rename, or edit a tool in `tools` | Invalid |
384| Remove a `thinking` block from the middle of the history and keep later ones | Invalid for every later thinking block |
385| An image or document URL that returns different bytes on the next request | Invalid |
386| The same turn-scoped message deleted or reworded on a later request | Invalid |
387| Change between requests | Later thinking blocks |
388| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
389| Append messages at the end | Valid |
390| Add a tool with `defer_loading: true` that nothing has referenced yet | Valid |
391| Remove `thinking` blocks from the start of the history, from the end, or all of them | Valid (the model loses that reasoning) |
392| Change any request parameter outside `system`, `tools`, and `messages` (`effort`, `max_tokens`, `output_config`, `tool_choice`, `metadata`, `thinking.display`, and so on) | Valid |
393| Add, move, or remove `cache_control` markers | Valid |
394| A rotating signed URL that returns the same bytes | Valid |
395| Server-side compaction or context editing removes or replaces content | Valid (the check compares what you sent, not the server's edited copy) |
396| A cleared [turn-scoped system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#per-turn-reminders) left in place | Valid |
397| Edit, reorder, or delete any earlier `user`, `assistant`, or `system` message | Invalid |
398| Re-render the context you put in the first user message with a changed value | Invalid for every thinking block |
399| Clear or shorten an earlier `tool_result`, re-encode an earlier image, or change an earlier `tool_use` input | Invalid for every later thinking block |
400| Add a text block to an earlier user turn, or remove one you added last time | Invalid |
401| Change the top-level `system` string or blocks | Invalid |
402| Add, remove, rename, or edit a tool in `tools` | Invalid |
403| Remove a `thinking` block from the middle of the history and keep later ones | Invalid for every later thinking block |
404| Put back a `thinking` block you removed on an earlier request | Invalid for thinking blocks produced while it was gone |
405| An image or document URL that returns different bytes on the next request | Invalid |
406| The same turn-scoped message deleted or reworded on a later request | Invalid |
387407
388408### Check whether your code edits the prefix
389409
390410First, diff what you send. Capture the request bodies your integration sends over a few normal turns, including a compaction or a tool change. For each pair of consecutive requests, compare `system`, `tools`, and the `messages` they share. They should be identical up to the newly appended turns.
391411
392Then confirm against the API. Add the `thinking-binding-controls-2026-08-01` beta header, set `prefix_mismatch_behavior` to `"drop_block"`, and run a normal multi-turn session through your integration on claude-fable-5-1. The following example runs two turns the way your integration should: `messages` only grows, each assistant turn goes back exactly as the API returned it, `thinking` blocks included, and `block_binding` is set on every request. It prints the number of dropped blocks after each turn:
412Then confirm against the API. Add the `thinking-binding-controls-2026-08-01` beta header, set `prefix_mismatch_behavior` to `"drop_block"`, and run a normal multi-turn session through your integration on claude-fable-5-1. The following example runs two turns the way your integration should: `messages` only grows, each assistant turn goes back exactly as the API returned it, `thinking` blocks included, and `block_binding` is set on every request. After each turn it prints the number of `thinking` blocks in the response and the number of dropped blocks:
393413
394414<CodeGroup>
395415 ```bash cURL
416 # Counts the thinking blocks in a response and the blocks the API dropped
417 COUNTS='"thinking blocks: \([.content[] | select(.type == "thinking")] | length), " +
418 "dropped: \(.input_transformations | length)"'
419
396420 FIRST=$(curl -s https://api.anthropic.com/v1/messages \
397421 -H "content-type: application/json" \
398422 -H "x-api-key: $ANTHROPIC_API_KEY" \
from line 429
405429 "type": "adaptive",
406430 "block_binding": { "prefix_mismatch_behavior": "drop_block" }
407431 },
408 "messages": [{ "role": "user", "content": "What is 27 * 453?" }]
432 "messages": [
433 {
434 "role": "user",
435 "content": "How many positive integers below 500 have exactly 6 positive divisors?"
436 }
437 ]
409438 }')
410 echo "$FIRST" | jq '.input_transformations | length'
439 echo "$FIRST" | jq -r "$COUNTS"
411440
412441 # Turn 2: the assistant turn goes back exactly as returned, then the next user message
413442 MESSAGES=$(jq -n --argjson first "$FIRST" '[
414 { role: "user", content: "What is 27 * 453?" },
443 {
444 role: "user",
445 content: "How many positive integers below 500 have exactly 6 positive divisors?"
446 },
415447 { role: "assistant", content: $first.content },
416 { role: "user", content: "Now divide that result by 3." }
448 { role: "user", content: "How many of those are odd?" }
417449 ]')
418450
419451 jq -n --argjson messages "$MESSAGES" '{
from line 461
429461 -H "x-api-key: $ANTHROPIC_API_KEY" \
430462 -H "anthropic-version: 2023-06-01" \
431463 -H "anthropic-beta: thinking-binding-controls-2026-08-01" \
432 -d @- | jq '.input_transformations | length'
464 -d @- | jq -r "$COUNTS"
433465 ```
434466
435467 ```bash CLI
468 # Counts the thinking blocks in a response and the blocks the API dropped
469 COUNTS='"thinking blocks: \([.content[] | select(.type == "thinking")] | length), " +
470 "dropped: \(.input_transformations | length)"'
471
436472 FIRST=$(ant beta:messages create --beta thinking-binding-controls-2026-08-01 \
437 --transform content --format json <<'YAML'
473 --format json <<'YAML'
438474 model: claude-fable-5-1
439475 max_tokens: 16000
440476 thinking:
from line 479
443479 prefix_mismatch_behavior: drop_block
444480 messages:
445481 - role: user
446 content: What is 27 * 453?
482 content: How many positive integers below 500 have exactly 6 positive divisors?
447483 YAML
448484 )
485 echo "$FIRST" | jq -r "$COUNTS"
449486
450487 # Turn 2: the assistant turn goes back exactly as returned, then the next user message
451488 ant beta:messages create --beta thinking-binding-controls-2026-08-01 \
452 --transform input_transformations --format json <<YAML
489 --format json <<YAML | jq -r "$COUNTS"
453490 model: claude-fable-5-1
454491 max_tokens: 16000
455492 thinking:
from line 495
458495 prefix_mismatch_behavior: drop_block
459496 messages:
460497 - role: user
461 content: What is 27 * 453?
498 content: How many positive integers below 500 have exactly 6 positive divisors?
462499 - role: assistant
463 content: $(echo "$FIRST" | jq -c .)
500 content: $(echo "$FIRST" | jq -c .content)
464501 - role: user
465 content: Now divide that result by 3.
502 content: How many of those are odd?
466503 YAML
467504 ```
468505
from line 506
469506 ```python Python
470507 client = anthropic.Anthropic()
471508
509 user_turns = [
510 "How many positive integers below 500 have exactly 6 positive divisors?",
511 "How many of those are odd?",
512 ]
513
472514 # messages grows across turns: each assistant turn goes back exactly as returned
473515 messages = []
474 for user_turn in ["What is 27 * 453?", "Now divide that result by 3."]:
516 for user_turn in user_turns:
475517 messages.append({"role": "user", "content": user_turn})
476518 response = client.beta.messages.create(
477519 model="claude-fable-5-1",
from line 526
484526 betas=["thinking-binding-controls-2026-08-01"],
485527 )
486528 messages.append({"role": "assistant", "content": response.content})
487 print(len(response.input_transformations or []))
529 thinking_blocks = sum(block.type == "thinking" for block in response.content)
530 dropped = len(response.input_transformations or [])
531 print(f"thinking blocks: {thinking_blocks}, dropped: {dropped}")
488532 ```
489533
490534 ```typescript TypeScript
491535 const client = new Anthropic();
492536
537 const userTurns = [
538 "How many positive integers below 500 have exactly 6 positive divisors?",
539 "How many of those are odd?"
540 ];
541
493542 // messages grows across turns: each assistant turn goes back exactly as returned
494543 const messages: Anthropic.Beta.BetaMessageParam[] = [];
495 for (const userTurn of ["What is 27 * 453?", "Now divide that result by 3."]) {
544 for (const userTurn of userTurns) {
496545 messages.push({ role: "user", content: userTurn });
497546 const response = await client.beta.messages.create({
498547 model: "claude-fable-5-1",
from line 554
505554 betas: ["thinking-binding-controls-2026-08-01"]
506555 });
507556 messages.push({ role: "assistant", content: response.content });
508 console.log(response.input_transformations?.length ?? 0);
557 const thinkingBlocks = response.content.filter((block) => block.type === "thinking");
558 const dropped = response.input_transformations ?? [];
559 console.log(`thinking blocks: ${thinkingBlocks.length}, dropped: ${dropped.length}`);
509560 }
510561 ```
511562
from line 563
512563 ```csharp C#
513564 AnthropicClient client = new();
514565
566 string[] userTurns =
567 [
568 "How many positive integers below 500 have exactly 6 positive divisors?",
569 "How many of those are odd?",
570 ];
571
515572 // messages grows across turns: each assistant turn goes back exactly as returned
516573 List<BetaMessageParam> messages = [];
517 foreach (var userTurn in new[] { "What is 27 * 453?", "Now divide that result by 3." })
574 foreach (var userTurn in userTurns)
518575 {
519576 messages.Add(new() { Role = Role.User, Content = userTurn });
520577 var response = await client.Beta.Messages.Create(
from line 595
538595 Role = Role.Assistant,
539596 Content = response.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
540597 });
541 Console.WriteLine(response.InputTransformations?.Count ?? 0);
598 var thinkingBlocks = response.Content.Count(block => block.TryPickThinking(out _));
599 var dropped = response.InputTransformations?.Count ?? 0;
600 Console.WriteLine($"thinking blocks: {thinkingBlocks}, dropped: {dropped}");
542601 }
543602 ```
544603
from line 604
545604 ```go Go
546605 client := anthropic.NewClient()
547606
607 userTurns := []string{
608 "How many positive integers below 500 have exactly 6 positive divisors?",
609 "How many of those are odd?",
610 }
611
548612 // messages grows across turns: each assistant turn goes back exactly as returned
549613 messages := []anthropic.BetaMessageParam{}
550 for _, userTurn := range []string{"What is 27 * 453?", "Now divide that result by 3."} {
614 for _, userTurn := range userTurns {
551615 messages = append(messages, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(userTurn)))
552616 response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
553617 Model: "claude-fable-5-1",
from line 630
566630 log.Fatal(err)
567631 }
568632 messages = append(messages, response.ToParam())
569 fmt.Println(len(response.InputTransformations))
633 thinkingBlocks := 0
634 for _, block := range response.Content {
635 if block.Type == "thinking" {
636 thinkingBlocks++
637 }
638 }
639 fmt.Printf("thinking blocks: %d, dropped: %d\n", thinkingBlocks, len(response.InputTransformations))
570640 }
571641 ```
572642
573643 ```java Java
574644 import com.anthropic.models.beta.AnthropicBeta;
645 import com.anthropic.models.beta.messages.BetaContentBlock;
575646 import com.anthropic.models.beta.messages.BetaMessage;
576647 import com.anthropic.models.beta.messages.BetaThinkingBlockBinding;
577648 import com.anthropic.models.beta.messages.BetaThinkingConfigAdaptive;
from line 652
581652 void main() {
582653 AnthropicClient client = AnthropicOkHttpClient.fromEnv();
583654
655 List<String> userTurns = List.of(
656 "How many positive integers below 500 have exactly 6 positive divisors?",
657 "How many of those are odd?");
658
584659 // The builder's message list grows across turns: each assistant turn goes back exactly as returned
585660 MessageCreateParams.Builder conversation = MessageCreateParams.builder()
586661 .model("claude-fable-5-1")
from line 667
592667 .build())
593668 .addBeta(AnthropicBeta.THINKING_BINDING_CONTROLS_2026_08_01);
594669
595 for (String userTurn : List.of("What is 27 * 453?", "Now divide that result by 3.")) {
670 for (String userTurn : userTurns) {
596671 conversation.addUserMessage(userTurn);
597672 BetaMessage response = client.beta().messages().create(conversation.build());
598673 conversation.addMessage(response);
599 IO.println(response.inputTransformations().map(List::size).orElse(0));
674 long thinkingBlocks = response.content().stream()
675 .filter(BetaContentBlock::isThinking)
676 .count();
677 int dropped = response.inputTransformations().map(List::size).orElse(0);
678 IO.println("thinking blocks: " + thinkingBlocks + ", dropped: " + dropped);
600679 }
601680 }
602681 ```
from line 689
610689
611690 $client = new Client();
612691
692 $userTurns = [
693 'How many positive integers below 500 have exactly 6 positive divisors?',
694 'How many of those are odd?',
695 ];
696
613697 // $messages grows across turns: each assistant turn goes back exactly as returned
614698 $messages = [];
615 foreach (['What is 27 * 453?', 'Now divide that result by 3.'] as $userTurn) {
699 foreach ($userTurns as $userTurn) {
616700 $messages[] = ['role' => 'user', 'content' => $userTurn];
617701 $response = $client->beta->messages->create(
618702 model: 'claude-fable-5-1',
from line 710
626710 betas: [AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01],
627711 );
628712 $messages[] = ['role' => 'assistant', 'content' => $response->content];
629 echo count($response->inputTransformations ?? []), PHP_EOL;
713 $thinkingBlocks = array_filter($response->content, fn ($block) => $block->type === 'thinking');
714 $dropped = $response->inputTransformations ?? [];
715 echo 'thinking blocks: ', count($thinkingBlocks), ', dropped: ', count($dropped), PHP_EOL;
630716 }
631717 ```
632718
from line 719
633719 ```ruby Ruby
634720 client = Anthropic::Client.new
635721
722 user_turns = [
723 "How many positive integers below 500 have exactly 6 positive divisors?",
724 "How many of those are odd?"
725 ]
726
636727 # messages grows across turns: each assistant turn goes back exactly as returned
637728 messages = []
638 ["What is 27 * 453?", "Now divide that result by 3."].each do |user_turn|
729 user_turns.each do |user_turn|
639730 messages << {role: "user", content: user_turn}
640731 response = client.beta.messages.create(
641732 model: "claude-fable-5-1",
from line 739
648739 betas: [Anthropic::AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01]
649740 )
650741 messages << {role: "assistant", content: response.content}
651 puts (response.input_transformations || []).length
742 thinking_blocks = response.content.count { |block| block.type == :thinking }
743 dropped = (response.input_transformations || []).length
744 puts "thinking blocks: #{thinking_blocks}, dropped: #{dropped}"
652745 end
653746 ```
654747</CodeGroup>
655748
656749```text Output wrap
6570
6580
750thinking blocks: 1, dropped: 0
751thinking blocks: 1, dropped: 0
659752```
660753
661Both turns print `0` because nothing earlier changed. Log `input_transformations` on every turn of your own integration. When the API drops a block, the entry looks like the following:
754Neither turn drops a block because nothing earlier changed. Check that the first response contains a `thinking` block. With adaptive thinking, some responses have none. If no response in the session has one, there is nothing to check and the dropped count is 0 whatever you change, so run the example again.
662755
756Log `input_transformations` on every turn of your own integration. When the API drops a block, the entry looks like the following:
757
663758```json
664759{
665760 "input_transformations": [
from line 767
672767}
673768```
674769
675* **Empty on every turn:** your integration keeps the prefix intact.
676* **`reason: "prefix_binding_mismatch"`:** something before the block at `path` changed since the previous request. Diff `system`, `tools`, and `messages` up to that turn to find it, then find the matching replacement in [Make changes without editing the prefix](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#replace-prefix-edits).
770* **Empty on every turn of a session that contains `thinking` blocks:** your integration keeps the prefix intact.
771* **`reason: "prefix_binding_mismatch"`:** something before the block at `path` changed since the previous request. Diff `system`, `tools`, and `messages` up to that turn to find it, or resend the request with `"error"`: the 400 usually ends with a sentence naming what changed. Then find the matching replacement in [Make changes without editing the prefix](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#replace-prefix-edits).
677772* **`reason: "model_binding_mismatch"`:** the conversation moved to a model that can't read the earlier model's blocks. This isn't a prefix edit. See [Switching models mid-conversation](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#switching-models).
678773
679To fail loudly in CI instead, set `"error"` and treat the 400 described in [What the API does with an invalid block](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#mismatch-behavior) as a test failure.
774To see a failure on purpose, send a third turn from the earlier example and add a `system` prompt to that request only, so that it differs from the first two requests, which had none. With `"drop_block"`, the dropped count is no longer 0: the response has one entry for each thinking block in the history, each with `reason: "prefix_binding_mismatch"`. With `"error"`, the request returns the 400 described in [What the API does with an invalid block](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#mismatch-behavior), and its last sentence names the `system` prompt. In the cURL and CLI tabs, remove the `jq` filter to see the error body. If the count is still 0, there was nothing to check: confirm that the model is claude-fable-5-1, that the request sets `block_binding`, that the history you sent contains `thinking` blocks, and that the first two requests had no `system` prompt.
680775
776Two plain turns rarely show the problem. Run a session through each of the following, with `"error"` set so that a regression fails your CI:
777
778* The first client-side compaction or trim
779* A tool, plugin, or MCP server that connects after the first turn
780* A mode or instruction change
781* A long tool loop, if you add reminders or shorten old tool results
782* A switch to another model and back
783* A save, a restart, and a resume on a later date
784
681785## Make changes without editing the prefix
682786
683787Each common prefix edit has a replacement that gives the model the same information and leaves earlier bytes unchanged, so later thinking stays valid. Find the edit your code makes today in the first column:
684788
685| Instead of | Use | Beta header |
686| --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
687| Rebuilding the top-level `system` prompt | A [mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#new-instructions) | None |
688| Injecting a reminder and deleting it on the next request | A [turn-scoped system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#per-turn-reminders) (`clear_at: "next_user_message"`) | `mid-conversation-system-clear-at-2026-08-21` |
689| Adding or removing entries in `tools` | [`tool_addition` and `tool_removal` blocks](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#tool-changes) | `mid-conversation-tool-changes-2026-07-01` |
690| Changing top-level `output_config.effort` (restarts the cache, doesn't affect thinking) | A [per-message `output_config`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#effort-changes) | `mid-conversation-output-config-2026-07-01` |
691| Dropping or summarizing old turns on the client | Server-side [compaction or context editing](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#server-side-trimming), or [client-side compaction](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#custom-compaction-on-the-client) that keeps no stale thinking | `compact-2026-01-12` or `context-management-2025-06-27` |
692| An image or document URL whose bytes change between requests | A [`file_id` from the Files API](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#files-by-id), or base64 | None |
789| Instead of | Use | Beta header |
790| --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
791| Rebuilding the top-level `system` prompt | A [mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#new-instructions) | None |
792| Re-rendering the context in your first user message (environment, date, memory, project instructions) on each request | Render it once and resend it unchanged. When something changes, [put the new version in the newest turn](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#changing-context) | None |
793| Clearing or shortening old `tool_result` content, or re-encoding old images, in place | Shorten a tool result or downscale an image before the first time you send it, not after. To clear old results later, [trim context on the server](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#server-side-trimming) with `clear_tool_uses_20250919` | `context-management-2025-06-27` |
794| Injecting a reminder and deleting it on the next request | A [turn-scoped system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#per-turn-reminders) (`clear_at: "next_user_message"`) | `mid-conversation-system-clear-at-2026-08-21` |
795| Adding or removing entries in `tools` | [`tool_addition` and `tool_removal` blocks](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#tool-changes) | `mid-conversation-tool-changes-2026-07-01` |
796| Changing top-level `output_config.effort` (restarts the cache, doesn't affect thinking) | A [per-message `output_config`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#effort-changes) | `mid-conversation-output-config-2026-07-01` |
797| Dropping or summarizing old turns on the client | Server-side [compaction or context editing](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#server-side-trimming), or [client-side compaction](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#custom-compaction-on-the-client) that keeps no stale thinking | `compact-2026-01-12` or `context-management-2025-06-27` |
798| An image or document URL whose bytes change between requests | A [`file_id` from the Files API](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#files-by-id), or base64 | None |
693799
694All of these assume you [send assistant turns back exactly as returned](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#append-assistant-turns-exactly-as-returned). To use several betas in one request, combine the values in one `anthropic-beta` header. The same names apply on Amazon Bedrock and Google Cloud (see [Beta headers](https://platform.claude.com/docs/en/api/beta-headers)):
800All of these assume you [send assistant turns back exactly as returned](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#append-assistant-turns-exactly-as-returned). Mid-conversation system messages, turn-scoped system messages, and tool changes aren't available on every model: [Mid-conversation system messages and tool changes](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) lists the models that accept them. If your code serves several models, keep editing the top-level `system` prompt for the models that don't accept them.
695801
802To use several betas in one request, combine the values in one `anthropic-beta` header. The same names apply on Amazon Bedrock and Google Cloud (see [Beta headers](https://platform.claude.com/docs/en/api/beta-headers)):
803
696804```text wrap
697805anthropic-beta: thinking-binding-controls-2026-08-01,mid-conversation-system-clear-at-2026-08-21,mid-conversation-tool-changes-2026-07-01
698806```
from line 809
701809
702810Store the `content` array from each response and send it back unchanged as the assistant turn: every block type, in the order received, including `thinking` blocks whose `thinking` field is empty. A serializer that drops unknown block types, drops empty fields, or reorders blocks edits the prefix for every later turn.
703811
812On Claude Fable 5.1, the `thinking` field is empty by default and the `signature` carries the reasoning, so a serializer that skips empty blocks removes thinking. If it removes all of them, nothing fails and the model loses its earlier reasoning on every turn. If you parse the stream yourself, keep the block even when no thinking text arrives: it opens, receives its `signature` in a `signature_delta` event, and closes. A block sent back with an empty `signature` fails.
813
704814### Add instructions with a mid-conversation system message
705815
706816Some harnesses rebuild the top-level `system` prompt on each request to carry the current time, a token budget, a mode flag, or newly discovered project context. That invalidates every thinking block in the conversation. Instead, freeze `system` at session start. When something changes, append a [`role: "system"` message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) at the point in `messages` where the change becomes true:
from line 824
714824
715825The model treats this message with system-prompt authority, and everything before it stays unchanged. In a tool loop, place the message after the `tool_result` user message, never between an assistant `tool_use` and its `tool_result` (see [Limitations](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#limitations)). Once sent, the message is part of the prefix for later thinking: leave it in place on later requests.
716826
827### Put changing context in the newest turn
828
829Some harnesses put an environment block in the first user message (working directory, branch, date, memory, project instructions) and render it again on every request. When any value changes, `messages[0]` changes, and every thinking block in the conversation is invalid. Render that block once and resend it as it was. When a value changes, say so in the newest turn: add a text block to the user message you are about to send, or append a [mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#new-instructions) if the change comes from you as the operator.
830
831```json
832{
833 "role": "user",
834 "content": [
835 {
836 "type": "text",
837 "text": "Environment update: the current branch is now release-2."
838 },
839 { "type": "text", "text": "Run the tests again." }
840 ]
841}
842```
843
844Once sent, that text block is part of the prefix for later thinking: leave it in place on later requests.
845
717846### Send per-turn reminders as turn-scoped system messages
718847
719The most common prefix edit is the per-turn nudge: a line such as "request independent reads together" or "you haven't updated the user in a while" that your code appends after each batch of tool results. To keep reminders from piling up, send each nudge as a [mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) with `clear_at: "next_user_message"`, placed after the `tool_result` user message. `clear_at` requires the beta header `mid-conversation-system-clear-at-2026-08-21`. The following `messages` array is the request after two tool calls and their results. `messages[3]` is the previous request's nudge, left in place, and `messages[6]` is this request's copy:
848A common prefix edit is the per-turn nudge: a line such as "request independent reads together" or "you haven't updated the user in a while" that your code appends after each batch of tool results. To keep reminders from piling up, send each nudge as a [mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) with `clear_at: "next_user_message"`, placed after the `tool_result` user message. `clear_at` requires the beta header `mid-conversation-system-clear-at-2026-08-21`. The following `messages` array is the request after two tool calls and their results. `messages[3]` is the previous request's nudge, left in place, and `messages[6]` is this request's copy:
720849
721850```json
722851[
from line 939
810939
811940### Trim context on the server
812941
813The second most common prefix edit is client-side trimming: dropping or summarizing the oldest turns and keeping the recent ones verbatim. The kept turns' thinking blocks were produced while the removed history was still in place, so they fail the check. The server-side equivalents don't count as edits, because the check compares the conversation as you sent it:
942Another common prefix edit is client-side trimming: dropping or summarizing the oldest turns and keeping the recent ones verbatim. The kept turns' thinking blocks were produced while the removed history was still in place, so they fail the check. The server-side equivalents don't count as edits, because the check compares the conversation as you sent it:
814943
815944* [Compaction](https://platform.claude.com/docs/en/build-with-claude/compaction) summarizes older turns into a compaction block when the context approaches a threshold you set, and the checked prefix restarts from that block. Its [`instructions` parameter](https://platform.claude.com/docs/en/build-with-claude/compaction#custom-summarization-instructions) takes your own summarization prompt, such as "preserve every ticker, position size, and stated assumption".
816945* [Context editing](https://platform.claude.com/docs/en/build-with-claude/context-editing) clears old tool results or old thinking blocks by rule, oldest first. The strategies are `clear_tool_uses_20250919` and `clear_thinking_20251015`.
from line 1194
10651194
10661195For an `image` or `document` block with a `url` source, the check covers the fetched bytes, not the URL string. A URL whose content changes invalidates later thinking: a "latest screenshot" endpoint, or a document someone edits between turns. A rotating signed URL for the same file doesn't. For content you reference across turns, upload it once with the [Files API](https://platform.claude.com/docs/en/build-with-claude/files) and use the `file_id`, or send base64.
10671196
1197### Libraries, proxies, and gateways
1198
1199A library, proxy, or gateway sits between someone else's history and the API, so its own rewrites count as edits, and its users can't see or fix them.
1200
1201* **Pass through what you don't recognize.** Forward the caller's `anthropic-beta` values and `thinking.block_binding` unchanged, and return `input_transformations` to them. An options schema that rejects unknown keys stops your users from choosing `"drop_block"`.
1202* **Leave a `role: "system"` message where the caller put it.** Moving it into the top-level `system` field changes `system` on that request and invalidates every thinking block in the conversation.
1203* **To turn tool use off for a request, send `tool_choice: {"type": "none"}`.** Don't remove `tools`.
1204* **Don't hide the 400.** If your code catches it, strips thinking, and retries on the caller's behalf, log that it did: their history is still edited, and the model loses its earlier reasoning on every later request.
1205
10681206## FAQ
10691207
10701208<AccordionGroup>
from line 1231
10931231 </Accordion>
10941232
10951233 <Accordion title="Can I resume a saved session later, after a restart or the next day?">
1096 Yes. A resumed session is an ordinary follow-up request: `system`, `tools`, and the earlier `messages` must match what you last sent byte-for-byte. Persist exactly what you sent and received, and replay that: the rendered system prompt, the tool definitions, and each assistant turn as returned. Don't re-render from inputs that might have changed since, such as the date, an updated instruction file, or a new tool version. Anything new goes in an appended message. See [Send assistant turns back exactly as returned](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#append-assistant-turns-exactly-as-returned).
1234 Yes. A resumed session is an ordinary follow-up request: `system`, `tools`, and the earlier `messages` must have the same content as what you last sent. JSON formatting and key order don't matter; the values do. Persist exactly what you sent and received, and replay that: the rendered system prompt, the tool definitions, and each assistant turn as returned. Don't re-render from inputs that might have changed since, such as the date, an updated instruction file, or a new tool version. Anything new goes in an appended message. See [Send assistant turns back exactly as returned](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#append-assistant-turns-exactly-as-returned).
1235 </Accordion>
1236
1237 <Accordion title="A saved session now fails on every request. How do I get it working again?">
1238 The stored history has an edit in it, so replaying it can't succeed. Send that session with `prefix_mismatch_behavior: "drop_block"` from now on, or remove its `thinking` and `redacted_thinking` blocks once and continue. Thinking the model produces from that point on stays valid as long as nothing before it changes again. Then find the edit so that new sessions don't hit it. See [Handle the error in code](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#handle-the-error-in-code).
10971239 </Accordion>
10981240
10991241 <Accordion title="My harness can route a turn to a non-Claude model. Do those turns invalidate Claude's earlier thinking?">