问题背景
当前「计费标头整流器」只有一个全局开关 enableBillingHeaderRectifier(默认开启)。它会在每次 claude / claude-auth 请求发往上游前,无条件从 system 中移除 x-anthropic-billing-header: ... 文本块:
这里的 x-anthropic-billing-header 并不是 HTTP header,而是 Claude Code 注入到请求体 system 数组首部的 text block。
全局剥离对 Bedrock 等非原生上游是合理的:它们可能把该保留关键字拒绝为 400;但同一行为会误伤 Anthropic 第一方订阅 OAuth 路径,尤其是 Auto Mode 的安全分类器旁路请求。
已确认的两侧兼容性冲突
1. 非原生上游:需要剥离
anthropics/claude-code#24168 记录了 Bedrock 对该 system 文本块返回:
400 x-anthropic-billing-header is a reserved keyword and may not be used in the system prompt.
这也是现有整流器最初要解决的问题。
2. Anthropic 第一方订阅 OAuth:必须保留
anthropics/claude-code#64585 给出了严格 A/B 复现:仅将 CLAUDE_CODE_ATTRIBUTION_HEADER 设为 0(即客户端不注入该 block),Auto Mode 分类器就对 Sonnet / Opus 确定性返回 429;恢复默认注入后立即正常。主对话请求不受影响,因此用户只会看到误导性的:
<model> is temporarily unavailable, so auto mode cannot determine the safety of Bash/Write/Edit right now
anthropics/claude-code#60438 的顶部更新也已将原先的“账户分类器池异常”结论更正为此根因;anthropics/claude-code#63819 的评论中也指向同一修复。
makecindy/cindy#758 还验证了宿主无条件设置 CLAUDE_CODE_ATTRIBUTION_HEADER=0 会导致所有需分类的工具调用 100% 失败。Cindy 最终改为:订阅 OAuth 连接保留 Claude Code 默认归因块,网关/第三方路径继续移除。
虽然 CCH 是在代理侧删除,而不是在客户端设置环境变量,但两者发给最终上游的请求体都缺少同一个 attribution block,因此会形成相同的第一方出站请求形态。
当前实现的问题
整流器的作用域由全局配置决定,而不是由最终选中的供应商决定。因此部署中只要同时存在:
- Anthropic 第一方 API / 订阅 OAuth 上游;
- Bedrock、Vertex、第三方 Anthropic-compatible 或需要稳定 KV/prompt cache 前缀的上游;
管理员就无法用一个全局布尔值同时满足两类渠道。
另外,rectifyBillingHeader() 会原地修改 session.request.message。实现供应商级策略时,如果一次请求可在供应商之间串行 fallback,或进入 hedge/discovery 并发尝试,必须保证某个供应商的 strip 不会污染后续/并行需要 preserve 的尝试。
建议方案
增加供应商级策略,例如:
type BillingHeaderPolicy = "inherit" | "preserve" | "strip";
inherit:沿用全局默认,保持数据库迁移和现有部署兼容;
preserve:不运行 rectifier;适用于 Anthropic 第一方端点,尤其是订阅 OAuth / Auto Mode;
strip:移除 attribution block;适用于 Bedrock、Vertex、第三方兼容上游或跨会话缓存场景。
可选地对新建供应商做安全默认:
- 明确识别为 Anthropic 第一方端点时默认
preserve;
- 明确识别为 Bedrock/Vertex/非原生上游时默认
strip;
- 自定义/无法可靠识别的 URL 由管理员显式选择,不建议仅根据
claude / claude-auth 类型推断,因为这两个类型目前同时覆盖第一方和中转语义。
全局开关可以保留为默认策略,但供应商配置应能覆盖它。UI 文案也建议说明:删除该 block 可能导致 Anthropic 订阅 OAuth 的 Auto Mode 分类器不可用。
验收标准
环境
- 核查版本:
main / v0.9.2
- 核查提交:
ccbad37f266e3e69d57a4427e2f27cf288796e63
问题背景
当前「计费标头整流器」只有一个全局开关
enableBillingHeaderRectifier(默认开启)。它会在每次claude/claude-auth请求发往上游前,无条件从system中移除x-anthropic-billing-header: ...文本块:src/drizzle/schema.ts#L978-L982src/app/v1/_lib/proxy/forwarder.ts#L3034-L3058这里的
x-anthropic-billing-header并不是 HTTP header,而是 Claude Code 注入到请求体system数组首部的 text block。全局剥离对 Bedrock 等非原生上游是合理的:它们可能把该保留关键字拒绝为 400;但同一行为会误伤 Anthropic 第一方订阅 OAuth 路径,尤其是 Auto Mode 的安全分类器旁路请求。
已确认的两侧兼容性冲突
1. 非原生上游:需要剥离
anthropics/claude-code#24168 记录了 Bedrock 对该 system 文本块返回:
这也是现有整流器最初要解决的问题。
2. Anthropic 第一方订阅 OAuth:必须保留
anthropics/claude-code#64585 给出了严格 A/B 复现:仅将
CLAUDE_CODE_ATTRIBUTION_HEADER设为0(即客户端不注入该 block),Auto Mode 分类器就对 Sonnet / Opus 确定性返回 429;恢复默认注入后立即正常。主对话请求不受影响,因此用户只会看到误导性的:anthropics/claude-code#60438 的顶部更新也已将原先的“账户分类器池异常”结论更正为此根因;anthropics/claude-code#63819 的评论中也指向同一修复。
makecindy/cindy#758 还验证了宿主无条件设置
CLAUDE_CODE_ATTRIBUTION_HEADER=0会导致所有需分类的工具调用 100% 失败。Cindy 最终改为:订阅 OAuth 连接保留 Claude Code 默认归因块,网关/第三方路径继续移除。虽然 CCH 是在代理侧删除,而不是在客户端设置环境变量,但两者发给最终上游的请求体都缺少同一个 attribution block,因此会形成相同的第一方出站请求形态。
当前实现的问题
整流器的作用域由全局配置决定,而不是由最终选中的供应商决定。因此部署中只要同时存在:
管理员就无法用一个全局布尔值同时满足两类渠道。
另外,
rectifyBillingHeader()会原地修改session.request.message。实现供应商级策略时,如果一次请求可在供应商之间串行 fallback,或进入 hedge/discovery 并发尝试,必须保证某个供应商的strip不会污染后续/并行需要preserve的尝试。建议方案
增加供应商级策略,例如:
inherit:沿用全局默认,保持数据库迁移和现有部署兼容;preserve:不运行 rectifier;适用于 Anthropic 第一方端点,尤其是订阅 OAuth / Auto Mode;strip:移除 attribution block;适用于 Bedrock、Vertex、第三方兼容上游或跨会话缓存场景。可选地对新建供应商做安全默认:
preserve;strip;claude/claude-auth类型推断,因为这两个类型目前同时覆盖第一方和中转语义。全局开关可以保留为默认策略,但供应商配置应能覆盖它。UI 文案也建议说明:删除该 block 可能导致 Anthropic 订阅 OAuth 的 Auto Mode 分类器不可用。
验收标准
claude/claude-auth供应商可单独配置inherit/preserve/strippreserve路径发往上游的system中仍包含原始x-anthropic-billing-headerblockstrip路径继续兼容当前字符串/数组等处理,并保留specialSettings审计strip供应商后切到preserve,第二次请求仍能拿到原始 block;反向同理环境
main/v0.9.2ccbad37f266e3e69d57a4427e2f27cf288796e63