Guest User

Untitled

a guest
Mar 30th, 2026
33
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.32 KB | None | 0 0
  1. # Claude Code 缓存 Bug 验证报告
  2.  
  3. ## 验证环境
  4.  
  5. - 当前系统 Claude Code: v2.1.84 (ELF standalone, 228MB, Bun v1.3.11 commit 759ce802)
  6. - 对比版本: v2.1.68 (237MB), v2.1.69 (235MB)
  7. - 分析方法: 二进制静态分析 (strings/objdump/readelf), JS bundle 提取对比, 模拟哈希计算
  8.  
  9. ---
  10.  
  11. ## Bug 1: Sentinel Replacement (cch=00000)
  12.  
  13. ### analyze.md 的声明
  14.  
  15. > standalone 二进制中存在 native-layer (Zig) 字符串替换,在 HTTP 请求发送前扫描 JSON body,
  16. > 将第一个 `cch=00000` 替换为基于 body 哈希的 5 位 hex。当对话内容包含该 sentinel 时,
  17. > 替换命中 messages[] 而非 system[],导致缓存失效。
  18.  
  19. ### 验证结果: 部分验证 / 核心声明无法从静态分析确认
  20.  
  21. #### 已确认的部分
  22.  
  23. 1. **`cch=00000` sentinel 确实存在** — 在三个版本(v2.1.68/69/84)中均出现 3 次,全部位于 `.bun` JS bundle 内:
  24.  
  25. ```
  26. L = " cch=00000;" // 硬编码在 attribution header builder 中
  27. ```
  28.  
  29. 2. **Attribution header 的实际用途是 system prompt block** — 并非 HTTP header,而是作为 system[0] 的文本内容注入:
  30.  
  31. ```javascript
  32. // 函数 M0$() 构建如下字符串:
  33. `x-anthropic-billing-header: cc_version=${VERSION}.${hash}; cc_entrypoint=${entrypoint}; cch=00000;`
  34. // 该字符串被 push 到 system prompt blocks,带有 cacheScope: null
  35. ```
  36.  
  37. 3. **cc_version 哈希计算机制已确认** — 使用 SHA-256 从第一条 user message 的第 4、7、20 位字符计算:
  38.  
  39. ```javascript
  40. function sGA(H, $) {
  41. let L = [4, 7, 20].map((_) => H[_] || "0").join("");
  42. let D = `59cf53e54c78${L}${$}`; // salt + chars + version
  43. return crypto.createHash("sha256").update(D).digest("hex").slice(0, 3);
  44. }
  45. ```
  46.  
  47. 4. **billing header 使用 `cacheScope: null`** — 意味着该 block 本身不设缓存断点,但其内容变化仍会破坏后续 block 的前缀缓存。
  48.  
  49. #### 无法确认的部分
  50.  
  51. 5. **native-layer (Zig) 字符串替换:未找到证据**
  52. - 搜索整个 native code section (0x00000000 - 0x06568000),`cch=` 模式出现 **0 次**
  53. - 二进制符号表中无 HTTP body scanning 或 sentinel replacement 相关函数
  54. - `wyhash` 存在于 native code(标准 Bun 功能),但无证据表明用于 sentinel 替换
  55. - 嵌入的 Bun v1.3.11 (759ce802) 可能是定制 fork,但静态分析无法证实 HTTP 层是否被修改
  56.  
  57. 6. **"5-char hex derived from hashing the body" 的声明与 JS 代码不一致**
  58. - JS 代码中的哈希是 3 字符 (`hex.slice(0,3)`),用于 `cc_version` 后缀
  59. - `cch=00000` 的 `00000` 是 5 个零,若被替换应是 5 字符 hex
  60. - 两者机制不同,可能确实存在 native 层额外替换 `00000` 的逻辑,但无法通过静态分析验证
  61.  
  62. ### Bug 1 结论
  63.  
  64. JS 层的 attribution header 和 billing hash 机制完全可验证。但 **native-layer 替换** 这一核心声明在二进制静态分析中找不到直接证据。验证此声明需要运行时流量捕获 (MITM proxy) 或 Ghidra 深度反编译。
  65.  
  66. ---
  67.  
  68. ## Bug 2: --resume 每次都破坏缓存 (since v2.1.69)
  69.  
  70. ### analyze.md 的声明
  71.  
  72. > v2.1.69 引入 deferred_tools_delta,导致 fresh session 和 resume session 的 messages[0] 内容不同。
  73. > 三个独立的缓存破坏因素: messages[0] 大小差异、system[0] billing hash 变化、cache_control 断点位置变化。
  74.  
  75. ### 验证结果: 强烈确认 ✓
  76.  
  77. #### 版本对比数据
  78.  
  79. | 指标 | v2.1.68 | v2.1.69 | v2.1.84 |
  80. |------|---------|---------|---------|
  81. | `deferred_tools_delta` 出现次数 | **0** | **14** | **16** |
  82. | `cch=00000` 出现次数 | 3 | 3 | 3 |
  83. | `[4,7,20]` 哈希计算 | 2 | 2 | 2 |
  84. | `cacheScope:null` | 10 | 10 | — |
  85. | `cacheScope:"org"` | 8 | 8 | — |
  86.  
  87. #### 确认的机制
  88.  
  89. 1. **`deferred_tools_delta` 在 v2.1.69 中引入** — v2.1.68 中完全不存在该字符串(grep count = 0),v2.1.69 中出现 14 次。这完全吻合 analyze.md 的声明。
  90.  
  91. 2. **`deferred_tools_delta` 的实际功能** — 列出通过 ToolSearch 可用的 deferred tools:
  92.  
  93. ```javascript
  94. case "deferred_tools_delta": {
  95. if (H.addedLines.length > 0)
  96. A.push(`The following deferred tools are now available via ToolSearch:\n${H.addedLines.join('\n')}`);
  97. // ...
  98. return qM([AA({content: A.join('\n\n'), isMeta: true})]);
  99. }
  100. ```
  101.  
  102. 3. **messages[0] 内容差异已验证** — 通过哈希计算模拟:
  103. - Fresh session: 第一条 user message 包含 system-reminders (~13KB),chars at [4,7,20] = 例如 `t`, `-`, `n`
  104. - Resume session: 第一条 user message 仅包含 AU$ context (~352B),chars at [4,7,20] = 例如 `s`, `e`, `t`
  105. - 计算结果: Fresh hash = `360`, Resume hash = `153` — **完全不同**
  106.  
  107. 4. **cc_version 差异导致 system[0] 内容变化**:
  108. - Fresh: `cc_version=2.1.84.360`
  109. - Resume: `cc_version=2.1.84.153`
  110. - system[0] (billing header) 内容改变 → 后续所有 block 的缓存前缀失效
  111.  
  112. 5. **cache_control 断点位置移动** — 在 fresh session 中,`cache_control` 设置在 messages[0] 上;resume 时,由于 messages[0] 内容不同,断点实际上无法匹配。
  113.  
  114. #### 根因分析
  115.  
  116. 在 fresh session 中,所有 system-reminder 附件(包括 deferred_tools_delta、MCP instructions、skills list)在第一次 API 调用前被注入到 messages[0](第一条 user message)中。
  117.  
  118. 在 resume session 中,对话历史从磁盘加载,messages[0] 保持原始内容(仅有 AU$ context)。新计算的 deferred_tools_delta 被追加到最新的 message position(messages[N]),而不是 messages[0]。
  119.  
  120. 这导致:
  121. - messages[0] 的文本内容不同 → cc_version billing hash 不同 → system[0] 内容不同
  122. - 整个缓存前缀失效,~500k tokens 全部重建
  123.  
  124. ### Bug 2 结论
  125.  
  126. **完全确认**。analyze.md 的描述准确无误:
  127. - `deferred_tools_delta` 确实在 v2.1.69 引入
  128. - resume 确实导致 messages[0] 内容差异
  129. - 差异确实通过 billing hash ([4,7,20] 位置) 传播到 system[0]
  130. - 这确实导致整个缓存前缀失效
  131.  
  132. ---
  133.  
  134. ## 总结
  135.  
  136. | Bug | 状态 | 置信度 |
  137. |-----|------|--------|
  138. | Bug 1 (sentinel replacement) | JS 层机制确认;native 层替换无静态证据 | 60% |
  139. | Bug 2 (--resume cache break) | **完全确认** | **95%** |
  140.  
  141. ---
  142.  
  143. ## 补充分析: 直接 API 调用 vs Claude Code SDK 是否受影响
  144.  
  145. ### 直接使用 Anthropic API SDK (Python/JS)
  146.  
  147. **不受影响。**
  148.  
  149. 直接使用 `anthropic` Python 或 JS SDK 调用 Claude 模型的用户完全不受此 bug 影响。原因:
  150. - SDK 用户自行构建 `messages[]` 和 `system[]` 数组
  151. - 不存在 `deferred_tools_delta` 附件注入
  152. - 不存在 `cch=00000` sentinel 或 billing hash 机制
  153. - 缓存策略由用户通过 `cache_control` 参数完全控制
  154.  
  155. ### 使用 Claude Code Agent SDK (@anthropic-ai/claude-code)
  156.  
  157. **受影响。** 代码流分析显示:
  158.  
  159. 1. **共享代码路径**: `DQ_` 函数负责计算所有 turn 的附件(包括 `deferred_tools_delta`),**无 `isNonInteractive` 条件判断**:
  160.  
  161. ```javascript
  162. // 附件计算函数 - CLI 和 SDK 共用
  163. async function DQ_(H, $, A, L, D, f) {
  164. if (CLAUDE_CODE_DISABLE_ATTACHMENTS || CLAUDE_CODE_SIMPLE) return [];
  165. // ... 没有 isNonInteractive 检查 ...
  166. gK("deferred_tools_delta", () => Promise.resolve(CV$(...))),
  167. // ...
  168. }
  169. ```
  170.  
  171. 2. **billing hash 也在共享路径**: `ixD(Z)` → `M0$(k)` 链条在 CLI 和 SDK 中都执行:
  172.  
  173. ```javascript
  174. // system prompt 构建 - 同时传入 isNonInteractiveSession
  175. $ = G9([M0$(k), K0$({isNonInteractive: f.isNonInteractiveSession, ...}), ...])
  176. ```
  177.  
  178. `isNonInteractive` 仅影响 identity 字符串选择("You are Claude Code..." vs "You are a Claude agent..."),**不跳过 billing hash 计算**。
  179.  
  180. 3. **`resumeSession` 在 SDK 中存在** (23 处引用),意味着 SDK 用户使用 resume 功能时同样会触发 Bug 2。
  181.  
  182. ### API Key vs OAuth 登录
  183.  
  184. 在 Claude Code 中配置 API key(而非 OAuth 登录)**同样受影响**。代码分析确认:
  185.  
  186. - `vL()` 对 API key 和 OAuth 用户都返回 `"firstParty"`(都请求 api.anthropic.com)
  187. - `rX6()` (控制 attribution header 生成) 不区分认证方式
  188. - `DQ_()` (附件计算,包括 `deferred_tools_delta`) 不区分认证方式
  189. - `VUH()` (`deferred_tools_delta` 的特性开关) 是服务端 feature flag,非认证相关
  190.  
  191. 因此,**无论 API key 还是 OAuth,在 Claude Code 中运行的代码路径完全相同**。Bug 2 对 API key 用户同样生效。
  192.  
  193. 实际上,对 API key 用户影响**更大**:因为 API key 按 token 计费,缓存失效直接增加费用(`cache_creation` \$0.30/MTok vs `cache_read` \$0.03/MTok,10x 差异)。
  194.  
  195. ### 结论
  196.  
  197. | 使用方式 | Bug 1 影响 | Bug 2 影响 |
  198. |----------|-----------|-----------|
  199. | 直接使用 Anthropic SDK (不经过 Claude Code) | ❌ 不受影响 | ❌ 不受影响 |
  200. | Claude Code CLI (OAuth 登录) | ⚠️ 可能受影响 | ✅ 受影响 |
  201. | Claude Code CLI (API key) | ⚠️ 可能受影响 | ✅ 受影响,且费用影响更直接 |
  202. | Claude Code Agent SDK | ⚠️ 可能受影响 | ✅ 受影响 (使用 resume 时) |
  203.  
  204. ---
  205.  
  206. ### 进一步验证建议
  207.  
  208. - **Bug 1**: 使用 mitmproxy 捕获 standalone 二进制 vs npm 版本的实际 HTTP 请求,对比 `cch=` 字段是否被替换
  209. - **Bug 2**: 无需进一步验证,代码证据充分
  210.  
Add Comment
Please, Sign In to add comment