Claude Code agent_summary: Measure and Reduce Token Overhead

Measure the model requests behind Claude Code background-agent progress labels. Compare one team's 16.4% estimated cost share with the Claude Code team's 2% overall and 6% terminal figures. Use a local OpenTelemetry receiver to find your own share, then assess the 2.1.311+ cache fix, broader agent controls, and community patches.

Contents
Claude Code agent_summary: Measure and Reduce Token Overhead

Update Claude Code to 2.1.311 or later, then measure agent_summary through OpenTelemetry before disabling agent features. These progress-summary requests can add cost while background agents work. The widely shared 16.4% figure is one team’s estimated cost share, not a universal token surcharge.

What agent_summary does

agent_summary generates progress labels through separate model requests. In dblumenau’s Claude Code 2.1.312 report, each request sent a working background agent’s transcript to its own model. The requested result was a three-to-five-word label. That reproduction used Haiku, so this mechanism is not specific to Opus.

Working background agents can trigger these requests roughly every 30 seconds. Unchanged transcripts can skip ticks, as the official changelog documents. Count actual requests instead of multiplying elapsed minutes by two.

Long context can therefore matter more than the visible label’s length. Inspect the reported model and cache usage when investigating the cost.

This is separate from your configurable terminal status bar. The custom status-line documentation describes a local script that consumes no API tokens. Removing that script does not address this separate model-request mechanism.

What 16.4%, 2%, and 6% actually measure

The three cost percentages describe different populations. As of 2026-11-01, the relevant published reports are:

ReportPopulation and windowReported summary cost shareCost basis
Flamerix AIOne team’s measured week16.4%Claude Code’s per-request cost_usd estimates
Claude Code team member edwinarbusUsers sending telemetry, during the week preceding the October 29 replyAbout 2%API list-price cost
Same team reply, terminal subsetTerminal usage within that telemetry sampleAbout 6%API list-price cost

Flamerix separately reported that summaries represented 19.7% of API requests. Request share and cost share have different denominators. Neither establishes the fraction of your subscription allowance consumed by summaries.

Use these reports as a reason to measure your workload. A team delegating many long tasks can differ substantially from a developer using mainly the foreground conversation.

Update first, without assuming a fixed saving

Version 2.1.311+ addresses expensive summary requests that miss the prompt cache, according to the Claude Code team reply. That claim concerns a cache fix, not removal of summary requests. It does not establish a fixed saving for your sessions.

For a native installation, run this in Bash:

claude --version
claude update
claude --version

Restart Claude Code after updating. If a package manager owns the installation, use its update command from the official setup instructions. Check the resulting version; an update channel or organization policy can constrain it.

The same team reply describes work on a cheaper mode using short notes and a smaller model. It gives no launch date. As of 2026-11-01, there is no documented public setting for enabling that mode. Do not budget for its savings yet.

Measure your session with OpenTelemetry

Collect claude_code.api_request log events and group them by query_source. The monitoring reference documents that attribute, the reported model, and estimated cost_usd.

If your organization already collects these events, use its backend. Filter one session or a controlled time window, count API-request events, and sum cost_usd by source. Add model as a second grouping when comparing models.

The local example below needs Bash, Python 3, and an already authenticated Claude Code installation. Use it on a machine whose telemetry destination you control. It keeps aggregates in memory and prints metadata without requiring raw prompt or response capture.

Start the local receiver

Run this in terminal one. It accepts OTLP HTTP JSON on loopback and prints cumulative source totals:

python3 -u - <<'PY'
import gzip, json, math
from collections import defaultdict
from http.server import BaseHTTPRequestHandler, HTTPServer

rows = defaultdict(lambda: [0, 0.0, 0])
models = defaultdict(set)
seen = set()

def scalar(value):
    return next((value[k] for k in ("stringValue", "doubleValue", "intValue", "boolValue")
                 if k in value), None)

def report():
    count = sum(row[0] for row in rows.values())
    cost = sum(row[1] for row in rows.values())
    missing = sum(row[2] for row in rows.values())
    print("\nquery_source\trequests\trequest_%\tknown_USD\tcost_%\tmissing_cost\tmodels")
    for source, (n, usd, absent) in sorted(rows.items()):
        share = f"{100 * usd / cost:.2f}" if cost > 0 and not missing else "n/a"
        names = ",".join(sorted(models[source]))
        print(f"{source}\t{n}\t{100 * n / count:.2f}\t{usd:.6f}\t{share}\t{absent}\t{names}")
    print(f"Total API records: {count}; missing costs: {missing}", flush=True)

class Receiver(BaseHTTPRequestHandler):
    def log_message(self, *args):
        pass

    def do_POST(self):
        if self.path != "/v1/logs":
            self.send_error(404)
            return
        raw = self.rfile.read(int(self.headers.get("Content-Length", "0")))
        try:
            if self.headers.get("Content-Encoding") == "gzip":
                raw = gzip.decompress(raw)
            data = json.loads(raw)
        except (ValueError, OSError):
            self.send_error(400)
            return
        for resource in data.get("resourceLogs", []):
            for scope in resource.get("scopeLogs", []):
                for record in scope.get("logRecords", []):
                    a = {item["key"]: scalar(item["value"])
                         for item in record.get("attributes", [])}
                    event = a.get("event.name") or scalar(record.get("body", {}))
                    if event in ("user_prompt", "claude_code.user_prompt"):
                        print("Received user_prompt event.", flush=True)
                    if event not in ("api_request", "claude_code.api_request"):
                        continue
                    identity = a.get("request_id") or json.dumps(record, sort_keys=True)
                    if identity in seen:
                        continue
                    seen.add(identity)
                    source = str(a.get("query_source") or "(missing)")
                    models[source].add(str(a.get("model") or "(missing)"))
                    row = rows[source]
                    row[0] += 1
                    try:
                        usd = (float(a["cost_usd"]) if a.get("cost_usd") is not None
                               else float(a["cost_usd_micros"]) / 1_000_000)
                        if not math.isfinite(usd) or usd < 0:
                            raise ValueError()
                        row[1] += usd
                    except (KeyError, ValueError, TypeError):
                        row[2] += 1
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(b"{}")
        report()

server = HTTPServer(("127.0.0.1", 4318), Receiver)
print("Listening on 127.0.0.1:4318. Stop after Claude exits with Ctrl+C.", flush=True)
try:
    server.serve_forever()
except KeyboardInterrupt:
    pass
finally:
    server.server_close()
    report()
PY

Launch the measured session

Run this in terminal two. The subshell limits these environment changes to the measured process:

(
  export CLAUDE_CODE_ENABLE_TELEMETRY=1
  export OTEL_LOGS_EXPORTER=otlp
  export OTEL_METRICS_EXPORTER=none
  export OTEL_TRACES_EXPORTER=none
  export OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/json
  export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://127.0.0.1:4318/v1/logs
  export OTEL_LOGS_EXPORT_INTERVAL=1000
  claude
)

Choose a normal task that warrants background work. For example, ask a background agent to review authentication tests without editing files. Continue working in the main conversation, and confirm that the background agent actually starts.

Keep this an interactive session, without piping Claude’s output. Replacing it with claude -p changes the execution mode and can change summary behavior.

Terminal one should first report Received user_prompt event. after you submit a prompt. API-request rows should then appear. Let the work finish, exit Claude normally, and allow the final export to arrive before stopping the receiver with Ctrl+C. Copy the final table from terminal scrollback.

Interpret the result and diagnose missing data

Read the agent_summary row for your measured interval. request_% uses all captured API-request events as its denominator. cost_% uses all captured estimated API-request costs:

summary cost share = 100 × summary estimated USD / all captured estimated USD

The models column lists models reported for each source. For a cache investigation in an existing backend, also inspect input_tokens, cache_read_tokens, cache_creation_tokens, and output_tokens.

The receiver keeps missing sources in the denominator as (missing). It marks cost percentages n/a if any captured request lacks a valid cost. Resolve missing fields before treating the result as a complete breakdown.

No agent_summary row means none was observed in that window. It does not prove the feature is disabled. First confirm that prompt events, API-request events, and actual background work were present.

If nothing arrives, check that the receiver is running, the protocol is http/json, and the endpoint ends in /v1/logs. An organization-managed destination may override local configuration. For further diagnosis, launch claude --debug-file ./otel-debug.log inside the same subshell. Inspect [3P telemetry] exporter errors using the monitoring guide.

Why transcripts, /usage, and bills can disagree

A missing transcript row can hide attribution without hiding the overall session total. In issue #104358, rabitlord1324 reports missing background-request records while describing Claude Code’s internal accounting as correct.

The current usage documentation describes session totals and per-model usage. It does not provide the query_source breakdown needed here. Do not assume that every summary is absent from /usage merely because a transcript parser misses it.

cost_usd remains an estimate. Claude Code normally uses list prices, but a managed modelPricing table can change its reporting rates. Subscription allowance consumption and API charges are separate quantities. Reconcile pay-as-you-go results against your provider’s actual usage records.

For BetterToken users, the documented Claude Code Base URL is https://bettertoken.ai. Its catalog lists claude-opus-5-5 as of 2026-11-01. Compare the measurement window with model, token usage, and charges in Workspace.

Choose a reduction that preserves useful work

Start with a narrower task or fewer background agents if the measured overhead matters. As of 2026-11-01, no summary-only off switch is documented in the environment-variable or subagent references.

OptionWhat it changesTradeoff
Fewer or shorter background tasksReduces opportunities for repeated transcript summariesCan reduce useful parallelism; compare completion time
A smaller model for a bounded subagentChanges the model doing the delegated workCheck output quality and the model actually reported in telemetry
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 claudeBroadly disables background execution through the affected toolsClaude waits for subagents to finish before continuing
CLAUDE_CODE_FORK_SUBAGENT=0 claudeTurns off autonomous conversation forksBackground subagents can still run; summary suppression is not guaranteed
Community Bun preload patchAttempts to intercept the summary timerRuns third-party code inside Claude Code and can stop working after an update

The background-task variable removes run_in_background and stops automatic backgrounding through the affected tools. MCP tasks, the Monitor tool, and separate background sessions have exceptions or separate controls. It is not a switch for every background activity.

The fork-mode control is documented, but its summary-suppression side effect is not guaranteed. On 2.1.312, chrisvaillancourt reported suppression on macOS. dblumenau still observed requests on Linux. Configuration and execution mode differ between reports.

Set a control before launching a fresh session. Check for conflicting env entries in ~/.claude/settings.json and project settings; they can replace inherited shell values.

Treat the Flamerix preload as an experimental community workaround. Review a pinned revision before considering it. Verify request counts after each client update; a frozen label alone does not show that requests stopped.

For bounded research, follow the read-only subagent model-verification guide. If large tool definitions inflate context, use the MCP context cost comparison to test whether a smaller tool surface helps.

Change one variable at a time. Compare the same task, model, context conditions, accepted result, and elapsed time alongside the source-specific cost. Restore background work if losing useful concurrency costs more than the measured saving.

Ready to optimize your LLM workflow?

Join thousands of developers building faster, smarter, and more cost-effective AI applications with BetterToken.

Get Started for Free