{"slug": "claude-code-burns-15-20-of-your-tokens-on-a-3-word-status-line", "title": "Claude Code burns ~15–20% of your tokens on a 3-word status line", "summary": "Flamerix AI released no-agent-summary.js, an MIT-licensed Bun preload that disables Claude Code's background agent-summary feature, which sends each background agent's full transcript to a model roughly every 30 seconds to generate a 3-5 word status line. Using OpenTelemetry data from Claude Code's api_request events, the team measured that agent_summary accounted for 16.4% of Claude Code spend and 19.7% of API requests, with 96% of that spend on Opus, and reported the share fell to 0% after the hook was installed. The hook works by wrapping setTimeout/setInterval and returning an inert handle when a callback's source text contains a summarizer anchor, since the module ships as precompiled Bun bytecode.", "body_md": "# Claude Code burns ~15–20% of your tokens on a 3-word status line. Here's how to turn it off\n\nby [Flamerix AI](https://flamerix.ai) · MIT License\n\n> Tested on Linux, Claude Code 2.1.257–2.1.312 (see Tested versions). The binary is not modified (`BUN_OPTIONS=--preload`). An update can silently undo it, so see Verify.\n\n## TL;DR\n\n```sh\ncurl -fsSL https://gist.githubusercontent.com/flamerix-ai/c3905705cc4205e5de3011fa2e74efce/raw/no-agent-summary.js -o ~/.local/share/no-agent-summary.js\nclaude daemon stop --any\nBUN_OPTIONS=\"${BUN_OPTIONS:+$BUN_OPTIONS }--preload $HOME/.local/share/no-agent-summary.js\" claude\n```\n\nTo make it permanent for every Claude Code process, see [Install](#install).\n\n## The problem\n\nEvery ~30 seconds, for every background agent, Claude Code sends that agent's **entire\ntranscript** to the agent's own model (usually Opus) and asks it to \"describe your most recent\naction in 3-5 words\". The answer only updates the agent's status line in the agents panel; it\nnever reaches the model or your conversation. There is no setting that turns it off, and since\n2.1.232 (August 2026) it has been on by default.\n\nThe anomaly was detected by [DRIM](https://flamerix.ai) in Claude Code's OpenTelemetry export:\ngrouping `claude_code.api_request` events by `query_source` showed `agent_summary` costing **more than the main conversation itself**.\n\n| `query_source = agent_summary` | week before the hook | 2 days after |\n|---|---|---|\n| share of all Claude Code spend | **16.4 %** (main conversation: 15.7 %) | **0 %** |\n| share of all API requests | **19.7 %**, about 1 in 5 | **0 %** |\n| part of that spend on Opus | 96 % | — |\n| input tokens per output token | ≈ 9,000 : 1 | — |\n\nSpend here means Claude Code's own per-request estimate (`cost_usd`), not an invoice: on a\nsubscription, what you lose is rate-limit headroom. We are not the only ones who measured it:\n[#86879](https://github.com/anthropics/claude-code/issues/86879) collects four independent\nmeasurements of 18–26 %.\n\n## The fix (how it works)\n\n`no-agent-summary.js` is a Bun preload (~80 lines of code). It wraps `setTimeout`/`setInterval`;\nwhen a callback's source text contains a summarizer anchor (e.g. the `[AgentSummary] Timer fired`\nlog line), it returns an inert handle instead of arming the timer. Every other timer passes\nthrough untouched. In the OpenTelemetry export the suppressed requests are\n`claude_code.api_request` events with `query_source = agent_summary`.\n\nWhy a timer hook rather than something simpler: the module ships as precompiled Bun bytecode, so\nediting its JS text inside the binary changes nothing, and the API client does not go through a\nwrapped `globalThis.fetch`. But `Function.prototype.toString` still returns a callback's source\neven when it runs from bytecode, so the timer can be recognised by a string inside it. The inert\ntimer uses exactly 2³¹−1 ms: Node and Bun treat a larger delay as 1 ms, so an \"infinite\" delay\nwould fire at once.\n\n## Install\n\n1. Put `no-agent-summary.js` somewhere stable; note its **absolute** path.\n2. Set `BUN_OPTIONS` **globally** so every Claude Code process gets it. One of:\n\n   ```sh\n   # ~/.profile, ~/.bashrc, ~/.zshrc\n   export BUN_OPTIONS=\"${BUN_OPTIONS:+$BUN_OPTIONS }--preload /abs/path/no-agent-summary.js\"\n   ```\n\n   ```fish\n   # ~/.config/fish/config.fish\n   set -gx BUN_OPTIONS \"--preload /abs/path/no-agent-summary.js\"\n   ```\n\n   ```ini\n   # ~/.config/environment.d/50-no-agent-summary.conf (systemd user env; also reaches\n   # user services and timers; applies when the user manager next starts, usually next login)\n   BUN_OPTIONS=--preload /abs/path/no-agent-summary.js\n   ```\n\n   Or a wrapper named `claude` earlier on `PATH` (the `case` keeps it idempotent when one Claude\n   Code process starts another):\n\n   ```sh\n   #!/bin/sh\n   HOOK=/abs/path/no-agent-summary.js\n   case \" ${BUN_OPTIONS-} \" in\n     *\" --preload $HOOK \"*) ;;\n     *) export BUN_OPTIONS=\"${BUN_OPTIONS:+$BUN_OPTIONS }--preload $HOOK\" ;;\n   esac\n   : \"${CC_AGENT_SUMMARY_LOG:=${XDG_RUNTIME_DIR:-/tmp}/no-agent-summary.log}\"\n   export CC_AGENT_SUMMARY_LOG\n   exec /path/to/the/real/claude \"$@\"\n   ```\n\n3. Restart the daemon and every already-running session, then start Claude Code from an\n   environment that has `BUN_OPTIONS`:\n\n   ```sh\n   claude daemon stop --any\n   ```\n\n### Env\n\n| variable | values | effect |\n|---|---|---|\n| `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`. |\n| `CC_AGENT_SUMMARY_LOG` | file path, optional | Appends `preload active mode=… bun=… hooked=…` on load, one line per matched timer (first 20), and an exit count. |\n| `CC_AGENT_SUMMARY_MARKER` | file path, optional | Overwritten on load with the `preload active …` line. |\n\n## Nuances\n\n- **Restart the daemon after install.** The daemon, pty host and pre-warmed `bg-spare` sessions\n  inherit the environment of the client that started the daemon, so a daemon started before\n  `BUN_OPTIONS` was set keeps spawning unhooked sessions. Hence `claude daemon stop --any`.\n- **Headless runs need it too:** `claude -p`, cron, CI, editor integrations.\n- **Platform:** Linux tested, macOS and Windows untested. On Windows the `/$bunfs/` gate most\n  likely never installs the hook.\n- **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.\n- **Background processes:** daemon, pty host and `bg-spare` must carry `--preload` in their environment (`/proc/<pid>/environ` on Linux); if not, restart the daemon.\n\n## Verify (after every update)\n\n1. **Preload loads:**\n\n   ```sh\n   BUN_OPTIONS=\"--preload $HOME/.local/share/no-agent-summary.js\" CC_AGENT_SUMMARY_LOG=/tmp/nas.log claude --version && cat /tmp/nas.log\n   ```\n\n   Expect `preload active mode=off …`. No line: `BUN_OPTIONS` did not reach the process, or the\n   path is not absolute.\n2. **Timer suppressed:** with the same `BUN_OPTIONS` and `CC_AGENT_SUMMARY_LOG=/tmp/nas.log`, run\n   a background subagent for a minute or two. The log shows\n   `summary timer #1 suppressed (setTimeout, delay=30000)` and the agent's panel status stops\n   updating. If you export OpenTelemetry, `query_source = agent_summary` should stay at zero; this\n   is the only check that catches every failure mode.\n\n### Fail-open\n\nMatching is by **source text**: an anchor must sit inside the callback passed to the timer. If an\nupdate breaks that, the hook matches nothing and summaries silently come back. Ways it can happen:\n\n- the log line or query-source id is renamed;\n- the callback is passed via `fn.bind(...)` (no source in `toString`) or another wrapper;\n- the strings move to a shared constant outside the callback;\n- the timer moves to an unwrapped API (`timers/promises`, a scheduler loop).\n\nSo re-run Verify after every update.\n\n## Effect\n\n- We run it under every Claude Code process on our machine (2.1.301). Since then there have been\n  zero `agent_summary` requests, and no other side query grew to take its place.\n- Subagents, background agents, forked skills and background sessions run normally; only the\n  summary fork is skipped.\n- Visible change: the per-agent panel status stops updating; SDK `task_progress` has no `summary`.\n- Overhead: one `Function.prototype.toString` per distinct timer callback, cached in a `WeakMap`.\n  `claude --version`: ~10 ms → ~17 ms (one measurement).\n\n## Tested versions\n\nLinux x64 binary from npm `@anthropic-ai/claude-code-linux-x64` (its sha256 equals the native\ninstaller's `manifest.json` checksum). **Static:** the extracted bundle's summary timer callback\ncarries a hook anchor. **Live:** `claude -p --model haiku` with\n`CLAUDE_CODE_AGENT_PROGRESS_SUMMARIES=1`, one subagent doing 8 steps of ~20 s, counting\n`[AgentSummary] Forking` in `--debug-file`.\n\n| version | published | sha256 | static | summary forks in a ~2.5-min agent run: no hook / hook `pass` / hook `off` | notes |\n|---|---|---|---|---|---|\n| 2.1.312 | 2026-10-27 | `4f4178ce41d8` | OK | **5 / 5 / 0** | `latest` on 2026-10-28; interactive: 6 no hook, 0 `off` |\n| 2.1.310 | 2026-10-23 | `b73676430af1` | OK | **5 / 5 / 0** | |\n| 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 |\n| 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 |\n| 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 |\n| 2.1.257 | 2026-09-01 | `9a64bda9d872` | OK | **1 / 1 / 0** (timer fired 11 / 11 / 0) | same as 2.1.280 |\n\n5 / 5 / 0 = the fork happens on this version, merely loading the hook changes nothing, and `off`\nremoves it.\n\n## Why there is no setting for this\n\n- **The documented switch only turns it on.** The SDK option `agentProgressSummaries` defaults to\n  `false`, but in the bundle it is one term of an OR, and every code path that writes it sets\n  `true`. So `false` changes nothing, and the undocumented `CLAUDE_CODE_AGENT_PROGRESS_SUMMARIES`\n  can only enable summaries too.\n- **The interval belongs to the server.** It comes from a per-model-family server flag and is\n  clamped to 30–240 s. The default is already the minimum, and no value disables the timer.\n- **The obvious env vars miss.** `DISABLE_NON_ESSENTIAL_MODEL_CALLS`, which some packagers set,\n  does not occur in the binary at all. `CLAUDE_CODE_FORK_SUBAGENT=0` and\n  `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1` do stop it, but only by taking fork subagents or\n  background agents with it.\n- **The prompt cache makes it cheaper, not cheap.** About 98 % of these calls re-read the\n  transcript from cache; the ~2 % that hit a cold cache carry over a third of its cost.\n- **Cost tools that read transcripts don't see it.** These requests are not written to the\n  session logs on disk ([#104358](https://github.com/anthropics/claude-code/issues/104358)); only\n  the OpenTelemetry export shows them. The docs section on background token usage doesn't list it\n  either, while Agent View's row summaries are documented as a Haiku-class model refreshing every\n  few minutes.\n- **A cheaper variant already exists, switched off.** 2.1.310 ships a `short` mode: about 2k\n  tokens of notes instead of the full transcript, optionally written by a smaller model. The\n  default is still the full transcript, and only server flags can change it.\n\n### Timeline (2026)\n\n| version (published) | what changed |\n|---|---|\n| 2.1.32 (Feb 5) | the code first appears; dead in public builds |\n| 2.1.72 (Mar 9) | reachable as an SDK opt-in |\n| 2.1.117 (Apr 21) | reachable in the interactive CLI behind the fork gate or a server rollout |\n| ≤ 2.1.220 (Jul 24) | forked skills are always summarized |\n| **2.1.232 (Aug 13)** | **on by default** for interactive background agents |\n| 2.1.300 (Oct 14) | interval becomes server-tunable, 30–240 s |\n| 2.1.310 (Oct 23) | the cheaper `short` mode lands, switched off |\n\nIn our telemetry its weekly share of spend went from 3.5–7 % in July (the fork gate was already\nopen for us) to 7.3 % and then 11.8 % in the week we picked up 2.1.233, and to about 16 % by\nmid-October.\n\n## License and disclaimer\n\nMIT © 2026 [Flamerix AI](https://flamerix.ai). Provided **as is**, without warranty of any kind;\nyou use it at your own risk. Flamerix AI is not liable for any damage, data loss or account\naction arising from its use. Changing a client's runtime behaviour may conflict with the terms of\nthe service you use it with; checking that is on you.\n\n\"Claude\" and \"Claude Code\" are trademarks of Anthropic, PBC. This project is independent and is\nnot affiliated with, sponsored or endorsed by Anthropic.\n", "url": "https://wpnews.pro/news/claude-code-burns-15-20-of-your-tokens-on-a-3-word-status-line", "canonical_source": "https://gist.github.com/flamerix-ai/c3905705cc4205e5de3011fa2e74efce", "published_at": "2026-10-29 03:33:42+00:00", "updated_at": "2026-10-29 04:11:47.756332+00:00", "lang": "en", "topics": ["ai-tools", "ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["Flamerix AI", "Claude Code", "Anthropic", "Opus", "Bun", "DRIM"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/claude-code-burns-15-20-of-your-tokens-on-a-3-word-status-line", "markdown": "https://wpnews.pro/news/claude-code-burns-15-20-of-your-tokens-on-a-3-word-status-line.md", "text": "https://wpnews.pro/news/claude-code-burns-15-20-of-your-tokens-on-a-3-word-status-line.txt", "jsonld": "https://wpnews.pro/news/claude-code-burns-15-20-of-your-tokens-on-a-3-word-status-line.jsonld"}}