Claude Code 状态行消耗 15-20% Token,社区发布禁用补丁
Claude Code 后台代理每 30 秒向模型发送完整对话记录以生成简短状态描述,导致约 15-20% 的 Token 被无效消耗。开发者通过 OpenTelemetry 数据发现此异常并发布了基于 BUN_OPTIONS 预加载的禁用脚本,该问题自 2.1.232 版本起默认开启且无官方关闭选项。
报道正文
本文目录13 个章节
已在 Linux、Claude Code 2.1.257–2.1.312 版本上测试(参见“已测试版本”)。二进制文件未被修改(
BUN_OPTIONS=--preload)。更新可能会悄无声息地撤销此更改,因此请参阅“验证”部分。
TL;DR
curl -fsSL https://gist.githubusercontent.com/flamerix-ai/c3905705cc4205e5de3011fa2e74efce/raw/no-agent-summary.js -o ~/.local/share/no-agent-summary.js
claude daemon stop --any
BUN_OPTIONS="${BUN_OPTIONS:+$BUN_OPTIONS }--preload $HOME/.local/share/no-agent-summary.js" claude若要使其对所有 Claude Code 进程永久生效,请参阅 安装。
问题所在
每隔约 30 秒,对于每个后台代理(agent),Claude Code 都会将该代理的完整对话记录(transcript)发送给该代理自身使用的模型(通常是 Opus),并要求它“用 3-5 个词描述你最近的操作”。这个答案仅用于更新代理面板中该代理的状态行;它永远不会传递给模型或你的对话上下文。目前没有任何设置可以关闭此功能,而且自 2.1.232 版本(2026 年 8 月)以来,它默认处于开启状态。
这一异常是由 DRIM 在 Claude Code 的 OpenTelemetry 导出数据中检测到的:按 query_source 对 claude_code.api_request 事件进行分组显示,agent_summary 的成本超过了主对话本身。
query_source = agent_summary | 挂钩(hook)前一周 | 2 天后 |
|---|---|---|
| 占所有 Claude Code 支出的比例 | 16.4 %(主对话:15.7 %) | 0 % |
| 占所有 API 请求的比例 | 19.7 %,约为五分之一 | 0 % |
| 其中花在 Opus 上的支出比例 | 96 % | — |
| 输入 token 与输出 token 之比 | ≈ 9,000 : 1 | — |
这里的“支出”指的是 Claude Code 自身的每次请求估算值(cost_usd),而非账单金额:在订阅模式下,你损失的是速率限制(rate-limit)的余量。并非只有我们测量到了这一点:#86879 收集了四项独立测量结果,数值在 18–26 % 之间。
修复方案(工作原理)
no-agent-summary.js 是一个 Bun preload 脚本(约 80 行代码)。它包装了 setTimeout/setInterval;当回调函数的源代码文本包含摘要器锚点(例如 [AgentSummary] Timer fired 日志行)时,它会返回一个无效句柄而不是启动定时器。其他所有定时器均不受影响地通过。在 OpenTelemetry 导出中,被抑制的请求是带有 query_source = agent_summary 的 claude_code.api_request 事件。
为什么选择定时器 hook 而不是更简单的方案:该模块以预编译的 Bun 字节码形式发布,因此直接修改二进制文件中的 JS 文本不会生效,且 API 客户端并未通过被包装的 globalThis.fetch。但是,即使回调函数是从字节码运行的,Function.prototype.toString 仍然会返回其源代码,因此可以通过字符串匹配来识别定时器。惰性定时器使用的延迟恰好为 2³¹−1 ms:Node 和 Bun 会将更大的延迟值视为 1 ms,因此所谓的“无限”延迟实际上会立即触发。
安装
- 将
no-agent-summary.js放在一个稳定的位置;记下其绝对路径。 - 全局设置
BUN_OPTIONS,以便每个 Claude Code 进程都能加载它。可选择以下任一方式:
# ~/.profile, ~/.bashrc, ~/.zshrc
export BUN_OPTIONS="${BUN_OPTIONS:+$BUN_OPTIONS }--preload /abs/path/no-agent-summary.js"# ~/.config/fish/config.fish
set -gx BUN_OPTIONS "--preload /abs/path/no-agent-summary.js"# ~/.config/environment.d/50-no-agent-summary.conf (systemd 用户环境;也会影响
# 用户服务和定时器;在下次启动用户管理器时生效,通常是下次登录时)
BUN_OPTIONS=--preload /abs/path/no-agent-summary.js或者,创建一个名为 claude 的 wrapper 脚本,并将其放置在 PATH 中较靠前的位置(使用 case 语句可确保当一个 Claude Code 进程启动另一个时保持幂等性):
#!/bin/sh
HOOK=/abs/path/no-agent-summary.js
case " ${BUN_OPTIONS-} " in
*" --preload $HOOK "*) ;;
*) export BUN_OPTIONS="${BUN_OPTIONS:+$BUN_OPTIONS }--preload $HOOK" ;;
esac
: "${CC_AGENT_SUMMARY_LOG:=${XDG_RUNTIME_DIR:-/tmp}/no-agent-summary.log}"
export CC_AGENT_SUMMARY_LOG
exec /path/to/the/real/claude "$@"- 重启守护进程以及所有已运行的会话,然后在包含
BUN_OPTIONS的环境中启动 Claude Code:
claude daemon stop --any环境变量
| 变量 | 取值 | 效果 |
|---|---|---|
CC_AGENT_SUMMARY_MODE | off(默认)、pass | off 会抑制摘要定时器。pass 作为 A/B 测试对照组:加载并记录日志,但保留所有定时器不变。其他任何值都会在 stderr 发出警告,并按 off 处理。 |
CC_AGENT_SUMMARY_LOG | 文件路径,可选 | 在加载时追加 preload active mode=… bun=… hooked=…,每匹配到一个定时器记录一行(前 20 个),并在退出时记录计数。 |
CC_AGENT_SUMMARY_MARKER | 文件路径,可选 | 在加载时被覆盖为 preload active … 行。 |
细节说明
- 安装后重启守护进程。 守护进程、pty host 和预热的
bg-spare会话会继承启动守护进程的客户端环境,因此在设置BUN_OPTIONS之前启动的守护进程将继续生成未挂钩的会话。因此需要执行claude daemon stop --any。 - 无头运行也需要此配置:
claude -p、cron、CI、编辑器集成。 - 平台: Linux 已测试,macOS 和 Windows 未经测试。在 Windows 上,
/$bunfs/门控很可能永远不会安装钩子。 - A/B 测试:
CC_AGENT_SUMMARY_MODE=pass加载钩子但启用所有计时器;摘要会返回,这表明off结果是钩子作用所致。 - 后台进程: 守护进程、pty host 和
bg-spare必须在其环境中携带--preload(Linux 上的/proc/<pid>/environ);如果没有,请重启守护进程。
验证(每次更新后)
- 预加载加载成功:
BUN_OPTIONS="--preload $HOME/.local/share/no-agent-summary.js" CC_AGENT_SUMMARY_LOG=/tmp/nas.log claude --version && cat /tmp/nas.log预期输出 preload active mode=off …。若无此行:说明 BUN_OPTIONS 未传递到进程,或路径不是绝对路径。
2. 计时器被抑制: 使用相同的 BUN_OPTIONS 和 CC_AGENT_SUMMARY_LOG=/tmp/nas.log,运行一个后台子代理一两分钟。日志应显示 summary timer #1 suppressed (setTimeout, delay=30000),且代理的面板状态停止更新。如果你导出 OpenTelemetry,query_source = agent_summary 应保持为零;这是唯一能捕获所有失败模式的检查项。
故障开放(Fail-open)
匹配基于源文本:锚点必须位于传递给计时器的回调内部。如果某次更新破坏了这一点,钩子将匹配不到任何内容,摘要会静默恢复。可能的情况包括:
- 日志行或查询源 ID 被重命名;
- 回调通过
fn.bind(...)传递(toString中无源代码)或其他包装器传递; - 字符串移至回调外部的共享常量中;
- 计时器移至未包装的 API(如
timers/promises、调度循环)。
因此,每次更新后都要重新运行“验证”步骤。
效果
- 我们在机器上的每个 Claude Code 进程(2.1.301)中运行它。此后,
agent_summary请求数量为零,也没有其他侧查询取而代之。 - Subagents、后台 agents、forked skills 和 background sessions 均正常运行;仅跳过 summary fork。
- 可见变化:每个 agent 的面板状态停止更新;SDK
task_progress中没有summary。 - 开销:每个不同的 timer callback 执行一次
Function.prototype.toString,并缓存在WeakMap中。claude --version:约 10 ms → 约 17 ms(单次测量)。
测试版本
来自 npm @anthropic-ai/claude-code-linux-x64 的 Linux x64 二进制文件(其 sha256 等于原生安装程序 manifest.json 中的校验和)。静态检查: 提取出的 bundle 中的 summary timer callback 包含 hook anchor。实时测试: 使用 CLAUDE_CODE_AGENT_PROGRESS_SUMMARIES=1 运行 claude -p --model haiku,其中一个 subagent 执行 8 步,每步约 20 秒,统计 --debug-file 中的 [AgentSummary] Forking。
| version | published | sha256 | static | summary forks in a ~2.5-min agent run: no hook / hook pass / hook off | notes |
|---|---|---|---|---|---|
| 2.1.312 | 2026-10-27 | 4f4178ce41d8 | OK | 5 / 5 / 0 | 2026-10-28 为 latest;交互式模式:无 hook 时 6 次,off 时 0 次 |
| 2.1.310 | 2026-10-23 | b73676430af1 | OK | 5 / 5 / 0 | |
| 2.1.308 | 2026-10-22 | 356733f185fa | OK | 5 / 6 / 0 | 2026-10-28 为 stable;pass 运行时 agent 执行了 9 步而非 8 步 |
| 2.1.301 | 2026-10-15 | bc1aad9982c7 | OK | 5 / 5 / 0 | 早期 hook 构建结果相同;交互式模式下 off 也为 0;自 2026-10-26 起日常使用,遥测数据显示 agent_summary 请求数为 0 |
| 2.1.280 | 2026-09-22 | 1e08503dbdf3 | OK | 1 / 1 / 0 (timer fired 11 / 11 / 0) | 交互式模式:在此版本上 -p 无法启用 summaries。由于 agent 停留在权限提示处,后续 timers 跳过了未变化的 transcript |
| 2.1.257 | 2026-09-01 | 9a64bda9d872 | OK | 1 / 1 / 0 (timer fired 11 / 11 / 0) | 与 2.1.280 相同 |
5 / 5 / 0 = 在该版本上发生 fork,仅加载 hook 不会改变任何行为,而 off 会将其移除。
为什么没有相关设置
- 文档中记载的开关只能将其打开。 SDK 选项
agentProgressSummaries默认为false,但在打包文件中它是 OR 逻辑的一项,且所有写入该值的代码路径都将其设为true。因此,设置为false没有任何作用,而未公开的CLAUDE_CODE_AGENT_PROGRESS_SUMMARIES环境变量也只能用于启用摘要功能。 - 时间间隔由服务器控制。 它来自针对特定模型系列的服务器端标志位,并被限制在 30–240 秒之间。默认值已经是最小值,且没有哪个值可以禁用计时器。
- 显而易见的环境变量无效。 某些打包者设置的
DISABLE_NON_ESSENTIAL_MODEL_CALLS在二进制文件中根本不存在。CLAUDE_CODE_FORK_SUBAGENT=0和CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1确实能阻止它,但代价是同时也禁用了 fork 子代理或后台代理。 - 提示缓存使其更便宜,而非廉价。 大约 98% 的这些调用会从缓存中重新读取转录记录;那约 2% 命中冷缓存的请求承担了其成本的三分之一以上。
- 读取转录记录的成本工具看不到它。 这些请求不会写入磁盘上的会话日志(#104358);只有 OpenTelemetry 导出会显示它们。文档中关于后台令牌用量的章节也未列出此项,而 Agent View 的行摘要被记录为每隔几分钟刷新一次 Haiku 级模型。
- 已存在一个更便宜的变体,但处于关闭状态。 2.1.310 版本引入了
short模式:使用约 2k 令牌的笔记代替完整转录记录,并可选地由较小的模型生成。默认仍然是完整转录记录,只有服务器标志位才能更改此设置。
时间线 (2026)
| 版本 (发布) | 变更内容 |
|---|---|
| 2.1.32 (2月5日) | 代码首次出现;在公开构建中处于死代码状态 |
| 2.1.72 (3月9日) | 作为 SDK 的 opt-in 选项可达 |
| 2.1.117 (4月21日) | 在交互式 CLI 中可达,需通过 fork 门控或服务器灰度发布 |
| ≤ 2.1.220 (7月24日) | forked skills 始终会被生成摘要 |
| 2.1.232 (8月13日) | 默认开启,适用于交互式后台代理 |
| 2.1.300 (10月14日) | 时间间隔变为服务器可调,范围 30–240 秒 |
| 2.1.310 (10月23日) | 更便宜的 short 模式上线,但处于关闭状态 |
在我们的遥测数据中,其每周支出占比从 7 月的 3.5–7%(当时 fork 门控对我们已经开放)上升到我们升级到 2.1.233 那一周的 7.3%,随后升至 11.8%,并在 10 月中旬达到约 16%。
许可证与免责声明
MIT © 2026 Flamerix AI。本软件按“原样”提供,不附带任何形式的担保;使用风险由您自行承担。对于因使用本软件而导致的任何损害、数据丢失或账户处罚,Flamerix AI 概不负责。更改客户端的运行时行为可能会与您所使用的服务条款相冲突,请务必自行核实合规性。
"Claude" 和 "Claude Code" 是 Anthropic, PBC 的商标。本项目为独立项目,与 Anthropic 无任何关联,也未获得其赞助或背书。