Follow Discord
Sweep 10 Nov 2026 · 02:18Z Build v2.1.322 539 read Stable v2.1.311 Latest v2.1.322 Next v2.1.322 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · api

cache-diagnostics changedbuild-with-claude/cache-diagnostics

Recorded here
Lines+252added
Lines−263removed
From line 33 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits22to this page, all time

The whole hunk

from line 33, old and new numbered
/
lines

The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.

from line 33
3333 
3434[Prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) cuts latency and cost significantly, but only when the beginning of your prompt is byte-for-byte identical to a recent request. A reordered tool, a timestamp interpolated into your system prompt, or an edit to an earlier message can silently invalidate the cache. Without cache diagnostics, the only signal is `usage.cache_read_input_tokens` dropping to zero, with no indication of what changed.
3535 
36Cache diagnostics closes that gap. Pass the `id` of your previous response, and the API compares the two requests and tells you where they diverged (the model, the system prompt, the tools, or the message history) so you can fix the root cause instead of guessing.
36Cache diagnostics closes that gap. Pass the `id` of your previous response, and the API compares the two requests. It tells you where they diverged (the model, the system prompt, the tools, or the message history), so you can fix the root cause instead of guessing.
3737 
3838## How cache diagnostics works
3939 
from line 45
4545 
4646## Basic usage
4747 
48Include the `diagnostics` object on every turn. The object is the opt-in: the API stores a fingerprint only for requests that include it. On the first turn, pass `"previous_message_id": null` to opt in without a prior message to compare against. On subsequent turns, pass the `id` from the previous response. The `cache-diagnosis-2026-04-07` beta header is no longer required, and requests that send it work as before.
48Include the `diagnostics` object on every turn. The object is the opt-in: the API stores a fingerprint only for requests that include it. On the first turn, pass `"previous_message_id": null` to opt in without a prior message to compare against. On subsequent turns, pass the `id` from the previous response.
4949 
5050<CodeGroup>
5151 ```bash cURL
from line 372
372372 SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>"
373373 
374374 r1 = client.beta.messages.create(
375 model: :"claude-opus-5-5",
375 model: Anthropic::Model::CLAUDE_OPUS_5_5,
376376 max_tokens: 1024,
377377 cache_control: {type: "ephemeral"},
378378 system_: SYSTEM,
from line 383
383383 )
384384 
385385 r2 = client.beta.messages.create(
386 model: :"claude-opus-5-5",
386 model: Anthropic::Model::CLAUDE_OPUS_5_5,
387387 max_tokens: 1024,
388388 cache_control: {type: "ephemeral"},
389389 system_: SYSTEM,
from line 676
676676 ```ruby Ruby
677677 # Turn 2: stream, referencing the previous response id
678678 stream = client.beta.messages.stream(
679 model: :"claude-opus-5-5",
679 model: Anthropic::Model::CLAUDE_OPUS_5_5,
680680 max_tokens: 1024,
681681 cache_control: {type: "ephemeral"},
682682 system_: SYSTEM,
from line 713
713713 
714714In a multi-turn conversation, carry the latest response `id` forward as `previous_message_id` on every turn. The first iteration passes `null` to opt in; each subsequent iteration passes the `id` from the previous response.
715715 
716<Tabs>
717 <Tab title="cURL">
718 <Info>
719 This workflow doesn’t translate well to a one-off shell command. Use the Claude SDK to run the loop; the per-turn HTTP request is identical to [Basic usage](https://platform.claude.com/docs/en/build-with-claude/cache-diagnostics#basic-usage).
720 </Info>
721 </Tab>
722 
723 <Tab title="CLI">
724 <Info>
725 This workflow doesn’t translate well to a one-off shell command. Use the Claude SDK to run the loop; the per-turn CLI command is identical to [Basic usage](https://platform.claude.com/docs/en/build-with-claude/cache-diagnostics#basic-usage).
726 </Info>
727 </Tab>
728 
729 <Tab title="Python">
730 ```python
731 client = anthropic.Anthropic()
732 
733 SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>"
734 
735 messages = []
736 prev_id = None
737 
738 for i, user_message in enumerate(
739 ["Summarize section 1.", "Now section 2.", "Now section 3."]
740 ):
741 messages.append({"role": "user", "content": user_message})
742 
743 r = client.beta.messages.create(
744 model="claude-opus-5-5",
745 max_tokens=1024,
746 cache_control={"type": "ephemeral"},
747 system=SYSTEM,
748 messages=messages,
749 diagnostics={"previous_message_id": prev_id},
750 )
751 
752 if r.diagnostics is not None and r.diagnostics.cache_miss_reason is not None:
753 print(f"Turn {i + 1} cache_miss_reason: {r.diagnostics.cache_miss_reason.type}")
754 
755 messages.append({"role": "assistant", "content": r.content})
756 prev_id = r.id
757 ```
758 </Tab>
759 
760 <Tab title="TypeScript">
761 ```typescript
762 const client = new Anthropic();
763 
764 const SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>";
765 
766 const prompts = ["Summarize section 1.", "Now section 2.", "Now section 3."];
767 
768 const messages: BetaMessageParam[] = [];
769 let prevId: string | null = null;
770 
771 for (const [i, prompt] of prompts.entries()) {
772 messages.push({ role: "user", content: prompt });
773 
774 const r: BetaMessage = await client.beta.messages.create({
775 model: "claude-opus-5-5",
776 max_tokens: 1024,
777 cache_control: { type: "ephemeral" },
778 system: SYSTEM,
779 messages,
780 diagnostics: { previous_message_id: prevId }
781 });
782 
783 if (r.diagnostics?.cache_miss_reason) {
784 console.log(`Turn ${i + 1} cache_miss_reason: ${r.diagnostics.cache_miss_reason.type}`);
716<CodeGroup exclude="shell:cURL, shell:CLI">
717 <CodeGroupItem language="cURL">
718 This workflow needs more logic than a single shell command. Use the Anthropic SDK to run the loop. Each turn sends the same HTTP request as in [Basic usage](https://platform.claude.com/docs/en/build-with-claude/cache-diagnostics#basic-usage).
719 </CodeGroupItem>
720 
721 <CodeGroupItem language="CLI">
722 This workflow needs more logic than a single shell command. Use the Anthropic SDK to run the loop. Each turn sends the same request as in [Basic usage](https://platform.claude.com/docs/en/build-with-claude/cache-diagnostics#basic-usage).
723 </CodeGroupItem>
724 
725 ```python Python
726 client = anthropic.Anthropic()
727 
728 SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>"
729 
730 messages = []
731 prev_id = None
732 
733 for turn, user_message in enumerate(
734 ["Summarize section 1.", "Now section 2.", "Now section 3."]
735 ):
736 messages.append({"role": "user", "content": user_message})
737 
738 response = client.beta.messages.create(
739 model="claude-opus-5-5",
740 max_tokens=1024,
741 cache_control={"type": "ephemeral"},
742 system=SYSTEM,
743 messages=messages,
744 diagnostics={"previous_message_id": prev_id},
745 )
746 
747 if (
748 response.diagnostics is not None
749 and response.diagnostics.cache_miss_reason is not None
750 ):
751 print(
752 f"Turn {turn + 1} cache_miss_reason: {response.diagnostics.cache_miss_reason.type}"
753 )
754 
755 messages.append({"role": "assistant", "content": response.content})
756 prev_id = response.id
757 ```
758 
759 ```typescript TypeScript
760 const client = new Anthropic();
761 
762 const SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>";
763 
764 const prompts = ["Summarize section 1.", "Now section 2.", "Now section 3."];
765 
766 const messages: BetaMessageParam[] = [];
767 let prevId: string | null = null;
768 
769 for (const [turn, prompt] of prompts.entries()) {
770 messages.push({ role: "user", content: prompt });
771 
772 const response: BetaMessage = await client.beta.messages.create({
773 model: "claude-opus-5-5",
774 max_tokens: 1024,
775 cache_control: { type: "ephemeral" },
776 system: SYSTEM,
777 messages,
778 diagnostics: { previous_message_id: prevId }
779 });
780 
781 if (response.diagnostics?.cache_miss_reason) {
782 console.log(
783 `Turn ${turn + 1} cache_miss_reason: ${response.diagnostics.cache_miss_reason.type}`
784 );
785 }
786 
787 messages.push({ role: "assistant", content: response.content });
788 prevId = response.id;
789 }
790 ```
791 
792 ```csharp C#
793 AnthropicClient client = new();
794 
795 var system = "You are an AI assistant analyzing a large document. <document>...</document>";
796 
797 List<BetaMessageParam> messages = [];
798 string? prevId = null;
799 string[] prompts = ["Summarize section 1.", "Now section 2.", "Now section 3."];
800 
801 for (int turn = 0; turn < prompts.Length; turn++)
802 {
803 messages.Add(new BetaUserMessageParam { Content = prompts[turn] });
804 
805 var response = await client.Beta.Messages.Create(
806 new()
807 {
808 Model = Messages::Model.ClaudeOpus5_5,
809 MaxTokens = 1024,
810 CacheControl = new(),
811 System = system,
812 Messages = messages,
813 Diagnostics = new() { PreviousMessageID = prevId },
814 }
815 );
816 
817 if (response.Diagnostics?.CacheMissReason is { Type: var type })
818 {
819 Console.WriteLine($"Turn {turn + 1} cache_miss_reason: {type.Raw()}");
785820 }
786821 
787 messages.push({ role: "assistant", content: r.content });
788 prevId = r.id;
789 }
790 ```
791 </Tab>
792 
793 <Tab title="C#">
794 ```csharp
795 AnthropicClient client = new();
796 
797 var system = "You are an AI assistant analyzing a large document. <document>...</document>";
798 
799 List<BetaMessageParam> messages = [];
800 string? prevId = null;
801 string[] prompts = ["Summarize section 1.", "Now section 2.", "Now section 3."];
802 
803 for (int i = 0; i < prompts.Length; i++)
804 {
805 messages.Add(new BetaUserMessageParam { Content = prompts[i] });
806 
807 var r = await client.Beta.Messages.Create(
808 new()
809 {
810 Model = Messages::Model.ClaudeOpus5_5,
811 MaxTokens = 1024,
812 CacheControl = new(),
813 System = system,
814 Messages = messages,
815 Diagnostics = new() { PreviousMessageID = prevId },
816 }
817 );
818 
819 if (r.Diagnostics?.CacheMissReason is { Type: var type })
820 {
821 Console.WriteLine($"Turn {i + 1} cache_miss_reason: {type.Raw()}");
822 }
823 
824 messages.Add(
825 new BetaAssistantMessageParam
826 {
827 Content = r.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
828 }
829 );
830 prevId = r.ID;
831 }
832 ```
833 </Tab>
834 
835 <Tab title="Go">
836 ```go
837 client := anthropic.NewClient()
838 ctx := context.Background()
839 
840 system := []anthropic.BetaTextBlockParam{
841 {Text: "You are an AI assistant analyzing a large document. <document>...</document>"},
842 }
843 
844 prompts := []string{"Summarize section 1.", "Now section 2.", "Now section 3."}
845 
846 var messages []anthropic.BetaMessageParam
847 prevID := param.Null[string]()
848 
849 for turn, prompt := range prompts {
850 messages = append(messages, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(prompt)))
851 
852 r, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
853 Model: anthropic.ModelClaudeOpus5_5,
854 MaxTokens: 1024,
855 CacheControl: anthropic.BetaCacheControlEphemeralParam{},
856 System: system,
857 Messages: messages,
858 Diagnostics: anthropic.BetaDiagnosticsParam{
859 PreviousMessageID: prevID,
860 },
861 })
862 if err != nil {
863 panic(err)
864 }
865 
866 if r.JSON.Diagnostics.Valid() && r.Diagnostics.JSON.CacheMissReason.Valid() {
867 fmt.Printf("Turn %d cache_miss_reason: %s\n", turn+1, r.Diagnostics.CacheMissReason.Type)
868 }
869 
870 messages = append(messages, r.ToParam())
871 prevID = anthropic.String(r.ID)
872 }
873 ```
874 </Tab>
875 
876 <Tab title="Java">
877 ```java
878 var client = AnthropicOkHttpClient.fromEnv();
879 
880 var system = "You are an AI assistant analyzing a large document. <document>...</document>";
881 var prompts = List.of("Summarize section 1.", "Now section 2.", "Now section 3.");
882 
883 var messages = new ArrayList<BetaMessageParam>();
884 String prevId = null;
885 
886 for (var turn = 0; turn < prompts.size(); turn++) {
887 messages.add(BetaMessageParam.ofUser(prompts.get(turn)));
888 
889 var r = client.beta().messages().create(
890 MessageCreateParams.builder()
891 .model(Model.CLAUDE_OPUS_5_5)
892 .maxTokens(1024)
893 .cacheControl(BetaCacheControlEphemeral.builder().build())
894 .system(system)
895 .messages(messages)
896 .diagnostics(BetaDiagnosticsParam.builder().previousMessageId(prevId).build())
897 .build()
898 );
899 
900 if (r.diagnostics().isPresent() && r.diagnostics().get().cacheMissReason().isPresent()) {
901 var reason = r.diagnostics().get().cacheMissReason().get();
902 IO.println("Turn " + (turn + 1) + " cache_miss_reason: " + reason.type().asString());
903 }
904 
905 messages.add(r.toParam());
906 prevId = r.id();
907 }
908 ```
909 </Tab>
910 
911 <Tab title="PHP">
912 ```php
913 $client = new Client();
914 
915 $system = 'You are an AI assistant analyzing a large document. <document>...</document>';
916 
917 $messages = [];
918 $prevId = null;
919 
920 foreach (['Summarize section 1.', 'Now section 2.', 'Now section 3.'] as $i => $userMsg) {
921 $turn = $i + 1;
922 $messages[] = ['role' => 'user', 'content' => $userMsg];
923 
924 $r = $client->beta->messages->create(
925 model: Model::CLAUDE_OPUS_5_5,
926 maxTokens: 1024,
927 cacheControl: new BetaCacheControlEphemeral,
928 system: $system,
929 messages: $messages,
930 diagnostics: (new BetaDiagnosticsParam)->withPreviousMessageID($prevId),
931 );
932 
933 if ($r->diagnostics?->cacheMissReason !== null) {
934 echo "Turn {$turn} cache_miss_reason: {$r->diagnostics->cacheMissReason->type}\n";
935 }
936 
937 $messages[] = ['role' => 'assistant', 'content' => $r->content];
938 $prevId = $r->id;
939 }
940 ```
941 </Tab>
942 
943 <Tab title="Ruby">
944 ```ruby
945 client = Anthropic::Client.new
946 
947 SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>"
948 
949 messages = []
950 prev_id = nil
951 
952 ["Summarize section 1.", "Now section 2.", "Now section 3."].each_with_index do |user_msg, i|
953 messages << {role: "user", content: user_msg}
954 
955 r = client.beta.messages.create(
956 model: :"claude-opus-5-5",
957 max_tokens: 1024,
958 cache_control: {type: "ephemeral"},
959 system_: SYSTEM,
960 messages: messages,
961 diagnostics: {previous_message_id: prev_id}
962 )
963 
964 if (reason = r.diagnostics&.cache_miss_reason)
965 puts "Turn #{i + 1} cache_miss_reason: #{reason.type}"
966 end
967 
968 messages << {role: "assistant", content: r.content}
969 prev_id = r.id
822 messages.Add(
823 new BetaAssistantMessageParam
824 {
825 Content = response.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
826 }
827 );
828 prevId = response.ID;
829 }
830 ```
831 
832 ```go Go
833 client := anthropic.NewClient()
834 ctx := context.Background()
835 
836 system := []anthropic.BetaTextBlockParam{
837 {Text: "You are an AI assistant analyzing a large document. <document>...</document>"},
838 }
839 
840 prompts := []string{"Summarize section 1.", "Now section 2.", "Now section 3."}
841 
842 var messages []anthropic.BetaMessageParam
843 prevID := param.Null[string]()
844 
845 for turn, prompt := range prompts {
846 messages = append(messages, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(prompt)))
847 
848 response, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
849 Model: anthropic.ModelClaudeOpus5_5,
850 MaxTokens: 1024,
851 CacheControl: anthropic.BetaCacheControlEphemeralParam{},
852 System: system,
853 Messages: messages,
854 Diagnostics: anthropic.BetaDiagnosticsParam{
855 PreviousMessageID: prevID,
856 },
857 })
858 if err != nil {
859 panic(err)
860 }
861 
862 if response.JSON.Diagnostics.Valid() && response.Diagnostics.JSON.CacheMissReason.Valid() {
863 fmt.Printf("Turn %d cache_miss_reason: %s\n", turn+1, response.Diagnostics.CacheMissReason.Type)
864 }
865 
866 messages = append(messages, response.ToParam())
867 prevID = anthropic.String(response.ID)
868 }
869 ```
870 
871 ```java Java
872 var client = AnthropicOkHttpClient.fromEnv();
873 
874 var system = "You are an AI assistant analyzing a large document. <document>...</document>";
875 var prompts = List.of("Summarize section 1.", "Now section 2.", "Now section 3.");
876 
877 var messages = new ArrayList<BetaMessageParam>();
878 String prevId = null;
879 
880 for (var turn = 0; turn < prompts.size(); turn++) {
881 messages.add(BetaMessageParam.ofUser(prompts.get(turn)));
882 
883 var response = client.beta().messages().create(
884 MessageCreateParams.builder()
885 .model(Model.CLAUDE_OPUS_5_5)
886 .maxTokens(1024)
887 .cacheControl(BetaCacheControlEphemeral.builder().build())
888 .system(system)
889 .messages(messages)
890 .diagnostics(BetaDiagnosticsParam.builder().previousMessageId(prevId).build())
891 .build()
892 );
893 
894 if (response.diagnostics().isPresent() && response.diagnostics().get().cacheMissReason().isPresent()) {
895 var reason = response.diagnostics().get().cacheMissReason().get();
896 IO.println("Turn " + (turn + 1) + " cache_miss_reason: " + reason.type().asString());
897 }
898 
899 messages.add(response.toParam());
900 prevId = response.id();
901 }
902 ```
903 
904 ```php PHP
905 $client = new Client();
906 
907 $system = 'You are an AI assistant analyzing a large document. <document>...</document>';
908 
909 $messages = [];
910 $prevId = null;
911 
912 foreach (['Summarize section 1.', 'Now section 2.', 'Now section 3.'] as $index => $userMsg) {
913 $turn = $index + 1;
914 $messages[] = ['role' => 'user', 'content' => $userMsg];
915 
916 $response = $client->beta->messages->create(
917 model: Model::CLAUDE_OPUS_5_5,
918 maxTokens: 1024,
919 cacheControl: new BetaCacheControlEphemeral,
920 system: $system,
921 messages: $messages,
922 diagnostics: (new BetaDiagnosticsParam)->withPreviousMessageID($prevId),
923 );
924 
925 if ($response->diagnostics?->cacheMissReason !== null) {
926 echo "Turn {$turn} cache_miss_reason: {$response->diagnostics->cacheMissReason->type}\n";
927 }
928 
929 $messages[] = ['role' => 'assistant', 'content' => $response->content];
930 $prevId = $response->id;
931 }
932 ```
933 
934 ```ruby Ruby
935 client = Anthropic::Client.new
936 
937 SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>"
938 
939 messages = []
940 prev_id = nil
941 
942 ["Summarize section 1.", "Now section 2.", "Now section 3."].each_with_index do |user_msg, turn|
943 messages << {role: "user", content: user_msg}
944 
945 response = client.beta.messages.create(
946 model: Anthropic::Model::CLAUDE_OPUS_5_5,
947 max_tokens: 1024,
948 cache_control: {type: "ephemeral"},
949 system_: SYSTEM,
950 messages: messages,
951 diagnostics: {previous_message_id: prev_id}
952 )
953 
954 if (reason = response.diagnostics&.cache_miss_reason)
955 puts "Turn #{turn + 1} cache_miss_reason: #{reason.type}"
970956 end
971 ```
972 </Tab>
973</Tabs>
957 
958 messages << {role: "assistant", content: response.content}
959 prev_id = response.id
960 end
961 ```
962</CodeGroup>
974963 
975964## Response format
976965 
from line 1001
10121001| Type | What it means | What to change |
10131002| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
10141003| `model_changed` | The `model` differs from the previous request (for example, a router, A/B test, or fallback selected a different model). The cache is per-model. | Hold the model constant within a cached conversation. |
1015| `system_changed` | The `system` parameter differs. Typically a timestamp, request ID, or other per-request value was interpolated into the system prompt. | Make the system prompt a byte-stable constant and move dynamic data into the first `user` message after your cache breakpoint. |
1004| `system_changed` | The `system` parameter differs. Typically a timestamp, request ID, or other per-request value was interpolated into the system prompt. | Make the system prompt a byte-stable constant and put the changing value in the `user` message you’re about to send. |
10161005| `tools_changed` | The `tools` array differs: tools were added, removed, or reordered between turns, or tool `input_schema` JSON was serialized non-deterministically. | Send the same tool list on every turn in a fixed order with deterministically serialized schemas (for example, sort keys). |
10171006| `messages_changed` | The model, system, and tools all match, but an earlier entry in `messages` was altered, reordered, or removed rather than appended to. Typically conversation history was truncated or edited, or assistant turns and `tool_result` blocks were re-serialized differently on resend. | Treat the history as append-only; echo assistant `content` and tool results back verbatim. |
10181007| `previous_message_not_found` | No stored fingerprint exists for the supplied `previous_message_id`. This is not evidence that your request changed. Typically the previous request did not include the `diagnostics` object, it came from a different workspace, or too much time has passed since it was sent. | Include the `diagnostics` object on every turn and keep consecutive turns close together in time. |
from line 1013
10241013 
10251014## Reading diagnostics alongside usage
10261015 
1027`diagnostics` answers “did my request change?” whereas `usage.cache_read_input_tokens` answers “did the cache hit?” Combining them tells you where to look.
1016`diagnostics` tells you whether your request changed, and `usage.cache_read_input_tokens` tells you whether the cache hit. Combining them tells you where to look.
10281017 
10291018This matrix applies to turns where you passed a real `previous_message_id`. On the first turn (`previous_message_id: null`), `diagnostics` is always `null` and `cache_read_input_tokens` is normally zero because the cache is being written, not read; no troubleshooting is needed. The matrix also does not apply when `cache_miss_reason` is `null` (the comparison is still pending; check the next turn) or when its `type` is `previous_message_not_found` or `unavailable` (no comparison was produced).
10301019 
from line 1026
10371026 
10381027## Limitations
10391028 
1040* **Claude API only:** Not available on Claude Models on Google Cloud (Google managed) or Amazon Bedrock.
1029* **Claude API only:** Cache diagnostics is available on the Claude API.
10411030* **Limited retention:** Fingerprints for `previous_message_id` lookup expire after a short period. Run diagnostic comparisons between closely spaced requests.
10421031* **Same workspace:** The previous request must have run in the same organization and workspace. To check, compare the `anthropic-workspace-id` [response header](https://platform.claude.com/docs/en/api/overview#response-headers) on the two responses.
10431032* **Comparison horizon:** For very long conversations where the only change is deep in the message list, the response might be `unavailable` rather than a precise location.
10441033 
Feedback