{"slug": "i-measured-claude-code-s-prompt-cache-cost-three-ways-85-of-it-wasn-t-mine-to", "title": "I Measured Claude Code's Prompt-Cache Cost Three Ways. 85% of It Wasn't Mine to Trim.", "summary": "A developer measured Claude Code's prompt-cache costs across three launch paths and found that 85% of cache_creation tokens are fixed overhead from Claude Code itself, not user-controllable. The VSCode extension and VSCode-internal terminal both hit Anthropic's shared cache, while a standalone terminal (iTerm2) never did, resulting in zero cache_read tokens. Subagents showed the same cache split as their parent session, indicating they share the launch path rather than inheriting cache state.", "body_md": "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`\n\nstayed at 0 on every run.\n\n**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.**\n\nClaude Code writes a JSONL log for every session under `~/.claude/projects/<project-key>/`\n\n, and each assistant turn in that log carries a `usage`\n\nobject with `cache_creation_input_tokens`\n\nand `cache_read_input_tokens`\n\n. 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.\n\n``` python\n#!/usr/bin/env python3\nimport json, os\nfrom pathlib import Path\n\nproject_key = os.getcwd().replace(\"/\", \"-\")\nproject_dir = Path.home() / \".claude/projects\" / project_key\njsonl_files = sorted(project_dir.glob(\"*.jsonl\"), key=lambda f: f.stat().st_mtime, reverse=True)\n\njsonl_path = jsonl_files[0]\nturn = 0\nfor line in jsonl_path.read_text().splitlines():\n    try:\n        e = json.loads(line)\n        usage = e.get(\"message\", {}).get(\"usage\", {})\n        role = e.get(\"message\", {}).get(\"role\", \"\")\n        if usage and role == \"assistant\":\n            turn += 1\n            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)}\")\n            if turn >= 2:\n                break\n    except Exception:\n        pass\n```\n\nI saved it as `measure-cache.py`\n\nand 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.\n\n**Same project, three launch paths:**\n\n| launch path | cache_creation | cache_read |\n|---|---|---|\n| VSCode extension (chat panel) | 39,054 | 10,270 |\n| VSCode-internal terminal (CLI) | 39,054 | 10,270 |\n| standalone terminal outside VSCode (e.g. iTerm2) | 50,985 | 0 |\n\n`cache_read`\n\ncosts about 1/10 of `cache_create`\n\nfor 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`\n\non every run, meaning it paid the full `cache_creation`\n\nprice every single time.\n\n**Subagents don't inherit anything, they just land on the same door:**\n\n| origin | subagent cache_creation | subagent cache_read |\n|---|---|---|\n| launched from inside VSCode | 39,054 | 10,270 |\n\nSubagents launched from inside VSCode showed the same 39,054 / 10,270 split as the parent session — independent `cache_creation`\n\neach 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.\n\n**The cache_creation breakdown, layer by layer, same project:**\n\n| configuration | cache_creation | delta |\n|---|---|---|\n| nothing added (baseline) | 39,054 | — |\n| + 85 user skills | 41,999 | +2,886 for the skill list |\n| + CLAUDE.md files + session hooks | 46,367 | +4,368 total for user-controlled layers |\n| pre-optimization state (same project, before trimming) | 68,097 | +26,098 vs. the optimized 46,367 |\n\nThe user-controllable share of `cache_creation`\n\ncame 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.\n\nCall 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.\n\nSubagents do NOT inherit the parent session's cache. Each subagent pays its own\n\n`cache_creation`\n\ncost, so parallel subagent fan-out multiplies fixed costs linearly.\n\nThat 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`\n\nbefore any of my own configuration even enters the bill.\n\nThe script above is generic — point it at any project directory and it reads whichever session log is newest. To see your own numbers:\n\n`cache_read`\n\non 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`\n\nprice.\n\nWhat's the smallest project you'd need to strip down to find out whether your own launch path is hitting the cache?\n\n*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.*\n\nThis 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.", "url": "https://wpnews.pro/news/i-measured-claude-code-s-prompt-cache-cost-three-ways-85-of-it-wasn-t-mine-to", "canonical_source": "https://dev.to/nomurasan/i-measured-claude-codes-prompt-cache-cost-three-ways-85-of-it-wasnt-mine-to-trim-48hm", "published_at": "2026-07-12 17:37:12+00:00", "updated_at": "2026-07-12 17:45:20.436302+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-tools", "developer-tools"], "entities": ["Claude Code", "Anthropic", "VSCode", "iTerm2"], "alternates": {"html": "https://wpnews.pro/news/i-measured-claude-code-s-prompt-cache-cost-three-ways-85-of-it-wasn-t-mine-to", "markdown": "https://wpnews.pro/news/i-measured-claude-code-s-prompt-cache-cost-three-ways-85-of-it-wasn-t-mine-to.md", "text": "https://wpnews.pro/news/i-measured-claude-code-s-prompt-cache-cost-three-ways-85-of-it-wasn-t-mine-to.txt", "jsonld": "https://wpnews.pro/news/i-measured-claude-code-s-prompt-cache-cost-three-ways-85-of-it-wasn-t-mine-to.jsonld"}}