Skip to the content.

git why

Put the why in version control.

CI License Dependencies Single file Python

git why is a Git subcommand that records why each commit exists — as native git trailers on the commit message, so the reasoning travels with git clone, git fetch and git push and shows up in plain git log. No database, no service.

When you build software by directing an AI, that reasoning otherwise lives in a chat window that closes. Six months later the code is still there and the why is gone. git why keeps the rationale, the prompt, and the rejected alternatives attached to the commit that they explain.

git why HEAD showing the reasoning behind a commit

Install

pipx install git+https://github.com/maledadams/git-why

or drop the single file on your PATH — no build, no dependencies:

curl -fsSL https://raw.githubusercontent.com/maledadams/git-why/main/git_why.py -o ~/.local/bin/git-why
chmod +x ~/.local/bin/git-why

Git picks it up as a subcommand automatically: git why.

Quickstart

git why init                        # install the hook (once per repo; tip-only, never blocks)

git why commit -m "add breed filter" \
  -b "let users narrow cat results by breed" \
  --rationale "client-side filter on the already-fetched list" \
  --agent claude-sonnet-5

git why HEAD                        # reasoning for a commit
git why src/filter.ts              # reasoning for the last change to a file
git why log --since "7 days ago"   # the reasoning trail
git why log --agent claude-sonnet-5
git why export > reasons.ndjson    # every recorded reason, one JSON object per line
git why check origin/main..HEAD    # optional CI gate (see "How much it enforces")

You never have to use git why commit — a plain git commit --trailer "Why: ..." works the same. The wrapper is just shorter.

What gets stored

Reasoning is stored as trailers on the commit message. Why: is the one that matters; the rest are optional.

Trailer Meaning
Why: why this change exists
Why-Prompt: the instruction that produced it
Why-Rationale: why this approach
Why-Alternatives: what was considered and rejected
Why-Spec: requirement / issue it satisfies
Why-Agent: model + tool that wrote it
Why-Session: id grouping commits from one work session
Why-Confidence: low / medium / high / human-reviewed
Why-Skip: this commit deliberately has no reason, and why

How it works

How much it enforces

By default, nothing is blocked. git why init installs a commit-msg hook that stays out of your way. git config why.strict sets how loud it is:

Level On a substantial change with no reason…
nudge (default) commit goes through; one line on stderr saying how to add a reason
substantial commit is blocked until you add a reason (git why init --enforce)
all as above, on every non-merge commit (git why init --all)
off completely silent (git why init --silent)

“Substantial” = more than ~15 non-generated lines, and the subject isn’t chore/build/ci/revert/bump. Lockfiles, *.min.* and snapshots don’t count toward the size. Trivial changes are never touched, at any level. Tune it: git config why.threshold 30, git config why.skip-paths "docs/*,*.md".

At the substantial / all levels a weak reason is rejected too — a Why: that just repeats the subject, or filler like update / wip / fix.

Any commit can opt out on purpose, and the reason for skipping is recorded: git commit --trailer "Why-Skip: vendored, not our code".

For AI agents

git why agent-setup prints a block to paste into CLAUDE.md, .cursorrules, or wherever your agent reads instructions. The gist:

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 has to type. Trivial changes can use a bare git commit.

The default hook never blocks the agent — it just leaves a tip if a real change went in with no reason. Turn on --enforce once you trust the workflow.

Why not just keep the chat logs

Chat logs aren’t attached to the code, aren’t in the repo, don’t survive a clone, and nobody greps them. A trailer is one line, lives on the commit, and git log already shows it.

How git why compares

Approach In the repo Survives clone / push Structured & queryable Per-commit Enforceable
git why (trailers) yes yes yes — export, log filters yes yes (opt-in)
git notes yes no (not pushed by default) no yes no
Conventional Commits yes yes commit type only, not the reason yes via commitlint
ADRs (markdown files) yes yes yes no — project-level no
Chat / agent session logs no no no loosely no
Prose in the commit body yes yes git log \| grep only yes no

FAQ

What is git why?

git why is a small Git subcommand that records why each commit exists. The reason, the prompt that produced the change, the approach taken and the alternatives rejected are stored as git trailers on the commit message — so they live in the commit, survive clone/push, and show in git log.

How is git why different from git blame?

git blame attributes each line to the commit and author that last changed it — not the intent behind the change. git why <file> picks up where that stops: it shows the recorded reasoning for the last commit that touched the file.

Where does git why store the reasoning?

In the commit message itself, as Key: value trailers (Why:, Why-Rationale:, Why-Agent:, …), the same mechanism as Co-Authored-By: and Signed-off-by:. There is no database, no server, and nothing to keep in sync.

How is this different from git notes?

git notes attach data to a commit but are not pushed or fetched by default, have no schema, and no tooling around them. git why uses trailers, which are part of the commit and travel automatically, and adds a reader, filters, an NDJSON export, and an optional enforcement hook.

Does git why work with any AI coding agent?

Yes. git why agent-setup prints an instruction block to paste into Claude Code, Cursor, Aider, or any agent’s config. Any tool that runs git commit can record a reason.

Does git why send my data anywhere?

No. It shells out to your local git and nothing else — no network calls, no telemetry. Your commit messages never leave your machine.

Does it change my commit workflow?

No. Plain git commit --trailer "Why: …" works; git why commit is just a shorter way to type it. The hook installed by git why init only prints a one-line tip by default — --enforce makes it block.

Can I enforce a reason in CI?

Yes: git why check <range> --level substantial exits non-zero if a non-trivial commit in the range has no reason (or a lazy one). Trivial diffs — lockfile bumps, typos — are ignored.

What repos and languages does it support?

Any Git repository, any language — it only reads commit metadata. It needs git 2.32+ and Python 3.8+, and is a single file with zero dependencies.

Roadmap

Kept deliberately small. Planned next, roughly in order:

Contributing

Issues and PRs are welcome, including from first-timers — see CONTRIBUTING.md and the good first issue label.

License

MIT © Lucia Adams