{"slug": "i-measured-8-weeks-of-claude-code-87-lower-cost-86-first-try-typecheck", "title": "I measured 8 weeks of Claude Code: 87% lower cost, 86% first-try typecheck", "summary": "A frontend lead at a ~120k-line TypeScript codebase built claude-code-metrics, an MIT-licensed CLI that parses Claude Code's local session logs (~/.claude/projects/**/*.jsonl) to compute token-cache savings and first-attempt pass rates. Across 8 weeks and 7.5B input tokens, 98.4% of input tokens were read from cache, cutting input cost 87.4% versus no caching, while typecheck passed on the first run 86.3% of the time (205 attempts) and tests 75.2% (298 attempts). The author cautions that filtered command output can inflate pass rates, noting unfiltered test runs passed only 52.4% of the time, making 75% an upper bound.", "body_md": "Everyone has an opinion about coding agents: they're too expensive, or they're magic, or the code they write is garbage. I lead the frontend of a ~120k-line TypeScript app where agents write most of the code, so I wanted numbers instead of opinions.\n\nIt turns out the data was already on my machine.\n\nClaude Code writes every session to `~/.claude/projects/**/*.jsonl`. Each line is an event, and two kinds of event matter here:\n\n`usage` block with token counts: uncached input, cache writes (split by 5-minute and 1-hour TTL), cache reads and output.`Edit`, `Write` and `Bash` command the agent ran, with its output.\nThat's enough to answer two questions:\n\nI wrote a small CLI, [claude-code-metrics](https://github.com/diego-prieri/claude-code-metrics), that reads those files and answers both. Nothing leaves your machine.\n\n```\nnpx tsx src/cli.ts                     # everything\nnpx tsx src/cli.ts --since 2026-10-01  # a period\nnpx tsx src/cli.ts --project my-app    # sessions whose cwd contains \"my-app\"\n```\n\nOver 8 weeks and 7.5B input tokens:\n\n|  | Value | \n|---|---|\n| Input tokens read from cache | **98.4%** | \n| Input cost vs. no caching | **87.4% lower** | \n| Responses | 30,439 | \n\nPrompt caching changes the price of each input token, depending on where it comes from:\n\n| Token type | Price vs. base input | \n|---|---|\n| Uncached input | 1× | \n| Cache write, 5-minute TTL | 1.25× | \n| Cache write, 1-hour TTL | 2× | \n| Cache read | 0.1× | \n\nSo the savings are:\n\n```\nsavings = 1 − actual / uncached\nactual   = input×1 + write5m×1.25 + write1h×2 + read×0.1\nuncached = (input + write5m + write1h + read) × 1\n```\n\nThe base price cancels out, so the ratio holds for any model. In code it's a few lines:\n\n``` js\nexport const MULTIPLIERS = { input: 1, write5m: 1.25, write1h: 2, read: 0.1 };\n\nexport function ratios(t: TokenTotals): CacheRatios {\n  const uncached = t.input + t.write5m + t.write1h + t.read;\n  if (!uncached) return { readShare: 0, savings: 0 };\n  const actual =\n    t.input * MULTIPLIERS.input +\n    t.write5m * MULTIPLIERS.write5m +\n    t.write1h * MULTIPLIERS.write1h +\n    t.read * MULTIPLIERS.read;\n  return { readShare: t.read / uncached, savings: 1 - actual / uncached };\n}\n```\n\nOne gotcha: Claude Code logs **one line per content block**, all with the same message id. If you don't deduplicate by id, you count the same response several times.\n\nAn agent session re-sends a long, mostly stable prefix on every turn: system prompt, tools, project instructions, the conversation so far. Only the tail changes. That's the ideal case for caching: you pay to write the prefix once, then read it at 10% of the price for the rest of the session.\n\nThis is the number I care about most, because it measures how good the agent's first attempt is, not how good it gets after five retries.\n\n| Check | Attempts | Passed on first run | \n|---|---|---|\n| Typecheck | 205 | **86.3%** | \n| Tests | 298 | **75.2%** | \n\nEach session is replayed in order:\n\n`MultiEdit`) to a matching file `.ts/.tsx/.vue` arms typecheck, code files arm tests.`tsc`, `jest`, `vitest`, `npm test`, …) is the `error TS…`, `N failed`, `not ok`).\nAgents love to pipe long output through `head`, `grep` or `tail`, and a filtered output can hide the failing line. So the CLI also reports the rate on **unfiltered runs only**:\n\n| Check | Unfiltered runs | Pass rate | \n|---|---|---|\n| Typecheck | 10 | 100% | \n| Tests | 21 | 52.4% | \n\nThe samples are small, but the test number is a warning: treat 75% as an **upper bound**. This is also why, in my workflow, the agent never commits without a gate that re-runs typecheck and tests with full output.\n\nThe code is MIT-licensed: [github.com/diego-prieri/claude-code-metrics](https://github.com/diego-prieri/claude-code-metrics). I'd love to see other people's numbers. If you run it, share yours in the comments.", "url": "https://wpnews.pro/news/i-measured-8-weeks-of-claude-code-87-lower-cost-86-first-try-typecheck", "canonical_source": "https://dev.to/diegoprieri/i-measured-8-weeks-of-claude-code-87-lower-cost-86-first-try-typecheck-gp2", "published_at": "2026-11-03 16:20:53+00:00", "updated_at": "2026-11-03 16:28:35.157526+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-products"], "entities": ["Claude Code", "claude-code-metrics", "Anthropic", "TypeScript", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/i-measured-8-weeks-of-claude-code-87-lower-cost-86-first-try-typecheck", "markdown": "https://wpnews.pro/news/i-measured-8-weeks-of-claude-code-87-lower-cost-86-first-try-typecheck.md", "text": "https://wpnews.pro/news/i-measured-8-weeks-of-claude-code-87-lower-cost-86-first-try-typecheck.txt", "jsonld": "https://wpnews.pro/news/i-measured-8-weeks-of-claude-code-87-lower-cost-86-first-try-typecheck.jsonld"}}