DEV Community

Sho Naka
Sho Naka

Posted on

I Measured Claude Code's Prompt-Cache Cost Three Ways. 85% of It Wasn't Mine to Trim.

I ran the same empty Claude Code project through three launch paths and read the cache tokens off the session log after every turn. One of those paths never touched the shared server-side cache at all — cache_read stayed at 0 on every run.

TL;DR: the launch path (VSCode extension, VSCode-internal terminal, or a standalone terminal like iTerm2) decides whether Claude Code hits Anthropic's shared prompt cache. And even after trimming CLAUDE.md files, skills, and hooks, roughly 85% of the remaining cache_creation cost turned out to be Claude Code's own fixed overhead — not anything I control.

What actually happened

Claude Code writes a JSONL log for every session under ~/.claude/projects/<project-key>/, and each assistant turn in that log carries a usage object with cache_creation_input_tokens and cache_read_input_tokens. To see what was actually happening turn by turn instead of guessing from the monthly bill, I wrote a short script that reads the newest session log for the current project directory and prints those two numbers per turn.

#!/usr/bin/env python3
import json, os
from pathlib import Path

project_key = os.getcwd().replace("/", "-")
project_dir = Path.home() / ".claude/projects" / project_key
jsonl_files = sorted(project_dir.glob("*.jsonl"), key=lambda f: f.stat().st_mtime, reverse=True)

jsonl_path = jsonl_files[0]
turn = 0
for line in jsonl_path.read_text().splitlines():
    try:
        e = json.loads(line)
        usage = e.get("message", {}).get("usage", {})
        role = e.get("message", {}).get("role", "")
        if usage and role == "assistant":
            turn += 1
            print(f"turn {turn}: input={usage.get('input_tokens',0)} cache_create={usage.get('cache_creation_input_tokens',0)} cache_read={usage.get('cache_read_input_tokens',0)}")
            if turn >= 2:
                break
    except Exception:
        pass
Enter fullscreen mode Exit fullscreen mode

I saved it as measure-cache.py and ran it from inside a baseline project — no CLAUDE.md, no hooks, no skills — right after opening a fresh session, then ran the exact same baseline through three different launch paths: the VSCode extension's chat panel, a terminal opened inside VSCode running the Claude Code CLI, and a standalone terminal (iTerm2) running the same CLI outside VSCode entirely.

The number that changed my mind

Same project, three launch paths:

launch path cache_creation cache_read
VSCode extension (chat panel) 39,054 10,270
VSCode-internal terminal (CLI) 39,054 10,270
standalone terminal outside VSCode (e.g. iTerm2) 50,985 0

cache_read costs about 1/10 of cache_create for the same tokens, so which launch path hits the shared cache is a real cost lever, not a rounding error. Both VSCode paths (extension and internal terminal) landed on identical numbers and hit the shared cache for 10,270 tokens. The standalone terminal never did — cache_read=0 on every run, meaning it paid the full cache_creation price every single time.

Subagents don't inherit anything, they just land on the same door:

origin subagent cache_creation subagent cache_read
launched from inside VSCode 39,054 10,270

Subagents launched from inside VSCode showed the same 39,054 / 10,270 split as the parent session — independent cache_creation each time, but landing on the same shared cache key as the parent because they share the VSCode launch path, not because they "inherit" anything from the parent session.

The cache_creation breakdown, layer by layer, same project:

configuration cache_creation delta
nothing added (baseline) 39,054
+ 85 user skills 41,999 +2,886 for the skill list
+ CLAUDE.md files + session hooks 46,367 +4,368 total for user-controlled layers
pre-optimization state (same project, before trimming) 68,097 +26,098 vs. the optimized 46,367

The user-controllable share of cache_creation came out to about 15% (7,313 / 46,367 tokens) in this project. The remaining roughly 85% is Claude Code's own fixed overhead — the 39,054-token floor that showed up before I added a single skill, hook, or CLAUDE.md file.

The rule I extracted

Call it the fan-out tax: every subagent Claude Code launches pays its own fixed cache-creation cost, and paying it once doesn't make it cheaper for the next subagent.

Subagents do NOT inherit the parent session's cache. Each subagent pays its own cache_creation cost, so parallel subagent fan-out multiplies fixed costs linearly.

That reframes what trimming CLAUDE.md is actually worth. The ~15% I can control in a single session is a modest saving on its own. But that saving gets multiplied by however many subagents run in parallel, while the 39,054-token fixed floor gets paid by every subagent regardless of what I trim. Ten subagents fanning out in parallel means the fixed floor alone is 390,540 tokens of cache_creation before any of my own configuration even enters the bill.

Try it yourself

The script above is generic — point it at any project directory and it reads whichever session log is newest. To see your own numbers:

  1. Pick an empty or near-empty project directory.
  2. Open it with the Claude Code VSCode extension, send one message, then run the script.
  3. Open the exact same directory in a terminal outside VSCode (iTerm2, Terminal.app, a plain SSH session) and run the CLI cold, then run the script again.
  4. Compare cache_read on turn 1 between the two runs.

If it reads 0 in the second run, that launch path isn't hitting your shared cache, and every session from that path is paying full cache_creation price.

What's the smallest project you'd need to strip down to find out whether your own launch path is hitting the cache?


Sho Naka (nomurasan). I measure a cost before I trust an assumption about it. Measured on Claude Code v2.1.126, claude-sonnet-4-6, macOS 25.4.0, 2026-05-03.

This is an adapted English version of an essay I first wrote in Japanese, on Qiita. I worked with AI to shape and translate the piece; the script, the measurements, and the conclusions are my own.

Top comments (0)