<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Diego Prieri</title>
    <description>The latest articles on DEV Community by Diego Prieri (@diegoprieri).</description>
    <link>https://dev.to/diegoprieri</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4224602%2F81440d96-da64-4f40-a5e6-c39f1bae417c.jpg</url>
      <title>DEV Community: Diego Prieri</title>
      <link>https://dev.to/diegoprieri</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/diegoprieri"/>
    <language>en</language>
    <item>
      <title>I measured 8 weeks of Claude Code: 87% lower cost, 86% first-try typecheck</title>
      <dc:creator>Diego Prieri</dc:creator>
      <pubDate>Tue, 03 Nov 2026 16:20:53 +0000</pubDate>
      <link>https://dev.to/diegoprieri/i-measured-8-weeks-of-claude-code-87-lower-cost-86-first-try-typecheck-gp2</link>
      <guid>https://dev.to/diegoprieri/i-measured-8-weeks-of-claude-code-87-lower-cost-86-first-try-typecheck-gp2</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;It turns out the data was already on my machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data Claude Code already keeps
&lt;/h2&gt;

&lt;p&gt;Claude Code writes every session to &lt;code&gt;~/.claude/projects/**/*.jsonl&lt;/code&gt;. Each line is an event, and two kinds of event matter here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Assistant responses&lt;/strong&gt; include a &lt;code&gt;usage&lt;/code&gt; block with token counts: uncached input, cache writes (split by 5-minute and 1-hour TTL), cache reads and output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool calls&lt;/strong&gt; include every &lt;code&gt;Edit&lt;/code&gt;, &lt;code&gt;Write&lt;/code&gt; and &lt;code&gt;Bash&lt;/code&gt; command the agent ran, with its output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's enough to answer two questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is prompt caching actually working, and what does it do to cost?&lt;/li&gt;
&lt;li&gt;When the agent edits code, how often does the first typecheck or test run pass?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wrote a small CLI, &lt;a href="https://github.com/diego-prieri/claude-code-metrics" rel="noopener noreferrer"&gt;claude-code-metrics&lt;/a&gt;, that reads those files and answers both. Nothing leaves your machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsx src/cli.ts                     &lt;span class="c"&gt;# everything&lt;/span&gt;
npx tsx src/cli.ts &lt;span class="nt"&gt;--since&lt;/span&gt; 2026-10-01  &lt;span class="c"&gt;# a period&lt;/span&gt;
npx tsx src/cli.ts &lt;span class="nt"&gt;--project&lt;/span&gt; my-app    &lt;span class="c"&gt;# sessions whose cwd contains "my-app"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Result 1: prompt caching
&lt;/h2&gt;

&lt;p&gt;Over 8 weeks and 7.5B input tokens:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input tokens read from cache&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;98.4%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input cost vs. no caching&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;87.4% lower&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Responses&lt;/td&gt;
&lt;td&gt;30,439&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  How the savings are computed
&lt;/h3&gt;

&lt;p&gt;Prompt caching changes the price of each input token, depending on where it comes from:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token type&lt;/th&gt;
&lt;th&gt;Price vs. base input&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Uncached input&lt;/td&gt;
&lt;td&gt;1×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache write, 5-minute TTL&lt;/td&gt;
&lt;td&gt;1.25×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache write, 1-hour TTL&lt;/td&gt;
&lt;td&gt;2×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache read&lt;/td&gt;
&lt;td&gt;0.1×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So the savings are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;savings = 1 − actual / uncached
actual   = input×1 + write5m×1.25 + write1h×2 + read×0.1
uncached = (input + write5m + write1h + read) × 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The base price cancels out, so the ratio holds for any model. In code it's a few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MULTIPLIERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;write5m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;write1h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ratios&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TokenTotals&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;CacheRatios&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uncached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write5m&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write1h&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;uncached&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;readShare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;savings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;MULTIPLIERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write5m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;MULTIPLIERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write5m&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write1h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;MULTIPLIERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write1h&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;MULTIPLIERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;readShare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;uncached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;savings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;uncached&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One gotcha: Claude Code logs &lt;strong&gt;one line per content block&lt;/strong&gt;, all with the same message id. If you don't deduplicate by id, you count the same response several times.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the number is so high
&lt;/h3&gt;

&lt;p&gt;An 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.&lt;/p&gt;

&lt;p&gt;Update (Nov 3): a reader pointed out that edits made from Bash (sed -i, cat &amp;gt;, tee) didn’t arm a check. They do now, and attempts went from 503 to 1,942. Updated rates: 84.8% typecheck and 76.2% tests on the first run; 86.5% of tests pass on unfiltered runs only (n=89). Thanks for the catch!&lt;/p&gt;

&lt;h2&gt;
  
  
  Result 2: first-try pass rate
&lt;/h2&gt;

&lt;p&gt;This 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.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Attempts&lt;/th&gt;
&lt;th&gt;Passed on first run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Typecheck&lt;/td&gt;
&lt;td&gt;205&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;86.3%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;298&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75.2%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  How "first try" is defined
&lt;/h3&gt;

&lt;p&gt;Each session is replayed in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An edit (&lt;code&gt;Write&lt;/code&gt;, &lt;code&gt;Edit&lt;/code&gt;, &lt;code&gt;MultiEdit&lt;/code&gt;) to a matching file &lt;strong&gt;arms&lt;/strong&gt; a check: &lt;code&gt;.ts/.tsx/.vue&lt;/code&gt; arms typecheck, code files arm tests.&lt;/li&gt;
&lt;li&gt;The next Bash command that runs that check (&lt;code&gt;tsc&lt;/code&gt;, &lt;code&gt;jest&lt;/code&gt;, &lt;code&gt;vitest&lt;/code&gt;, &lt;code&gt;npm test&lt;/code&gt;, …) is the &lt;strong&gt;first try&lt;/strong&gt;. Its output decides pass or fail (&lt;code&gt;error TS…&lt;/code&gt;, &lt;code&gt;N failed&lt;/code&gt;, &lt;code&gt;not ok&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Reruns before the next edit are ignored. They measure iteration, not the first attempt.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The caveat that matters most
&lt;/h3&gt;

&lt;p&gt;Agents love to pipe long output through &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt; or &lt;code&gt;tail&lt;/code&gt;, and a filtered output can hide the failing line. So the CLI also reports the rate on &lt;strong&gt;unfiltered runs only&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Unfiltered runs&lt;/th&gt;
&lt;th&gt;Pass rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Typecheck&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;52.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The samples are small, but the test number is a warning: treat 75% as an &lt;strong&gt;upper bound&lt;/strong&gt;. This is also why, in my workflow, the agent never commits without a gate that re-runs typecheck and tests with full output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Subscription plans aren't billed per token.&lt;/strong&gt; There, the 87% means "what these tokens would have cost on the API": a measure of efficiency, not money saved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This is observation, not an eval.&lt;/strong&gt; Tasks aren't controlled. To compare prompts or context, you need a harness that runs the same tasks under each variant. That's what I built &lt;a href="https://github.com/diego-prieri/agent-context-evals" rel="noopener noreferrer"&gt;agent-context-evals&lt;/a&gt; for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-existing failures&lt;/strong&gt; make every attempt look like a failure. Measure on a clean baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transcripts get cleaned up over time&lt;/strong&gt;, so the period is whatever is still on disk.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I took away
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Caching is not something to tune. It's something not to break.&lt;/strong&gt; Keep the prefix stable (instructions, tool definitions, project context) and the savings come on their own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agents are good, not reliable.&lt;/strong&gt; 86% on the first typecheck is impressive. The other 14% is why verification gates aren't optional.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure your own workflow.&lt;/strong&gt; Your numbers will be different from mine, and the data is already sitting on your disk.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code is MIT-licensed: &lt;a href="https://github.com/diego-prieri/claude-code-metrics" rel="noopener noreferrer"&gt;github.com/diego-prieri/claude-code-metrics&lt;/a&gt;. I'd love to see other people's numbers. If you run it, share yours in the comments.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>typescript</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
