三词状态栏吃掉 15–20% 的 token:Claude Code 的 agent_summary 是怎么被关掉的

Flamerix AI 在自己的 OpenTelemetry 里发现,agent_summary 占了 Claude Code 全部花费的 16.4%,比主对话还多:每 30 秒把整个 agent 会话记录发给 Opus,只为生成三五个字的进度描述。他们用一个 80 行的 Bun preload 把它关掉,并逐版本给出了验证数据。

中文
复制

在 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 都会把该 agent 的完整对话记录发送给该 agent 自己的模型(通常是 Opus),并要求它“用 3-5 个词描述你最近的操作”。这个回答只会更新 agents 面板中该 agent 的状态行,永远不会到达模型或你的对话。没有任何设置可以关闭它,而且自 2.1.232(2026 年 8 月)起,它默认开启。

这个异常是由 DRIM 在 Claude Code 的 OpenTelemetry 导出数据中发现的:按 query_source 对 claude_code.api_request 事件分组后显示,agent_summary 的花费超过了主对话本身。

query_source = agent_summary钩子生效前一周2 天后
占 Claude Code 总花费的比例16.4 %(主对话:15.7 %)0 %
占所有 API 请求的比例19.7 %,约每 5 个中有 1 个0 %
其中花在 Opus 上的部分96 %—
每个输出 token 对应的输入 token≈ 9,000 : 1—

这里的“花费”指 Claude Code 自己的逐请求估算(cost_usd),不是账单:在订阅制下,你损失的是速率限制的余量。并非只有我们测量到了这一点:#86879 收集了四项独立测量,结果为 18–26 %。

修复方案(工作原理)

no-agent-summary.js 是一个 Bun 预加载脚本(约 80 行代码)。它包装了 setTimeout/setInterval;当回调的源文本包含摘要器锚点(例如 [AgentSummary] Timer fired 日志行)时,它返回一个惰性句柄,而不是启动定时器。其他所有定时器都原样通过。在 OpenTelemetry 导出数据中,被抑制的请求是 claude_code.api_request 事件,带有 query_source = agent_summary。

为什么用定时器钩子而不是更简单的办法:该模块以预编译的 Bun 字节码形式发布,所以修改二进制文件内的 JS 文本不会产生任何效果,而且 API 客户端不经过被包装的 globalThis.fetch。但 Function.prototype.toString 即使从字节码运行,仍然会返回回调的源代码,因此可以通过其中的字符串识别出这个定时器。惰性定时器正好使用 2³¹−1 毫秒:Node 和 Bun 会把更大的延迟当作 1 毫秒,所以“无限”延迟会立即触发。

安装

  1. 把 no-agent-summary.js 放到一个固定位置,记下它的绝对路径。

  2. 全局设置 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 user env; also reaches
    # user services and timers; applies when the user manager next starts, usually next login)
    BUN_OPTIONS=--preload /abs/path/no-agent-summary.js
    

    或者在 PATH 上更靠前的位置放一个名为 claude 的包装脚本(这样当一个 Claude Code 进程启动另一个时,case 能保证它幂等):

    #!/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 "$@"
  3. 重启 daemon 和所有已在运行的会话,然后从带有 BUN_OPTIONS 的环境启动 Claude Code:

    claude daemon stop --any

环境变量

变量取值作用
CC_AGENT_SUMMARY_MODEoff(默认)、passoff 会关闭摘要定时器。pass 是对照组:照常加载并记录日志,所有定时器原样保留。其他任何值都会在 stderr 上告警,并按 off 处理。
CC_AGENT_SUMMARY_LOG文件路径,可选加载时追加 preload active mode=… bun=… hooked=…,每个命中的定时器一行(前 20 个),外加一个退出计数。
CC_AGENT_SUMMARY_MARKER文件路径,可选加载时用 preload active … 那一行覆盖写入。

细节

  • 安装后要重启 daemon。 daemon、pty host 和预热好的 bg-spare 会话继承的是启动 daemon 的那个客户端的环境,所以在设置 BUN_OPTIONS 之前启动的 daemon 会继续拉起没有挂上 hook 的会话。所以要 claude daemon stop --any。
  • 无头运行同样需要: claude -p、cron、CI、编辑器集成。
  • 平台: Linux 上测过,macOS 和 Windows 未测。在 Windows 上,/$bunfs/ 这道关卡多半根本不会安装 hook。
  • A/B: CC_AGENT_SUMMARY_MODE=pass 会加载 hook,但所有定时器照常启用;摘要又回来了,说明 off 的结果是 hook 造成的。
  • 后台进程: daemon、pty host 和 bg-spare 的环境里必须带上 --preload(Linux 上是 /proc/<pid>/environ);如果没有,就重启 daemon。

验证(每次更新后都做)

  1. 预加载生效:

    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)

匹配依据是源文本:锚点必须位于传给定时器的回调内部。一旦某次更新破坏了这一点,钩子就匹配不到任何东西,摘要会在无声无息中重新出现。可能的情形有:

  • 日志行或 query-source id 被改名;
  • 回调经由 fn.bind(...) 传入(toString 中没有 source),或经过其他包装;
  • 字符串被移到回调之外的共享常量里;
  • 定时器被移到未包装的 API(timers/promises、调度循环)。

因此每次更新后都要重新跑一遍 Verify。

效果

  • 本机每个 Claude Code 进程(2.1.301)下都启用了它。此后 agent_summary 请求为零,也没有其他旁路查询增长起来顶替它的位置。
  • 子代理、后台代理、fork 出来的 skill 和后台会话都正常运行,只有摘要 fork 被跳过。
  • 可见的变化:每个代理的面板状态不再更新;SDK task_progress 没有 summary。
  • 开销:每个不同的定时器回调一次 Function.prototype.toString,缓存在 WeakMap 中。claude --version:约 10 ms → 约 17 ms(单次测量)。

测试过的版本

来自 npm @anthropic-ai/claude-code-linux-x64 的 Linux x64 二进制(其 sha256 与原生安装器的 manifest.json 校验和一致)。静态: 解包后的 bundle 中,摘要定时器回调带有钩子锚点。实测: claude -p --model haiku 配 CLAUDE_CODE_AGENT_PROGRESS_SUMMARIES=1,一个子代理执行 8 步、每步约 20 s,统计 --debug-file 中的 [AgentSummary] Forking。

版本发布日期sha256静态约 2.5 分钟代理运行中的摘要 fork 数:无钩子 / 钩子 pass / 钩子 off备注
2.1.3122026-10-274f4178ce41d8OK5 / 5 / 0latest 于 2026-10-28;交互式:6 次无钩子,0 次 off
2.1.3102026-10-23b73676430af1OK5 / 5 / 0
2.1.3082026-10-22356733f185faOK5 / 6 / 0stable 于 2026-10-28;pass 那次运行的代理走了 9 步而非 8 步
2.1.3012026-10-15bc1aad9982c7OK5 / 5 / 0与更早的钩子构建结果相同;交互式 off 也是 0;自 2026-10-26 起日常使用,遥测中 0 次 agent_summary 请求
2.1.2802026-09-221e08503dbdf3OK1 / 1 / 0(定时器触发 11 / 11 / 0)交互式:-p 在此版本上无法启用摘要。代理卡在权限提示上,因此后续定时器跳过了未变化的对话记录
2.1.2572026-09-019a64bda9d872OK1 / 1 / 0(定时器触发 11 / 11 / 0)与 2.1.280 相同

5 / 5 / 0 = 分叉就发生在这个版本上,仅仅加载 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 确实能停掉它,但代价是连分叉子代理或后台代理一起停掉。
  • 提示缓存让它更便宜,但并没有变便宜。 这些调用里约 98 % 从缓存中重读对话记录;命中冷缓存的那约 2 % 承担了超过三分之一的成本。
  • 读取对话记录的成本工具看不到它。 这些请求不会写进磁盘上的会话日志(#104358),只有 OpenTelemetry 导出能看到。文档中关于后台 token 用量的那一节也没有列出它,而 Agent View 的行摘要按文档说明是由一个 Haiku 级模型每隔几分钟刷新一次。
  • 更便宜的变体早就有了,只是关着。 2.1.310 提供了 short 模式:用约 2k token 的笔记代替完整对话记录,还可以选择由更小的模型来写。默认仍然是完整对话记录,只有服务端标志能改变这一点。

时间线(2026)

版本(发布日期)变化
2.1.32(2 月 5 日)代码首次出现;在公开构建中是死代码
2.1.72(3 月 9 日)可通过 SDK 主动开启
2.1.117(4 月 21 日)在交互式 CLI 中可通过分叉开关或服务端灰度开启
≤ 2.1.220(7 月 24 日)分叉的 skill 一律生成摘要
2.1.232(8 月 13 日)对交互式后台代理默认开启
2.1.300(10 月 14 日)间隔可由服务端调整,30–240 秒
2.1.310(10 月 23 日)更便宜的 short 模式上线,处于关闭状态

在我们的遥测数据中,它的每周支出占比从 7 月的 3.5–7%(当时 fork 开关已向我们开放)升至 7.3%,在我们接入 2.1.233 的那一周升至 11.8%,到 10 月中旬约为 16%。

许可证与免责声明

MIT © 2026 Flamerix AI。按原样提供,不附带任何形式的担保;使用风险由你自行承担。对于因使用本软件而产生的任何损害、数据丢失或账号处置,Flamerix AI 概不负责。改变客户端的运行时行为可能与你所用服务的条款相冲突;核查这一点由你负责。

“Claude”和“Claude Code”是 Anthropic, PBC 的商标。本项目独立开发,与 Anthropic 无关联,也未获其赞助或认可。

来源: GitHub Gist← 返回首页