# Fork of flamerix-ai's no-agent-summary (Claude Code agent_summary off switch), v3.1: same blocking code, plus a per-build self-check that warns instead of failing silently, a session-start check,…

> Source: <https://gist.github.com/declanbx/c4511b8642c9d7179ab5761ff6473871>
> Published: 2026-10-30 00:03:12+00:00

Fork of [flamerix-ai's gist](https://gist.github.com/flamerix-ai/c3905705cc4205e5de3011fa2e74efce) (v3).
**The code that blocks the summaries is upstream's, unchanged.** This fork adds what makes it safe to leave
installed across Claude Code updates. Upstream's full README follows the line below, unchanged.

- **Fails loudly, not silently, inside the session.** Once per Claude Code build the hook scans the binary for the
anchors it matches on (~50 ms on a 276 MB build, cached by path + size + mtime) and records the verdict. A
session-start check (`check.sh` , added to Claude Code's`settings.json` by the installer) then shows, inside
the session,`agent-summary blocker: this Claude Code build (…) no longer contains the code it blocks` when an
update broke the match, or`agent-summary blocker: NOT loaded in this session` when the session was started
without`BUN_OPTIONS` . It is silent when all is well. (The hook's own stderr line is not enough: tested in an
interactive session, Claude Code's full-screen UI draws over it and it only reappears after exit.)
- **Proof it caught one.** The first suppressed summary timer on each build is recorded, so you can see the hook
did its job on the version you run, not only that it loaded.
- **`status.sh`** lists every build checked (anchors present, suppression seen) and every running Claude Code
process as`hooked` or`NOT hooked (restart it)` . Each hooked process leaves a marker while it runs, because
macOS does not expose another process's environment.
- **`install.sh`** copies the hook to`~/.local/share/no-agent-summary/` (a path without spaces, since`BUN_OPTIONS` splits on whitespace), adds one idempotent block to`~/.zshrc` or`~/.bashrc` , adds the session-start check to`$CLAUDE_CONFIG_DIR/settings.json` (default`~/.claude` ; existing hooks kept, previous file saved as`settings.json.nas-bak` ; needs`python3` ), and checks that`claude --version` loads the hook.`install.sh --uninstall` removes all three (tested: settings and rc file come back byte-identical).

```
git clone https://gist.github.com/c4511b8642c9d7179ab5761ff6473871.git no-agent-summary && bash no-agent-summary/install.sh
```

Restart running sessions afterwards; `status.sh` names the ones still unhooked.

Summary calls during one ~175 s subagent run, counted from `[AgentSummary] Forking` in `--debug-file`:

| session | how summaries were on | no hook | hook `pass` | hook `off` | 
|---|---|---|---|---|
| headless ( `claude -p --model haiku` ) | `CLAUDE_CODE_AGENT_PROGRESS_SUMMARIES=1` | 5 | 5 (v3) | 0 (v3, and v3.1) | 
| interactive TUI, background agent | default, nothing set | n/a | 5 (v3.1) | 0 (v3.1) | 

- The session-start check, in real interactive sessions: a deliberately broken anchor showed the "no longer
contains" message; a session started without `BUN_OPTIONS` showed "NOT loaded"; a normal hooked session showed nothing.
- Two settings people suggest instead, tested the same way with no hook: `disableAgentView: true` → still 5 calls
(it only hides the agents screen);`"env": {"CLAUDE_CODE_FORK_SUBAGENT": "0"}` → 0 calls, but it also turns off
fork subagents. If you never use forks, that one line is the simplest fix.
- Each summary call re-read the subagent's whole context (median 37,250 tokens, from cache) to return a 14-token label (10 calls).
- Upstream's matching costs almost nothing: 2.2 ms over 4.8 minutes of an interactive session (4,566 `setTimeout` calls, 3,305 callbacks stringified). So it is left as is.
- The gate admits the macOS native path (`~/.local/share/claude/versions/<ver>` ) and the desktop app's bundled
copy (`…/Claude/claude-code/<ver>/<hash>/claude.app/Contents/MacOS/claude` ;`Bun.main` is`/$bunfs/root/cli` in both). Agents were not run in the desktop app.
- Size on one user's machine: replaying the 30-s timer over a week of transcripts (190 background agents, mostly Opus 5.5) puts summaries at about 8% of list-price-equivalent spend. The replay was checked against a live run (5 calls predicted, 5 measured). Lower than upstream's 16%, partly because Opus 5.5 cache reads cost 0.05× input and the replay assumes a warm cache.

by [Flamerix AI](https://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](#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](https://flamerix.ai) 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](https://github.com/anthropics/claude-code/issues/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.

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):

``` bash
#!/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` :

```
claude daemon stop --any
```

| 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](#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.

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](https://news.ycombinator.com/user?id=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.

- **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](https://github.com/anthropics/claude-code/issues/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](#tested-versions) . Thanks to[lostsock](https://news.ycombinator.com/user?id=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](#which-processes-it-touches) ); a second load in the same process is
a no-op. Thanks to[Bedz01](https://gist.github.com/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](https://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.
