Back to Home
AI

Claude Code regularly sends each background agent’s transcript to its model

Article URL: https://gist.github.com/flamerix-ai/c3905705cc4205e5de3011fa2e74efce Comments URL: https://news.ycombinator.com/item?id=50284255 Points: 349 # Comments: 106

t
tech4you AI
October 30, 202614 min read
Share

Claude Code burns ~15–20% of your tokens on a 3-word status line. Here's how to turn it off

by Flamerix AI · MIT License

Tested on Linux, Claude Code 2.1.257–2.1.312; macOS reported working by others (see Tested versions). The binary is not modified (BUN_OPTIONS=--preload). An update can silently undo it, so see Verify.

Every ~30 seconds, Claude Code re-sends each background agent's full transcript to the model just to fill that agent's status line in the agents panel. no-agent-summary.js is a Bun --preload that stops that timer without modifying the Claude Code binary.

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

To make it permanent for every Claude Code process, see Install.

Every ~30 seconds, for every background agent, Claude Code sends that agent's entire transcript to the agent's own model (usually Opus) and asks it to "describe your most recent action in 3-5 words". The answer only updates the agent's status line in the agents panel; it never reaches the model or your conversation. There is no setting that turns it off, and since 2.1.232 (August 2026) it has been on by default.

The anomaly was detected by DRIM in Claude Code's OpenTelemetry export: grouping claude_code.api_request events by query_source showed agent_summary costing more than the main conversation itself.

query_source = agent_summary week before the hook 2 days after
share of all Claude Code spend 16.4 % (main conversation: 15.7 %) 0 %
share of all API requests 19.7 %, about 1 in 5 0 %
part of that spend on Opus 96 % —
input tokens per output token ≈ 9,000 : 1 —

Spend here means Claude Code's own per-request estimate (cost_usd), not an invoice: on a subscription, what you lose is rate-limit headroom. We are not the only ones who measured it: #86879 collects four independent measurements of 18–26 %.

no-agent-summary.js is a Bun preload (~70 lines of code). It wraps only the global setTimeout, the one the summary timer is armed through; when a callback's source text contains a summarizer anchor (e.g. the [AgentSummary] Timer fired log line), it returns an inert handle instead of arming the timer. Every other timer passes through untouched. In the OpenTelemetry export the suppressed requests are claude_code.api_request events with query_source = agent_summary.

Why a timer hook rather than something simpler: the module ships as precompiled Bun bytecode, so editing its JS text inside the binary changes nothing, and the API client does not go through a wrapped globalThis.fetch. But Function.prototype.toString still returns a callback's source even when it runs from bytecode, so the timer can be recognised by a string inside it. The inert timer uses exactly 2³¹−1 ms: Node and Bun treat a larger delay as 1 ms, so an "infinite" delay would fire at once.

Which processes it touches

The hook installs only inside Claude Code: Bun.main must be /$bunfs/root/cli (the compiled entry, the same on every row below) and process.execPath must be a Claude Code install. Any other bun, or a compiled Bun binary with another entry name, loads the file and does nothing.

install channel (Linux, measured with claude --version) process.execPath
nix …/bin/.claude-wrapped
npm @anthropic-ai/claude-code …/@anthropic-ai/claude-code/bin/claude.exe (.exe on Linux too)
npm platform package …/@anthropic-ai/claude-code-linux-x64/claude
native installer ~/.local/share/claude/versions/<version>

macOS and Windows are not measured; the hook installs there only if they produce these same shapes, and Verify step 1 tells you. One known pass-through: a foreign binary built with bun build --compile --outfile cli and renamed claude; there, only timers whose callback carries an anchor change.

  1. Put no-agent-summary.js somewhere stable; note its absolute path.

  2. Set BUN_OPTIONS globally so every Claude Code process gets it. Write --preload=<path>, with =: BUN_OPTIONS reaches every bun on the machine, and with a space (--preload <path>) bun subcommands break (bun install → error: Script not found "install"). Claude Code reads both forms. One of:

    # ~/.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

    Or a wrapper named claude earlier on PATH (the case keeps it idempotent when one Claude Code process starts another, and also recognises the v2 space form):

    #!/bin/sh
    HOOK=/abs/path/no-agent-summary.js
    case " ${BUN_OPTIONS-} " in
      *" --preload=$HOOK "*|*" --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. Restart the daemon and every already-running session, then start Claude Code from an environment that has BUN_OPTIONS:

variable values effect
CC_AGENT_SUMMARY_MODE off (default), pass off suppresses the summary timer. pass is the A/B control: loads and logs, arms every timer untouched. Any other value warns on stderr and acts as off.
CC_AGENT_SUMMARY_LOG file path, optional Appends preload active mode=… bun=… hooked=global.setTimeout on load, one line per matched timer (first 20), and an exit count.
CC_AGENT_SUMMARY_MARKER file path, optional Overwritten on load with the preload active … line.
  • Restart the daemon after install. The daemon, pty host and pre-warmed bg-spare sessions inherit the environment of the client that started the daemon, so a daemon started before BUN_OPTIONS was set keeps spawning unhooked sessions. Hence claude daemon stop --any.
  • Headless runs need it too: claude -p, cron, CI, editor integrations.
  • Platform: Linux tested by us, macOS (darwin-arm64) reported working by others, Windows untested (see Which processes it touches).
  • npm --ignore-scripts: bin/claude stays a shell stub and Bun never starts, so the hook never runs.
  • Upgrading from v2: overwrite the file in place, switch BUN_OPTIONS to the = form and restart the daemon. If both forms name the same path, bun loads it once. Two different paths (say an old copy kept beside the new one) both load: a second v3 copy is a no-op, but v2 has no such guard, so do not leave a v2 path in BUN_OPTIONS.
  • A/B: CC_AGENT_SUMMARY_MODE=pass loads the hook but arms every timer; the summaries come back, which shows the off result is the hook's doing.
  • Background processes: daemon, pty host and bg-spare must carry --preload in their environment (/proc/<pid>/environ on Linux); if not, restart the daemon.

Verify (after every update)

  1. Preload loads:

    BUN_OPTIONS="--preload=$HOME/.local/share/no-agent-summary.js" CC_AGENT_SUMMARY_LOG=/tmp/nas.log claude --version && cat /tmp/nas.log

    Expect preload active mode=off …. No line: BUN_OPTIONS did not reach the process, or the path is not absolute.

  2. Timer suppressed: with the same BUN_OPTIONS and CC_AGENT_SUMMARY_LOG=/tmp/nas.log, run a background subagent for a minute or two. The log shows summary timer #1 suppressed (setTimeout, delay=30000) and the agent's panel status stops updating. If you export OpenTelemetry, query_source = agent_summary should stay at zero; this is the only check that catches every failure mode.

Matching is by source text: an anchor must sit inside the callback passed to the timer. If an update breaks that, the hook matches nothing and summaries silently come back. Ways it can happen:

  • the log line or query-source id is renamed;
  • the callback is passed via fn.bind(...) (no source in toString) or another wrapper;
  • the strings move to a shared constant outside the callback;
  • the timer moves off the global setTimeout (setInterval, require("timers"), timers/promises, a scheduler loop).

So re-run Verify after every update.

  • We run it under every Claude Code process on our machine (2.1.301). Since then there have been zero agent_summary requests, and no other side query grew to take its place.
  • Subagents, background agents, forked skills and background sessions run normally; only the summary fork is skipped.
  • Visible change: the per-agent panel status stops updating; SDK task_progress has no summary.
  • Overhead: one Function.prototype.toString per distinct timer callback, cached in a WeakMap. claude --version: ~10 ms → ~17 ms (one measurement).

Linux x64 binary from npm @anthropic-ai/claude-code-linux-x64 (its sha256 equals the native installer's manifest.json checksum). Static: the extracted bundle's summary timer callback carries a hook anchor. Live: claude -p --model haiku with CLAUDE_CODE_AGENT_PROGRESS_SUMMARIES=1, one subagent doing 8 steps of ~20 s, counting [AgentSummary] Forking in --debug-file.

The two v3 rows are this script, re-run on 2026-10-29. The rows below them were measured with v2.

version published sha256 static summary forks in a ~2.5-min agent run: no hook / hook pass / hook off notes
2.1.312 v3 2026-10-27 4f4178ce41d8 OK 5 / 6 / 0 the pass run took 232 s on a loaded machine, so one more 30 s tick fit; off with the gist file itself: 0
2.1.301 v3 2026-10-15 bc1aad9982c7 OK 5 / 5 / 0 run through the Nix-packaged binary rather than the npm one
2.1.312 2026-10-27 4f4178ce41d8 OK 5 / 5 / 0 latest on 2026-10-28; interactive: 6 no hook, 0 off
2.1.310 2026-10-23 b73676430af1 OK 5 / 5 / 0
2.1.308 2026-10-22 356733f185fa OK 5 / 6 / 0 stable on 2026-10-28; the pass run's agent took 9 steps instead of 8
2.1.301 2026-10-15 bc1aad9982c7 OK 5 / 5 / 0 same with earlier hook builds; interactive off also 0; in daily use since 2026-10-26, 0 agent_summary requests in telemetry
2.1.280 2026-09-22 1e08503dbdf3 OK 1 / 1 / 0 (timer fired 11 / 11 / 0) interactive: -p cannot enable summaries on this version. The agent sat at a permission prompt, so later timers skipped the unchanged transcript
2.1.257 2026-09-01 9a64bda9d872 OK 1 / 1 / 0 (timer fired 11 / 11 / 0) same as 2.1.280

5 / 5 / 0 = the fork happens on this version, merely loading the hook changes nothing, and off removes it.

Reported by others

platform version checked by
macOS 26.7.1, native darwin-arm64, bun 1.4.4 2.1.312 static: the 3–5 words prompt, the timer (30 s, clamped 30–240 s), no off switch; live: the preload loads, and an interactive session with a background subagent logged summary timer #1 suppressed (setTimeout, delay=30000). Token numbers not checked (no OTEL export) lostsock, HN, 2026-10-29

On claude -p: headless runs don't arm the summary timer by default. Our live tests set CLAUDE_CODE_AGENT_PROGRESS_SUMMARIES=1 for that reason. The default-on overhead is in interactive sessions with background agents.

Why there is no setting for this

  • The documented switch only turns it on. The SDK option agentProgressSummaries defaults to false, but in the bundle it is one term of an OR, and every code path that writes it sets true. So false changes nothing, and the undocumented CLAUDE_CODE_AGENT_PROGRESS_SUMMARIES can only enable summaries too.
  • The interval belongs to the server. It comes from a per-model-family server flag and is clamped to 30–240 s. The default is already the minimum, and no value disables the timer.
  • The obvious env vars miss. DISABLE_NON_ESSENTIAL_MODEL_CALLS, which some packagers set, does not occur in the binary at all. CLAUDE_CODE_FORK_SUBAGENT=0 and CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 do stop it, but only by taking fork subagents or background agents with it.
  • The prompt cache makes it cheaper, not cheap. About 98 % of these calls re-read the transcript from cache; the ~2 % that hit a cold cache carry over a third of its cost.
  • Cost tools that read transcripts don't see it. These requests are not written to the session logs on disk (#104358); only the OpenTelemetry export shows them. The docs section on background token usage doesn't list it either, while Agent View's row summaries are documented as a Haiku-class model refreshing every few minutes.
  • A cheaper variant already exists, switched off. 2.1.310 ships a short mode: about 2k tokens of notes instead of the full transcript, optionally written by a smaller model. The default is still the full transcript, and only server flags can change it.
version (published) what changed
2.1.32 (Feb 5) the code first appears; dead in public builds
2.1.72 (Mar 9) reachable as an SDK opt-in
2.1.117 (Apr 21) reachable in the interactive CLI behind the fork gate or a server rollout
≤ 2.1.220 (Jul 24) forked skills are always summarized
2.1.232 (Aug 13) on by default for interactive background agents
2.1.300 (Oct 14) interval becomes server-tunable, 30–240 s
2.1.310 (Oct 23) the cheaper short mode lands, switched off

In our telemetry its weekly share of spend went from 3.5–7 % in July (the fork gate was already open for us) to 7.3 % and then 11.8 % in the week we picked up 2.1.233, and to about 16 % by mid-October.

  • docs — first macOS report (darwin-arm64, 2.1.312) added to Tested versions. Thanks to lostsock.
  • v3 — --preload=<path> everywhere (the space form broke bun subcommands); the hook installs only inside Claude Code (Bun.main plus process.execPath, see Which processes it touches); a second load in the same process is a no-op. Thanks to Bedz01 for the --preload= form and the Claude-Code-only gate (gist comment, 2026-10-29).
  • v3 — wraps only the global setTimeout, the one the summary timer is armed through (v2 also wrapped setInterval and require("timers")).

MIT © 2026 Flamerix AI. Provided as is, without warranty of any kind; you use it at your own risk. Flamerix AI is not liable for any damage, data loss or account action arising from its use. Changing a client's runtime behaviour may conflict with the terms of the service you use it with; checking that is on you.

"Claude" and "Claude Code" are trademarks of Anthropic, PBC. This project is independent and is not affiliated with, sponsored or endorsed by Anthropic.


Originally published on Hacker News (Best)

Related Articles