<?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: Nikhil Kumar </title>
    <description>The latest articles on DEV Community by Nikhil Kumar  (@nickelsec).</description>
    <link>https://dev.to/nickelsec</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%2F4112586%2F1131bbfb-6580-4222-ae95-8f479fa82b60.jpg</url>
      <title>DEV Community: Nikhil Kumar </title>
      <link>https://dev.to/nickelsec</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nickelsec"/>
    <language>en</language>
    <item>
      <title>My side project cost $1,278 in tokens. I built a tool to find out where it went.</title>
      <dc:creator>Nikhil Kumar </dc:creator>
      <pubDate>Sun, 27 Sep 2026 16:57:54 +0000</pubDate>
      <link>https://dev.to/nickelsec/my-side-project-cost-1278-in-tokens-i-built-a-tool-to-find-out-where-it-went-1jmk</link>
      <guid>https://dev.to/nickelsec/my-side-project-cost-1278-in-tokens-i-built-a-tool-to-find-out-where-it-went-1jmk</guid>
      <description>&lt;p&gt;Quick question. Your last vibe coded project. How long did it take you, and what did it cost?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3kn4k34z7t94ig5l73c9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3kn4k34z7t94ig5l73c9.gif" alt=" " width="199" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not "about a week". Not "I'm on the Max plan so, nothing?". The actual answer. Which feature ate the most time. Which bug cost you forty bucks of tokens going in circles. Which afternoon you'd rather forget.&lt;/p&gt;

&lt;p&gt;I didn't know either, and it bugged me. So I built a tool that works it out, and it turns out the answer was sitting on my disk the whole time.&lt;/p&gt;

&lt;p&gt;The project I built it on came out like this: &lt;strong&gt;542 prompts, 32 sittings, 36 hours at the keyboard, and $1,278 at API rates.&lt;/strong&gt; Yes, that last one is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your agent has been keeping a diary
&lt;/h2&gt;

&lt;p&gt;Claude Code, Codex and Pi all write a transcript of every session to your machine. Every prompt, every file the agent touched, every command it ran, every token it burned. It's all in there, going back as far as your agent keeps it.&lt;/p&gt;

&lt;p&gt;Nobody reads these files, because they're unreadable. One session can run for a week and wander through a dozen unrelated things. It's a wall of JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;bough&lt;/strong&gt; reads them for you and draws the shape of what you actually built.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ffgnriz062cvl2vlavo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ffgnriz062cvl2vlavo.gif" alt="bough reading a project's history and drawing it" width="199" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You install it, type &lt;code&gt;bough&lt;/code&gt;, pick a project, and it opens in your browser. That's the whole setup. No account, no API key, and nothing leaves your machine. It doesn't even need the internet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install nickelsec/tap/bough
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Linux, Windows and &lt;code&gt;go install&lt;/code&gt; are on &lt;a href="https://www.bough.run" rel="noopener noreferrer"&gt;bough.run&lt;/a&gt;, it's one command each.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The map of your project
&lt;/h2&gt;

&lt;p&gt;The first thing you see is a line running left to right. That's time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Each big square on the line is a sitting.&lt;/strong&gt; A stretch where you sat down and worked, until you stopped for the night or went and did something else. Under each one it tells you the day, what time you started, and how long you were actually at the keyboard. "Fri 25 Sep, 5:21 PM · 7 min."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The smaller squares hanging off it are tasks.&lt;/strong&gt; This is the part I'm proudest of. Your agent thinks in sessions, and a session is useless as a unit of work because one of them can cover a week of unrelated stuff. bough works out where one piece of work ends and the next one starts. It looks at how long you paused, whether the agent had to compact its memory, and whether you changed subject and files at the same time. So instead of "session 7f3a9", you get "that time I rebuilt the export path" as its own thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every little circle is a single prompt you typed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hover anything and the path back to its sitting lights up, with a note saying what it was. Click it and the full record opens on the side, scrolled to that exact prompt, in your own words, untrimmed. Every typo intact. Humbling, honestly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each piece of work cost
&lt;/h2&gt;

&lt;p&gt;Behind every task there's a faint circle. Its size is how much that task was charged.&lt;/p&gt;

&lt;p&gt;Hover it and you get the breakdown in tokens and in dollars. Open the usage table from the rail and there's every sitting and every task in one place, with the same columns the agents themselves report.&lt;/p&gt;

&lt;p&gt;Here's what surprised me most. &lt;strong&gt;Most of the money isn't the model writing code.&lt;/strong&gt; It's the model re-reading the conversation. Every single reply, it reads everything so far again. On my projects that came out between 176 and 732 times more context read than output written. A long rambling chat can cost more than a hard piece of engineering, just because it's long.&lt;/p&gt;

&lt;p&gt;Once you see that, you'll probably start clearing context a lot more often.&lt;/p&gt;

&lt;p&gt;A few honest things about the dollar figure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's what the work &lt;em&gt;would&lt;/em&gt; have cost at published API rates. If you're on a flat subscription, you didn't pay this. Flip it around though and it's what your subscription saved you, which is a fun number to know.&lt;/li&gt;
&lt;li&gt;If you run a local model through Pi, with Ollama or llama.cpp, that work is $0, and it says "(local)" next to it so you know why.&lt;/li&gt;
&lt;li&gt;If a model has no published price, it says NA. It won't make up a number. A wrong number is worse than no number.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One task from early on reads 14 prompts, 5 failures, an hour and 29 minutes, and $34.77. Seeing it laid out like that, with every failed attempt in order, is a different experience from remembering it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your commits are on the map
&lt;/h2&gt;

&lt;p&gt;A task with a small mark on it is one that ended in a commit. The note gives you the hash and the message.&lt;/p&gt;

&lt;p&gt;bough also reads your project's git history, read-only, to check them. So a commit shows up with its real subject line and how many lines it added. Everything else in the drawing is worked out from your history. The commits are the part you can go and check yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  "You kept coming back to this file"
&lt;/h2&gt;

&lt;p&gt;This one is sneaky useful. For every sitting, bough notices which file you kept returning to, and how much of it actually changed each time.&lt;/p&gt;

&lt;p&gt;"kept coming back to index.html (4 times, 92 lines)"&lt;/p&gt;

&lt;p&gt;Coming back nine times to fix a typo is a very different afternoon from coming back nine times to rewrite the whole thing. Now you can tell which it was.&lt;/p&gt;

&lt;p&gt;It also draws dotted lines between sittings that went back to the same files. So when you pick something up again three days later, you can see the thread. On a big project there are a lot of these, so there's a switch to hide them when they get in the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filters, because you will have a lot of history
&lt;/h2&gt;

&lt;p&gt;The rail on the right is where it gets fun. Nothing disappears when you filter. Everything that doesn't match just fades, so you still see the whole shape around what you're looking for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search your own prompts.&lt;/strong&gt; Type a few words you remember writing and it rings every prompt that has all of them. Perfect for "that time I asked it to fix the timezone thing" when you have no idea which week it was.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filter by file.&lt;/strong&gt; Type &lt;code&gt;hero.css&lt;/code&gt; and every task that touched it lights up. Great for "when did this file start going wrong".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filter by date.&lt;/strong&gt; Pick a range, see only that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filter by cost.&lt;/strong&gt; One slider. Drag it and everything under the threshold fades. "Show me every task that cost more than $25" is one drag. You can switch it to any token count instead of dollars too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to show.&lt;/strong&gt; A few switches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only substantial sittings, which fades anything under five prompts so the quick "fix this typo" visits get out of the way.&lt;/li&gt;
&lt;li&gt;Show where it was hard, which colours the work where you rewrote the same file over and over and the prompts came thick and fast. I'll be honest, this one is a guess, and the app labels it as one. It's off until you ask for it.&lt;/li&gt;
&lt;li&gt;Show links between sittings, the dotted lines from before.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  It works in the terminal too
&lt;/h2&gt;

&lt;p&gt;Don't want a browser? Add &lt;code&gt;--text&lt;/code&gt; and you get the same thing as an indented list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fri 25 Sep     make it even worst
17:21          1 task, 4 prompts, 7 minutes
               kept coming back to index.html (4 times, 92 lines)

  make it even worst
    4 prompts, 12 changes, 3 failures, 7 minutes, 11k written, 24x context, $0.26
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Yes, that prompt says "make it even worst". I was testing on a deliberately terrible portfolio site. Don't judge.)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--json&lt;/code&gt; gives you the whole structure if you want to build something on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use /stats or ccusage?
&lt;/h2&gt;

&lt;p&gt;You should! They're good at a different question. Claude Code's &lt;code&gt;/stats&lt;/code&gt; and tools like ccusage tell you how much you've used and spent. They stop at a session, and a session is an opaque id that can cover weeks of unrelated work.&lt;/p&gt;

&lt;p&gt;bough answers &lt;strong&gt;what did I build, and what did each piece of it cost.&lt;/strong&gt; Knowing what one feature or one stubborn bug cost you is where the numbers actually start meaning something.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boring but important bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It only reads. It never writes to your agent's history or your repo.&lt;/li&gt;
&lt;li&gt;It never touches the network. Your prompts, your code and your costs stay on your machine.&lt;/li&gt;
&lt;li&gt;It's one Go binary, free, and MIT licensed.&lt;/li&gt;
&lt;li&gt;It reads &lt;strong&gt;Claude Code, OpenAI Codex CLI and Pi&lt;/strong&gt;, and finds all three automatically. If you use more than one on the same project, they show up in the same list.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Go look at your own receipt
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install nickelsec/tap/bough
bough
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then pick the project you're proudest of, or the one that nearly broke you. I promise at least one number in there will surprise you.&lt;/p&gt;

&lt;p&gt;It's still early, and the task boundaries are tuned on my own history, so if yours land somewhere weird I'd genuinely love to hear about it. Issues, ideas and "this number looks wrong" are all welcome on &lt;a href="https://github.com/nickelsec/bough" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you find out what your worst afternoon cost, tell me in the comments. I showed you mine.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vibecoding</category>
      <category>programming</category>
      <category>agents</category>
    </item>
    <item>
      <title>I parsed 76,801 lines of my own Claude Code history. Three things nearly broke my parser.</title>
      <dc:creator>Nikhil Kumar </dc:creator>
      <pubDate>Sun, 06 Sep 2026 16:42:15 +0000</pubDate>
      <link>https://dev.to/nickelsec/i-parsed-76801-lines-of-my-own-claude-code-history-three-things-nearly-broke-my-parser-h44</link>
      <guid>https://dev.to/nickelsec/i-parsed-76801-lines-of-my-own-claude-code-history-three-things-nearly-broke-my-parser-h44</guid>
      <description>&lt;p&gt;Claude Code writes every session to &lt;code&gt;~/.claude/projects&lt;/code&gt; as JSONL. I wanted to know what I'd actually built over three weeks on one repo, so I started reading those files.&lt;/p&gt;

&lt;p&gt;The format is undocumented. Here is what I measured across 28 transcripts, 275 MB and 76,801 lines, and the three things that gave me wrong numbers before I noticed.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The files replay, and counting lines overcounts by 3x
&lt;/h2&gt;

&lt;p&gt;Claude Code appends to a transcript when you resume or rewind a session, and it rewrites records it has already written. The same &lt;code&gt;uuid&lt;/code&gt; shows up more than once.&lt;/p&gt;

&lt;p&gt;My largest session:&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;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;lines&lt;/td&gt;
&lt;td&gt;36,676&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;distinct records&lt;/td&gt;
&lt;td&gt;10,964&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;uuids appearing more than once&lt;/td&gt;
&lt;td&gt;7,919&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So counting lines, which is the obvious first thing anyone does, overstates that&lt;br&gt;
session by more than three times. Worse, the overstatement is uneven, so there&lt;br&gt;
is no constant you can divide by to fix it.&lt;/p&gt;

&lt;p&gt;Deduplicate on &lt;code&gt;uuid&lt;/code&gt;, keeping the last copy. Later copies carry fields the&lt;br&gt;
earlier ones did not.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. One field is sometimes an object and sometimes a string
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;toolUseResult&lt;/code&gt; is an object 17,319 times and a bare string 490 times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// This is wrong, and it fails quietly&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Record&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ToolUseResult&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ToolUseResult&lt;/span&gt; &lt;span class="s"&gt;`json:"toolUseResult"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you type it as an object only, every string-valued line fails to decode. And&lt;br&gt;
if your reader skips a record on a decode error, which is a reasonable default,&lt;br&gt;
you lose it silently. In my own test fixture that dropped 85 of 340 records. I&lt;br&gt;
found it a week later, by accident, because a count didn't match.&lt;/p&gt;

&lt;p&gt;The fix is a custom unmarshaller that accepts either shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ToolUseResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;UnmarshalJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sc"&gt;'{'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="n"&gt;ToolUseResult&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;t&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;&lt;code&gt;message.content&lt;/code&gt; has the same problem: a list of blocks 52,641 times, a bare&lt;br&gt;
string 400 times.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Quiet commits arrive with no hash
&lt;/h2&gt;

&lt;p&gt;When the agent commits, the result carries the commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"toolUseResult"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gitOperation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"commit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sha"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"d0a65cc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"committed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"branch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Except Claude Code fills that in by reading what git printed. Silence git and&lt;br&gt;
the field never appears:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;commit command&lt;/th&gt;
&lt;th&gt;calls&lt;/th&gt;
&lt;th&gt;carried a hash&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;without &lt;code&gt;-q&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;53 (93%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;with &lt;code&gt;-q&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Relying on that field alone found 4 of one repository's 31 commits.&lt;/p&gt;

&lt;p&gt;The dependable signal is the shell command itself: a &lt;code&gt;git commit&lt;/code&gt; that returned&lt;br&gt;
without an error. Which leads to the trap I enjoyed least.&lt;/p&gt;
&lt;h2&gt;
  
  
  The bonus trap: commits that never happened
&lt;/h2&gt;

&lt;p&gt;If you detect commits by matching &lt;code&gt;git commit&lt;/code&gt; in shell commands, a command that&lt;br&gt;
&lt;em&gt;writes a file&lt;/em&gt; containing those words looks identical to one that runs them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; release.sh &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;SH&lt;/span&gt;&lt;span class="sh"&gt;'
git commit -m "release &lt;/span&gt;&lt;span class="nv"&gt;$VERSION&lt;/span&gt;&lt;span class="sh"&gt;"
&lt;/span&gt;&lt;span class="no"&gt;SH
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seven of one project's apparent commits were heredocs like this. The fix is to&lt;br&gt;
check whether a heredoc opens before the match. A real commit taking its message&lt;br&gt;
on a heredoc opens it &lt;em&gt;after&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why any of this mattered to me
&lt;/h2&gt;

&lt;p&gt;I was building a thing that draws your Claude Code history as a diagram: squares for days, smaller squares for tasks, a circle for every prompt, with commits marked on the work that produced them. Every number above is one I got wrong first and had to go back and fix.&lt;/p&gt;

&lt;p&gt;It's a Go binary, runs locally, reads files already on your disk and sends&lt;br&gt;
nothing anywhere. &lt;a href="https://github.com/nickelsec/bough" rel="noopener noreferrer"&gt;github.com/nickelsec/bough&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full format notes, including record types and the fields that carry less&lt;br&gt;
than you'd hope, are at &lt;a href="https://www.bough.run/docs/format" rel="noopener noreferrer"&gt;bough.run/docs/format&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you're parsing these files yourself, the replay is the one that will get you.&lt;br&gt;
It's silent, it's uneven, and every number downstream of it is wrong.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>data</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
