現象與挑戰

在同一台機器、同一個 API key、同一個對話歷史,Turn 2 的快取表現: 直連 API → cache_read: 63695;經 proxy → cache_read: 0。 兩者差距剛好是全部的 token 數,不是部分 miss,是全滅。

麻煩在於:你不能直接看 proxy 在傳什麼。Claude Code 的 request body 體積很大(整個上下文 + tools),我需要能在不動 proxy 邏輯的前提下, 完整捕捉每次 Claude Code 送出的 bytes,之後再慢慢分析。

步驟一:錄音 proxy

不動原來的 M7 proxy,在前面再加一個「錄音 proxy」: 標準函式庫的 http.server + urllib,攔截每個請求, 把 request body 與 headers 分開存成 JSON 檔,然後原封不動轉發給 M7。 憑證欄位不落地——只記 body,headers 另存一份(authentication 欄位單獨排除)。

錄音 proxy 本身不做任何轉換,所以快取命中率跟直連幾乎一致—— 這讓錄音 proxy 也成為「乾淨對照組」,同時收下兩批資料: Claude Code 真實送出的 bytes,以及 M7 轉換後送到 API 的 bytes。

步驟二:離線重放

有了錄好的 body 之後,可以離線重放:把每個 body 餵進 pipeline(模擬 M7 的轉換), 得到轉換後的版本,再用 curl 帶完整 headers 重打 API,觀察快取命中。

重放時有一個關鍵細節:必須帶上 Claude Code 本身傳的 anthropic-beta header,包含 context-managementprompt-caching-scope 等 feature flag,少一個就會直接收到 400。 這些 flag 決定了 API 端如何解析 cache_control 欄位,差一個 flag 就是不同的快取行為。

步驟三:stage bisect

M7 pipeline 有 4 個轉換步驟:stabilize → compress → register → serialize。 把 pipeline 切成幾段,每段單獨重放,看哪段開始讓快取掛掉。

結果很快收斂:「只做 register」的 pair 重放 → 快取全滅; 「只做 stabilize + compress(不 register)」的 pair → 快取命中正常。 兇手鎖定是 register_ccr_tool——在 tools 陣列末端加了 1 個工具。

兇手是 1 個工具,但為什麼?

Anthropic 的 prefix cache 按渲染序逐 byte 比對前綴, tools 是前綴最前面的一段。多了 1 個工具,整段 tools 就不同了, 後面的 system prompt 和 messages 雖然一樣,也救不回來。

更耐人尋味的是:Claude Code 自己發出的跨 process 流量, 即便 Agent tool description 的 agent 清單每次啟動順序都不一樣(2588 bytes 差異), API 端還是給了 ~30k 的部分命中——這個容錯機制的細節不公開, 疑似跟 prompt-caching-scope-2026-01-05 beta 有關。 但 register_ccr_tool 增加的那 1 個工具, 偏偏破壞了這個容錯:部分命中從 30k 變成 0。

差點下錯結論的實驗

調查中間,對「只做 reserialize(不改邏輯、只把 JSON 解析再序列化)」的 pair 重放, 結果快取全命中,一度以為「API 做的是 content-level hash、格式不影響快取」。

事後才驗清楚:Claude Code 本來就送 compact JSON,Python round-trip 後的 bytes 跟原始完全一樣(差 2 bytes 是 streaming flag), 那次命中只是「用同樣的 bytes 命中了自己剛剛打進去的快取」。 根本沒有任何「格式容忍」——是前提條件不成立,導致實驗失去意義。

關鍵教訓

錄音 proxy 讓偵錯和執行分離:需要捕捉憑證敏感的 HTTP 流量時,「先錄下 body,之後離線重放」比「直接 debug live 流量」更安全、更可重現。

下結論前先確認對照組真的有差異:「reserialize 命中了」看起來是結論,但前提是 bytes 要不同。bytes 完全相同時,命中的是自己——不是 API 的格式容忍。

stage bisect 把問題縮到最小:4 個步驟不用逐一猜,切成兩半驗一半,幾輪就能鎖定。把 pipeline 設計成可單獨重放是關鍵前提。

cache 容錯邊界要實測,不能靠推理:Claude Code 跨 process 有 ~30k 部分命中,但多 1 個工具就讓它歸零。這種非線性的容錯邊界,只有實驗才找得到。

來源:個人開發日誌 2026-06-12 · headroom 學習分支 · M7 wrap Claude Code 實測 · cache_read 63695 → 0 bug 調查