事情是这样的。
上周有人在 OpenRouter 代理上用 Claude Code,突然发现 token 消耗量翻了三倍。不是代码量变了,不是使用频率变了,而是每次请求的缓存全部失效。排查了一圈,罪魁祸首是 Claude Code 2.1.36 版本引入的一个改动——在系统提示词开头加入了一个随机的 cch 字段。
发生了什么
从 2.1.36 版本开始,Claude Code 会在每个 API 请求的系统提示词开头,注入一段 x-anthropic-billing-header 内容,其中包含一个随机生成的 5 位十六进制 cch 值。每次启动、每次请求,这个值都不一样。
对 Anthropic 官方 API 来说,这不是问题。官方后端有自己的缓存机制,不受提示词内容影响。
但第三方代理和 OpenRouter 这类中转服务不一样。它们通常把提示词内容本身作为缓存 key 来计算 KV cache 命中率。cch 每次都在变,意味着提示词的哈希值永远在变,缓存命中率直接归零。
"上周在 OpenRouter 代理上遇到了这个问题。在我找出原因之前,token 消耗量一夜之间翻了三倍。如果用量的话,还是直接走 Anthropic 官方 API 吧。第三方代理的意义就在于 prompt caching,结果这个功能直接被干掉了。"
后果
后果很直接:
token 消耗暴涨。每次请求都要重新处理完整的系统提示词,预填充阶段无法复用缓存,推理速度变慢。对于依赖本地模型(llama.cpp、LM Studio)的用户来说,影响更明显——本地后端本来就是为了复用前缀缓存来加速的,提示词变了,优势就没了。
DataMoat 的文章详细解释了本地模型的配置方案,核心思路也是一样的:减少提示词的动态变化,保持前缀稳定。
怎么解决
Anthropic 提供了一个环境变量来关闭这个行为:
设置 CLAUDE_CODE_ATTRIBUTION_HEADER=0 即可关闭 cch 字段。
完整的推荐启动命令:
CLAUDE_CODE_ATTRIBUTION_HEADER=0 \ claude \ --exclude-dynamic-system-prompt-sections \ --settings '{"includeGitInstructions":false}'
三个措施的作用:
CLAUDE_CODE_ATTRIBUTION_HEADER=0 去掉随机的 cch 字段,这是解决缓存失效的核心。
--exclude-dynamic-system-prompt-sections 排除动态系统提示词段落,减少每次会话的变化。
--settings '{"includeGitInstructions":false}' 关闭 git 相关指令的注入,进一步稳定提示词内容。
如果你用的是本地模型(比如 llama-server),还可以加上更多环境变量:
ANTHROPIC_BASE_URL=http://127.0.0.1:8080 \ ANTHROPIC_API_KEY=no-key \ ANTHROPIC_MODEL=your-model.gguf \ CLAUDE_CODE_ATTRIBUTION_HEADER=0 \ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ DISABLE_TELEMETRY=1 \ DISABLE_ERROR_REPORTING=1 \ claude --bare \ --model your-model.gguf \ --exclude-dynamic-system-prompt-sections \ --settings '{"includeGitInstructions":false}'
写在最后
这个改动本身是 Anthropic 为了计费追踪加上的,本身没有恶意。但它没有考虑到第三方代理和本地模型用户的使用场景,直接导致了缓存机制的失效。
对于大量使用 Claude Code 的团队来说,缓存命中率归零意味着成本的直接上升。如果你也在用第三方代理或者本地模型,建议尽快加上 CLAUDE_CODE_ATTRIBUTION_HEADER=0。
毕竟,谁也不想莫名其妙多付三倍的 token 费用。