DEV Community

Cover image for I Kept Losing the "Why" Behind AI-Written Code, So I Built git why
Lucia Adams
Lucia Adams

Posted on AI-assisted

I Kept Losing the "Why" Behind AI-Written Code, So I Built git why

git blame tells you who changed a line and when. It never tells you why — especially when an AI agent wrote the change. git why is a zero-dependency CLI that stores the reasoning behind a commit as a git trailer, so the "why" survives a clone instead of dying in a chat transcript.

Short version, if you're skimming: git blame answers "who and when." Nothing answers "why," especially once an AI agent is the one writing your commits. git why is a small CLI that stores the reasoning as a git trailer — no server, no database, survives a clone — and a matching git why <path> command reads it back. Repo's at the bottom.

The problem, honestly

Here's how I build most things these days: I describe what I want to an agent, it writes the code, I review it, and — assuming it's not obviously wrong — it gets committed. Fast. Genuinely fast. I'm not going to pretend otherwise.

But somewhere in that loop there's a hole, and it took me embarrassingly long to actually name it.

A commit records what changed. Git blame will tell you who did it, down to the timestamp. What neither of them records is why. Why this approach and not the other one? What did we try first and throw away? What did I actually ask for, in my own words, before the agent turned it into a diff?

That reasoning exists — for about twenty minutes. Then the chat window closes and it's gone. Not deleted, exactly. Just... unreachable. Buried in a transcript I'm never opening again.

Three months later I'm staring at some function wondering why it's shaped the way it is, I run git blame, and the answer comes back: "you, in April." Cool. Thanks. Extremely helpful.

That's the gap. Not a tooling gap, really — a memory gap. The codebase remembers everything except the one thing I actually need when I come back to it.

What I actually wanted (a short, stubborn list)

I went in with a few non-negotiables, mostly because I've watched "let's add a system for X" turn into its own maintenance burden more than once:

  • The reasoning lives attached to the commit itself — not in a side database I have to keep in sync
  • No service to run. If it needs a daemon, I'm not doing it
  • Visible in tools I already reach for — git log, nothing new to learn
  • It has to survive a clone. If reasoning doesn't travel with the repo, it isn't really infrastructure, it's just notes

That last one ruled out basically every "AI decision log" tool I looked at. Pretty ones, too. But if the why lives in a hosted dashboard and the code lives in git, you've just built two sources of truth that will drift apart the moment nobody's watching. And somebody's always not watching.

The primitive was already there: git trailers

Turns out git already solved the structured-metadata-on-a-commit problem, ages ago. It's called a trailer — those Key: value lines you've definitely seen at the bottom of commit messages without thinking about them much: Co-Authored-By:, Signed-off-by:, Reviewed-by:.

Git ships tooling for this out of the box. git interpret-trailers parses them. git commit --trailer "Key: value" writes them. Nothing exotic.

And because a trailer is just part of the commit message, it's part of the commit object. It pushes. It clones. It shows up in plain git log with zero configuration on the other end. No plugin required to read it — you could git log and eyeball it manually if you had to.

So the schema for git why is just more of the same pattern:

Why: why this change exists
Why-Prompt: the instruction that produced it
Why-Rationale: why this approach, specifically
Why-Alternatives: what was considered and rejected
Why-Agent: model + tool that wrote it
Why-Session: id grouping commits from one work session
Why-Skip: this commit deliberately has no reason, and here's why
Enter fullscreen mode Exit fullscreen mode

That Why-Skip field matters more than it looks like it should. Not every commit needs a paragraph of justification — bumping a lockfile doesn't. But a tool that can't tell the difference between "no reason given" and "no reason needed" is going to train people to ignore it within a week. I'd rather it stay quiet on trivial stuff and only push when it counts.

The tool itself

git why is one Python file. No dependencies to install, nothing to configure beyond running the setup once. It reads the trailers back out in a format meant for a human glancing at a terminal, not a machine parsing JSON:

$ git why HEAD
commit a1b2c3d  2026-09-08 14:22  Lucia Adams
Add breed filter to cat-finder

  why       Let users narrow cat results by breed
  | rationale  client-side filter on the already-fetched list
  | rejected   server-side query per breed — extra requests
  | agent      claude-sonnet-5
Enter fullscreen mode Exit fullscreen mode

git why <path> gives you the reasoning behind the last commit that actually touched that file — think of it as git blame's companion, the part blame was always missing. git why log gives the full timeline if you want to scroll through the story of a project rather than one file.

And git why export dumps every recorded decision as NDJSON, which sounds like a small feature until you need it. "Show me every decision claude-sonnet-5 made last month" stops being a git log | grep regex nightmare and becomes a one-line jq query instead.

For setup, git why init installs a commit-msg hook. Important bit: it doesn't block anything by default. It just prints a one-line nudge if a meaningfully sized change landed with no reasoning attached, and leaves it there. If you want it stricter, git why init --enforce turns on actual blocking — and even then, it's not dumb about it. It skips trivial diffs (lockfile bumps, typo fixes) and it rejects lazy non-answers too, like a reason that's literally just "update" or one that echoes the commit subject back at you word for word.

Under the hood, writing a record is nothing more than git commit --trailer with the right keys. git why commit -m "..." -b "..." is just a shorter way to type the same thing.

Wiring it into an agent's workflow

This is really the part I built the tool for. Run git why agent-setup and it prints a block you drop straight into CLAUDE.md or .cursorrules. The instruction it gives the agent, roughly:

Commit with git why commit -m "<subject>" -b "<why this change exists>" --prompt "<the request>" --agent "<model>". Write the reason yourself, from the conversation — it's intent the user never actually typed into the code. Trivial changes can still use a bare git commit.

That's it. The agent writes its own reasoning down at commit time, while it still has the context, instead of me trying to reconstruct it from memory three months later. And if it forgets on something that mattered? The hook says something. Quietly. Once.

What this is not, to be clear

It's not a decision-tracking platform. It's not a chat archive, and it's definitely not trying to replace whatever ADR process your team already has for the big stuff. It's a convention plus a small reader, nothing more ambitious than that.

If the reasoning genuinely doesn't fit inside a commit message — if it needs paragraphs and diagrams and a room full of people nodding — that's not a git why problem. That's a signal the change itself is too big and probably needed a design doc before a single line got written.

FAQ

What's the difference between git why and git blame?
git blame tells you who last touched a line and when. git why tells you why that change happened — the reasoning, the alternatives considered, and (if an agent wrote it) which model and what prompt produced it.

Does git why need a server or database?
No. Everything's stored as a git trailer inside the commit message itself, so it lives in the commit object and travels with the repo on every clone and push. There's nothing to host and nothing to sync.

Will it block my commits?
Not unless you turn it on. git why init only prints a soft nudge by default. --enforce is opt-in, and even then it skips trivial diffs and rejects low-effort placeholder reasons.

Can I use it with any AI coding agent, not just Claude?
Yes — the trailer schema and the hook don't care which model wrote the commit. Why-Agent is just a string; put whatever tool or model actually did the work.

How do I query decisions across a whole project's history?
git why export dumps every recorded decision as NDJSON, which you can pipe into jq or load anywhere you'd normally process structured logs.

Try it

pipx install git+https://github.com/maledadams/git-why
Enter fullscreen mode Exit fullscreen mode

Repo's here, MIT licensed

If you've got opinions on the trailer schema — missing fields, different naming, whatever — that's exactly the kind of feedback I want. Open an issue.

Top comments (4)

Collapse
 
topstar_ai profile image
Luis Cruz

The use of git trailers to capture the "why" behind commits is a brilliant approach to bridging that memory gap you described. It not only keeps the reasoning tied to the commit itself but also ensures that it survives cloning, which is essential for long-term maintainability. One improvement idea could be to include a feature that allows users to easily reference or search past "whys" across multiple commits, creating a more interconnected understanding of project evolution. If you’re looking for support in refining this tool or expanding its functionality, I'd be glad to discuss a paid collaboration! What challenges have you faced in getting feedback on the usability of git why?

Collapse
 
maledadams profile image
Lucia Adams

Appreciate that, genuinely. And good timing, that's actually already there, just not obvious from a first read: git why log --agent claude-sonnet-5 or --spec <issue> filters the trail across commits, and git why export dumps the whole history as NDJSON if you want to query it with something like jq. I think the real gap is discoverability, not the feature. Most people won't find log filtering until they go digging.

Appreciate the offer, but I'm keeping this one solo for now, it's small enough that another set of hands would mostly mean coordination overhead rather than faster progress. If you want to open an issue or PR though, genuinely welcome, especially on the trailer schema since I designed it around my own workflow first.

Biggest usability wall so far, honestly: getting people to actually notice the filtering/export commands exist instead of only ever running git why HEAD. Might be a README problem more than a tool problem.

Collapse
 
mandraketech profile image
Navneet Karnani

Curious on the performance impact.

How many commits, and message size, before this becomes a pain for clone as well as the local git objects folder ?

I recently started using the git author and commiter env vars to capture the agent as the author and git user for the other one.

My challenge has been to get the agents to respect that protocol. They always end up using either the trailing or just within the message.

Collapse
 
maledadams profile image
Lucia Adams

I built this because I kept losing context on code written by agents weeks prior. Curious how everyone else is managing provenance or decision-making in AI-heavy codebases?