Complexity: medium
My git blame now tells me why
git blame tells you who changed a line. Mine now tells me why.
I posted about this on LinkedIn, and a few people asked how it works. Here’s the long version. But first the problem, because the fix only makes sense once you’ve seen what goes wrong.
Git keeps the code, not the reason
Every change starts as a conversation. These days that’s mostly a chat with an AI agent: you describe what’s broken, it digs around, explains what it found, and writes the fix. Then it commits, and git saves exactly one part of that.
youThe bank sync fails every morning with "too many requests".
agentThe bank's API allows one request per second. We send 40 at once.
agentAdding a one-second pause between calls. It looks wasteful, but it's what the API needs.
youWorks. Ship it.
The chat, and the reason for that pause, are not saved anywhere git can see.
Git saves what changed, a short title, who did it and when. The why stays behind in the chat. And the why is often the only thing that explains a line that looks wrong.
I wrote about this back in June in Git wasn’t built for agents. Git is from 2005. Back then the reasoning lived in the head of whoever typed the code, and you could walk over and ask them. Now the one who typed it is an AI, and its head is a chat window you closed three days ago.
With agents, the missing reason turns into bugs
Since October 11, my machine has made 109 commits in 5 repos. I typed none of them: Stewie, my other agents and some scheduled jobs did. And every agent starts each new chat from zero. It doesn’t remember Monday’s chat, so it only knows what the code and the commit tell it.
That’s how a fixed bug comes back:
The fix
An agent fixes the sync by adding a pause. The reason is in the chat.
The cleanup
New chat: "make the sync faster". A fresh agent finds a one-second sleep in a loop, with no reason given. It deletes it. The tests pass.
The bug is back
"Too many requests", again. Nothing in the code or in git says why that line was there.
The Thursday agent didn’t do anything stupid. A one-second sleep inside a loop, with no reason given, looks like a mistake. Any careful developer might delete it. The information that would have stopped it existed. It was just in a chat nobody could find.
Without the why, every odd-looking fix is one cleanup away from being undone. And agents do a lot of cleanups.
The fix: put the chat one step away from the code
What I wanted was simple: from any line of code, get back to the conversation that put it there. So on October 11 I asked Stewie: what if every commit to a private repo had its chat attached? I said “build it” at 13:03. The first linked commit landed at 13:07.
Now git blame is only the first step:
youThe bank sync fails every morning with "too many requests".
agentAdding a one-second pause between calls. It looks wasteful, but it's what the API needs.
→ The pause stays. The agent makes the sync faster some other way.
Same line, same commit. But one command later, the agent (or I) can read the reason for the pause before touching it. That’s the whole point. The rest is plumbing.
The last piece wasn’t code. I added one rule to the notes all my agents read before they work: something broke or changed unexpectedly? Find the commit, read the chat behind it, and only then change it back. An agent that reverts without reading why just brings the old bug back faster.
How it works
It’s a small git hook, about 170 lines in total. Git can run little scripts at certain moments of a commit, and you can point every repo on a machine at one shared set of them (git config --global core.hooksPath). Mine does something at three moments:
git commit
An agent, a scheduled job or I run it, in any repo on the machine.
1 · Before the commit is saved
Both yes: two lines go at the bottom of the commit message.
2 · After it is saved
One line goes into a list on my machine: this commit came from that chat. Every commit, every repo, public ones too. The list never leaves the machine.
3 · After an amend or rebase
Git gives the commit a new ID. The new ID inherits the old one's chat, so the link survives.
Later: something broke · recall --commit a41c9e2
Opens the chat behind that commit: my request, and the agent's last 25 steps before it committed.
A few details that matter:
- How it knows an agent is committing. The agents tell on themselves. Claude Code and Codex each put their session ID in an environment variable, and my scheduled jobs set their own. If none is set, a human is typing.
- Why only private repos. Those two lines only mean something on my machine. On a public repo they’d be noise for everyone else, and they’d show how I work. Public commits still go into the local list, so I can trace them anyway.
- It runs the repo’s own hooks too. Pointing git at a shared hooks folder normally switches off each project’s own hooks (linters, formatters). Mine calls them after it’s done.
- It can never break a commit. Every error is swallowed, and the whole thing adds about half a second.
Same idea as Delta and Origin, much smaller
In June, Zed and Cursor announced the big versions of this idea: the conversation is part of the source. Both have shipped since. Zed’s Delta, the first app built on DeltaDB, has been in public beta since September 16. Cursor’s Origin has been in early beta since August 17.
The difference is how much each one keeps:
| My hook | Zed Delta | Cursor Origin | |
|---|---|---|---|
| What it keeps | A link from each commit to its chat | Every edit between commits, plus the chat | Your repos and pull requests, next to Cursor’s agents |
| Smallest step back | A commit | A single edit | A commit or pull request |
| Who can open the chat | Only me, on my machine | Your whole team, live | Your team, in Cursor |
| What you have to change | Nothing | Your workflow moves into Delta | Where your code is hosted |
Mine doesn’t store any conversation. It only points at where it already is. That’s also its limits: it’s only as fine as a commit, it only works on my machine, and if the chat is deleted the link goes nowhere. For one person running a lot of agents, that’s enough. For a team that reviews agent work together, Delta or Origin is the real thing.
Build your own in five lines
If you use Claude Code, save this as .git/hooks/prepare-commit-msg in a repo and make it executable:
#!/bin/sh
# Stamp the Claude Code session into every commit it makes.
[ -n "$CLAUDE_CODE_SESSION_ID" ] || exit 0 # a human is committing
[ "$2" = merge ] && exit 0 # leave merge commits alone
grep -q '^Session: ' "$1" && exit 0 # amend: keep the original
git interpret-trailers --in-place --trailer "Session: claude:$CLAUDE_CODE_SESSION_ID" "$1"
Claude Code already keeps every chat on your disk, as a file named after its session ID under ~/.claude/projects/. So when a line looks wrong, git log -1 --format='%(trailers:key=Session,valueonly)' <commit> gives you the ID, and that file is the why. Don’t use it on public repos as is.
The honest caveat
This isn’t version control for agents. It’s a sticky note on each commit that says where to look. It doesn’t save the work between commits, and it doesn’t help a team.
But the problem it solves is real, and I run into it every day. Code is easy to keep. What gets lost is the reason behind it.