Max Narditdata & ai systems
← Articles
9 mindev

Claude Code background subagents and your usage limit: Anthropic's 99% status-line claim, and the cache writes you can measure

Isolation is the point of a subagent, and in my transcripts most of its bill was five-minute cache writes. Measured on a Max plan: background subagents about 11.5% of priced usage, 64% of that in cache writes. A Python script to reproduce it, and four levers with the part of the bill each one acts on.

Contents

On November 4, Anthropic’s developer account said background subagents in Claude Code now use less of your usage limit, because each status update costs “about 99% less.” The same day, Claude Code 2.1.318 shipped three more changes for background subagents. I wanted to know what that does to my own bill, so I went through two weeks of my transcripts. I couldn’t find the request Anthropic says got cheaper anywhere in them. What they do show is the subagent’s own work, and nearly two thirds of that cost is five-minute cache writes. The status-line change is about a different request, and the 2.1.318 cache fix targets one source of those writes, which, by a rough proxy, looked small in my logs.

The numbers below are from a Max subscription on the first-party API, October 22 to November 4. The window ends the day 2.1.318 shipped, so it is a baseline, not a before-and-after.

What did Claude Code 2.1.316 to 2.1.318 change for background subagents?

A notice fix, a cache fix and a new effort setting, spread over three releases. The 2.1.318 release notes fix the “Now using usage credits” notice reappearing “about every 30 seconds while a background subagent is running.” They move a background subagent’s shell command to the background after 3 minutes “on all providers, so the prompt cache doesn’t expire while it runs.” And on Bedrock, Vertex, Foundry, or with telemetry off, a subagent progress summary “can now wait up to 5 minutes for the subagent’s pending response” on a cold prompt cache.

2.1.316 added CLAUDE_CODE_SUBAGENT_EFFORT, the effort level subagents run at “when nothing else names one, instead of inheriting the session’s.” 2.1.317 made subagents on a different model from the main conversation use “that model’s saved effort level or its default,” and added a cost report to the Claude apps gateway admin API (GET /v1/organizations/analytics/cost), which breaks past spend down per developer, per day or per group.

None of the three sets of notes mentions the status-line change. It exists only in Anthropic’s post, which ties it to v2.1.316 and later.

What does Anthropic claim about Claude Code subagent status updates?

That the status line used to re-read the whole subagent conversation every 30 seconds, and now doesn’t. The first post:

Background subagents in Claude Code now use less of your usage limit.
A running subagent updates its status line every 30 seconds, and each
update used to re-read its whole conversation. Now Haiku 5.5 writes it
from short notes instead, so each update costs about 99% less.

The follow-up gives the session-level number: “Sessions with background subagents used about 5% less of the limit, so the same limit gets you about 6% more prompts.” It adds that the change is “live for most Pro, Max and Ultra users and for everyone on Team and Enterprise.”

All three figures are Anthropic’s own, measured across its users, with no method published. “Live for most” means a rollout, so an individual account may or may not have it. The 2.1.318 line about Bedrock, Vertex, Foundry and telemetry-off setups suggests that there the progress summary still waits on the subagent’s own cache. My read, not something the notes state: the cheap path is a first-party feature.

I could not check it in my own logs either. A subagent’s transcript under ~/.claude/projects/<project>/<session-id>/subagents/ records the subagent’s requests with their model, effort and token usage. In my window, a few hundred background-subagent requests ran on 2.1.316 or later, and none of the subagent files records a Haiku request. The only Haiku requests in the session files are sessions I ran on Haiku myself. Either the rollout hadn’t reached this account, or the status-line request isn’t written where I looked. I can’t tell which, so this audit can neither identify those requests nor confirm or refute the 5%.

Why do Claude Code subagents get a five-minute prompt cache?

Because Claude Code gives the one-hour cache by default only to the main conversation and a few helper requests. The prompt caching docs put every request in one of two buckets. On a subscription within plan usage, the main conversation gets one hour. Everything else, which includes subagents, workflows, teammates, forks and compaction, gets five minutes, apart from server-controlled helpers that Anthropic sets to one hour. Subagents “get five minutes even on a subscription until you choose a longer one.”

That is the reason for the 3-minute rule in 2.1.318. A subagent that waits on a build or a test suite for more than five minutes comes back to an expired cache, and its next request writes its context again at the cache-write rate. Moving the command to the background at minute three is meant to prevent that.

The docs add that a subagent starts a separate conversation with its own instructions and tool set, and its first request “doesn’t read the parent’s cache, because the two prefixes differ.” A fork is the exception: it inherits the parent’s prefix and reads the parent’s cache on its first request.

There is also an effect on the main conversation. Once a subscription runs past its plan limit and draws on usage credits, Claude Code drops the main conversation to the five-minute TTL as well, unless you pinned promptCacheTtl. That is the moment the “Now using usage credits” notice marks. Five-minute writes are cheaper per token, but a main conversation that sits idle for more than five minutes then rebuilds its cache on the next prompt, and on credits that rebuild is billed. It is the same drop that switches off idle compaction once you are on credits.

How do you measure what Claude Code background subagents cost you?

Start with /usage, then read the transcripts. On a Pro, Max, Ultra, Team or Enterprise plan, the /usage breakdown attributes recent usage to skills, subagents, plugins and MCP servers as percentages over the last 24 hours or 7 days, computed from local history. That gives you the share, without the cause. The Prompt cache (main) line in the same view “covers the main conversation only, not subagents,” so for cache behavior you need the files.

Each assistant line in a transcript carries a usage object (cache writes split by TTL, cache reads, output), the model, the effort and the Claude Code version. Each subagent file has a .meta.json next to it that says whether it ran in the background, and whether it was a fork. The script below sums a date range into buckets and weights them at Opus 5.5 list prices from the caching price table: $4 input, $5 five-minute write, $8 one-hour write, $0.20 cache read, $20 output per million tokens. On a subscription that is a weighting, not a bill; Anthropic doesn’t publish how tokens convert to plan usage.

python
# subagent_cost.py: python3 -I subagent_cost.py 2026-10-22 2026-11-05
import collections, glob, json, os, sys
from datetime import datetime, timezone
 
PRICE = dict(inp=4, w5=5, w1h=8, read=0.20, out=20)  # Opus 5.5 list, $ per MTok
start, end = (datetime.fromisoformat(d).replace(tzinfo=timezone.utc) for d in sys.argv[1:3])
seen, tok, info = set(), collections.defaultdict(collections.Counter), collections.Counter()
 
def bucket_of(path):
    if "/subagents/" not in path:
        return "main"
    try:
        meta = json.load(open(path[:-6] + ".meta.json"))
    except (OSError, ValueError):
        meta = {}
    if meta.get("agentType") == "fork":
        return "fork"
    return "sub-" + meta.get("requestShape", "unknown")  # sub-background / sub-foreground
 
def parts(u):
    # A request with an advisor call lists each model call under "iterations";
    # the top-level TTL split then covers only the first one.
    its = u.get("iterations") or [dict(u, type="message")]
    for it in its:
        cc = it.get("cache_creation") or {}
        yield it.get("type"), dict(
            inp=it.get("input_tokens") or 0, read=it.get("cache_read_input_tokens") or 0,
            w5=cc.get("ephemeral_5m_input_tokens") or 0, w1h=cc.get("ephemeral_1h_input_tokens") or 0,
            out=it.get("output_tokens") or 0, writes=it.get("cache_creation_input_tokens") or 0)
 
paths = sorted(glob.glob(os.path.expanduser("~/.claude/projects/**/*.jsonl"), recursive=True))
for path in paths:
    b, first, prev = bucket_of(path), True, None
    for line in open(path, errors="ignore"):
        try:
            rec = json.loads(line)
            msg, u = rec["message"], rec["message"]["usage"]
            ts = datetime.fromisoformat(rec["timestamp"].replace("Z", "+00:00"))
        except (ValueError, KeyError, TypeError):
            continue
        is_first, gap = first, prev is not None and (ts - prev).total_seconds() > 300
        first, prev = False, ts  # copied history still sets the order
        if not msg.get("id"):
            info[(b, "skipped: no message id")] += 1
            continue
        if msg["id"] in seen:  # streamed chunks and resumed sessions repeat a message
            continue
        seen.add(msg["id"])
        if not start <= ts < end:
            continue
        for kind, p in parts(u):
            t = tok[b if kind == "message" else "advisor (other model, not priced)"]
            t.update(p)
            t["w_unsplit"] += p["writes"] - p["w5"] - p["w1h"]
            t["w_first"] += p["writes"] if is_first else 0
            t["w_after_gap"] += p["writes"] if gap else 0
        info[(b, rec.get("version"), msg.get("model"), rec.get("effort"))] += 1
 
priced = {b: sum(t[k] * PRICE[k] / 1e6 for k in PRICE) for b, t in tok.items() if "advisor" not in b}
grand = sum(priced.values()) or 1
for b, t in sorted(tok.items()):
    w = max(t["writes"], 1)
    print(f"\n{b}: " + (f"{100 * priced[b] / grand:.1f}% of priced usage" if b in priced else f"{t['inp'] + t['out']} tokens"))
    if b in priced and priced[b]:
        print("  split", {k: f"{100 * t[k] * PRICE[k] / 1e6 / priced[b]:.0f}%" for k in PRICE if t[k]})
    print(f"  cache-read tokens per cache-write token: {t['read'] / w:.1f}")
    print(f"  writes on a file's first request: {100 * t['w_first'] / w:.0f}%")
    print(f"  writes after a >5 min gap: {100 * t['w_after_gap'] / w:.2f}%")
    print(f"  writes with no TTL split: {t['w_unsplit']}")
for key, n in sorted(info.items(), key=str):
    print(*key, n)

Two things to get right. Deduplicate by message id across all files, not per file: a streamed reply writes the same usage on several lines, and a resumed session copies earlier messages into a new file. My first pass got both wrong and overcounted the main conversation badly. Filter on each record’s timestamp, not on file modification time, or a long-running session drags weeks of old requests into the window. Some requests also carry an advisor call to a second model under usage.iterations; the script takes the TTL split from each iteration and reports the advisor tokens on their own line, unpriced, because they bill at another model’s rates. If you mostly use another model, swap in its row from the price table.

What did two weeks of Claude Code subagent transcripts show?

That background subagents were about a ninth of my priced usage, and that nearly two thirds of their cost was cache writes.

Oct 22 to Nov 4, priced at Opus 5.5 list ratesMain conversationBackground subagentsForks
Share of priced usage87.4%11.5%1.0%
Cache writes, share of the bucket52% (one-hour)64% (five-minute)63% (five-minute)
Cache reads, share of the bucket39%24%29%
Output, share of the bucket9%12%8%
Cache-read tokens per cache-write token29.69.411.7

Foreground subagents were 0.1%, too few to read anything into, and advisor calls to a second model are left out of all the shares. Background subagents logged about 9 cache-read tokens for every cache-write token; the main conversation about 30. On Opus 5.5 a cache read costs 0.05 times base input and a five-minute write 1.25 times, a factor of 25, so when reads outnumber writes by less than 25 to 1, writes are the bigger line.

Most of them don’t come from the start. First requests together account for 10% of background-subagent cache writes. Output can’t explain much either: at 12% of the cost, priced at $20, even if every output token were written back once at $5 it would cover about 3 of the 64 points. The remaining 85% or so is everything else that entered the cache on later turns: tool results, instructions, rebuilds after an invalidation. The transcripts don’t say which, so I can’t tell you how much of it is tool output. That is the part worth testing first, because it is the part you control.

Long pauses were rare. Requests that came more than five minutes after the same subagent’s previous request carried 0.73% of background-subagent cache writes. That is a proxy for the expiry the 3-minute rule targets, not a measurement of it: a gap is not proof that the cache expired, and those writes include new content. It does say my subagents seldom sat idle long enough for the question to come up. A workload with long builds inside subagents would look different, and the same line of the script shows which one you have.

The effort field didn’t match my settings. My settings.json says effortLevel: xhigh, and every background-subagent request in the window ran at medium. Two things explain it. My sessions start with --effort medium on the command line, and subagents on the session’s model inherit the session’s level. And per the model configuration docs, a top-level effortLevel in user settings “doesn’t count for Opus 5.5” anyway; it is the older form that still applies to Opus 5, Fable 5.1 and earlier models. The transcript’s effort field is what actually ran.

Which Claude Code settings change what background subagents spend?

Four levers, and the part of the bill each one acts on. None of them is measured here; they are where I would test first.

  • What a subagent reads in (part of the 64% write share). A full test log, a whole file where a line range would do, an unfiltered search: new content enters the cache as a write at 1.25 times input before any later turn can read it cheaply. Ask for summaries from tools that support them, cap output in the commands you give a subagent, and batch related work into one subagent so it doesn’t load the same files twice. A resumed subagent’s first request can read the cache the original run warmed, but only while that cache is still alive, so within five minutes by default.
  • Model (reprices every column). model: haiku in a subagent’s frontmatter for searches and summaries. Claude Code resolves a subagent’s model from the per-invocation parameter first, then the frontmatter, then CLAUDE_CODE_SUBAGENT_MODEL, so the variable alone is only a fallback. The Haiku 5.5 subagent post covers its effort levels and the 100K price line, which matter more on that model than anywhere else.
  • Effort (output, 12%, plus the thinking carried in context). Set effort: in the frontmatter for agents you define, or CLAUDE_CODE_SUBAGENT_EFFORT for the rest. As of today the variable is in the 2.1.316 changelog but not on the environment variables page, so check the effort field in a subagent transcript after setting it. CLAUDE_CODE_EFFORT_LEVEL beats every subagent setting.
  • Subagent cache TTL (the expiry case only). subagentPromptCacheTtl: "1h" or CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL=1h sets the hour for the whole outside-the-main-conversation bucket, and FORCE_PROMPT_CACHING_5M=1 overrides it. A one-hour write costs $8 against $5. Repricing my recorded five-minute writes at the hour rate adds 60% to 64% of the bucket, roughly 38% on the background-subagent line, before counting any writes it would avoid. It pays only when subagents often sit idle past five minutes, and mine rarely did.

Claude Code background subagents: is the 99% the number to watch?

Not for your own bill. Anthropic’s 5% is plan usage across its users, and I can’t reproduce it from one account. The number you can watch is in your own transcripts. In mine, background subagents were 11.5% of priced usage and their cache writes about 7% of the total, which makes the writes column the first place to look.

Those writes are also the price of the thing a subagent is for. A subagent keeps a test log or a crawl out of the main conversation, where it would be read again on every later turn until the next compaction. The subagent pays to cache it and then throws the context away. Whether that trade wins depends on the workload, and I haven’t run the same job both ways to measure it. Before you put several background subagents on a Pro, Max or Ultra plan, run the script once, look at that column, and trim what each subagent reads in before you trim how many you run.

01 / Discussion

No comment section here — all discussions happen on X.

Max Nardit@mnardit

02 / More articles