From 26e5efbbdf6739eafa782cb98225e3fc2fbea99c Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sun, 4 Oct 2026 21:00:20 +0800 Subject: [PATCH] fix(claude): reserve cache breakpoint for thread continuation - Decrement `maxBlocks` when a `thread` field is present in payload to reserve a cache breakpoint for Anthropic thread continuation Closes: #6338 --- .../executor/claude_executor_cloaking.go | 6 ++++++ .../runtime/executor/claude_executor_test.go | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/internal/runtime/executor/claude_executor_cloaking.go b/internal/runtime/executor/claude_executor_cloaking.go index e482119ce..14bcd5b3d 100644 --- a/internal/runtime/executor/claude_executor_cloaking.go +++ b/internal/runtime/executor/claude_executor_cloaking.go @@ -1876,6 +1876,12 @@ func enforceCacheControlLimit(payload []byte, maxBlocks int) []byte { return payload } + thread := gjson.GetBytes(payload, "thread") + if thread.Exists() && thread.Type != gjson.Null && maxBlocks > 0 { + // Anthropic reserves one cache breakpoint for thread continuation. + maxBlocks-- + } + total := countCacheControls(payload) if total <= maxBlocks { return payload diff --git a/internal/runtime/executor/claude_executor_test.go b/internal/runtime/executor/claude_executor_test.go index 666a63ab3..da361df6b 100644 --- a/internal/runtime/executor/claude_executor_test.go +++ b/internal/runtime/executor/claude_executor_test.go @@ -3240,6 +3240,27 @@ func TestEnforceCacheControlLimit_PreservesKeyOrderWhenModified(t *testing.T) { } } +func TestEnforceCacheControlLimit_ReservesThreadMarker(t *testing.T) { + payload := []byte(`{ + "thread": {"type":"create"}, + "tools": [{"name":"t1","cache_control":{"type":"ephemeral"}}], + "system": [ + {"type":"text","text":"s1","cache_control":{"type":"ephemeral"}}, + {"type":"text","text":"s2","cache_control":{"type":"ephemeral"}} + ], + "messages": [{"role":"user","content":[ + {"type":"text","text":"u1","cache_control":{"type":"ephemeral"}}, + {"type":"text","text":"u2","cache_control":{"type":"ephemeral"}} + ]}] + }`) + + out := enforceCacheControlLimit(payload, 4) + + if got := countCacheControls(out); got != 3 { + t.Fatalf("cache_control count = %d, want 3 when thread is present", got) + } +} + func TestEnforceCacheControlLimit_ToolOnlyPayloadStillRespectsLimit(t *testing.T) { payload := []byte(`{ "tools": [