Capture why AI wrote each commit, stored in Git-notes Gitwhy, a new open-source tool in beta, automatically records AI provenance for every git commit by installing a post-commit hook that captures environment variables such as $CLAUDE_CODE_MODEL, $COPILOT_MODEL, and $AI_AGENT, along with branch and commit message data, storing the information in git-notes. The tool, which requires no flags and works with standard git commit commands, infers intent, origin, ticket numbers, and model details, providing a provenance record via 'ghw why HEAD'. Beta— gitwhy is under active development. Feedback welcome via GitHub Issues . Every git commit already knows what changed. gitwhy remembers why . It drops a single post-commit hook into your repo. That hook is a passive listener — it catches the environment variables AI tools leave behind $CLAUDE CODE MODEL , $COPILOT MODEL , $AI AGENT , parses your branch name for a ticket, reads your commit message for intent, and writes a provenance record to git-notes. All without you typing a single flag. Two steps. Zero flags. Plain git commit. ghw init git commit -m "feat: add login handler" standard git, nothing special Later — what actually happened here? ghw why HEAD gitwhy provenance record ───────────────────────── schema: gitwhy/v1 target: commit a3f1d8c by: agent:claude-code when: 2026-06-25T10:00:00Z intent: add login handler origin: spec context: ticket: PROJ-42 prompt: unknown model: claude-sonnet-4-6 branch: feature/PROJ-42-login That's the entire default workflow. ghw init once, then never think about it again. ghw init installs a single script into .git/hooks/post-commit . Every time you or an AI agent run git commit , that hook fires automatically and silently captures: | What it sniffs | How | |---|---| Who / what agent | Sniffs $AI AGENT , $COPILOT AGENT MODEL — detects Claude Code, Copilot, Cursor, etc. | Which model | Reads $CLAUDE CODE MODEL , $COPILOT MODEL , $ANTHROPIC MODEL , $OPENAI MODEL | Why intent | Pulls the subject line from your commit message | Ticket number | Scans git branch for PROJ-123 patterns | Origin human vs spec | Infers from conventional commit type feat → spec , chore → human | Prompt if AI | Captures $COPILOT AGENT PROMPT or $CLAUDE CODE PROMPT | All of these environment variables are ephemeral — they exist only while the AI tool is running and vanish the moment the process exits. The hook intercepts them before they disappear. Just write a normal commit message. gitwhy parses it without any flags. git commit -m "feat: add login handler" intent: add login handler ← from commit message description origin: spec ← inferred from "feat" type ticket: PROJ-42 ← parsed from branch feature/PROJ-42-login model: claude-sonnet-4-6 ← detected from $COPILOT MODEL / $CLAUDE CODE MODEL by: agent:claude-code ← detected from $AI AGENT env var branch: feature/PROJ-42-login ← from git gitwhy reads conventional commit https://www.conventionalcommits.org/ format and maps it to provenance fields automatically: | Commit message | intent | origin | |---|---|---| feat: add login handler | add login handler | spec | fix: null pointer in auth | null pointer in auth | spec | perf: cache token lookup | cache token lookup | spec | chore: update deps | update deps | human | docs: add API examples | add API examples | human | test: cover edge cases | cover edge cases | human | feat : breaking auth change | BREAKING: breaking auth change | spec | Non-conventional messages fall back to LLM summarization if configured then "unknown" . gitwhy scans the branch name for a PROJECT-123 pattern and sets it as the ticket automatically. No flags needed. feature/PROJ-42-login → ticket: PROJ-42 fix/AUTH-7-token-null → ticket: AUTH-7 main → ticket: unknown gitwhy reads environment variables set by AI tools at commit time: | Tool | Env var read | Captured as | |---|---|---| | Claude Code | AI AGENT=claude-code/... | by: agent:claude-code | | Claude Code | CLAUDE CODE MODEL | model: claude-sonnet-4-6 | | GitHub Copilot CLI | COPILOT AGENT MODEL or COPILOT MODEL | by: copilot , model: gpt-4o | | GitHub Copilot CLI | COPILOT AGENT PROMPT | prompt: ... | | Any tool | ANTHROPIC MODEL , OPENAI MODEL , GITHUB MODEL , AI MODEL | model: ... | If no env var is found, model falls back to default model in .gitwhy/config.yaml , then "unknown" . Set a default model once, and every commit picks it up: ghw config set default model claude-sonnet-4-6 git log | git blame | gitwhy | | |---|---|---|---| | Shows what changed | ✅ | ✅ | ✅ | | Shows who changed it | ✅ | ✅ | ✅ | Shows why it changed | ❌ | ❌ | ✅ | | Captures AI model used | ❌ | ❌ | ✅ | | Links ticket/spec | ❌ | ❌ | ✅ | | Distinguishes human vs AI | ❌ | ❌ | ✅ | Zero-friction plain git commit | ✅ | ✅ | ✅ | git log and git blame tell you the what and who . gitwhy adds the why , what model , and what spec — the context that matters six months later when you're debugging AI-generated code. | If you want to... | Run this | |---|---| | Set up the hook in a repo | ghw init | | Check hook health and last capture | ghw status | | See provenance for a commit | ghw why HEAD | | Browse annotated history | ghw log --why | | Export all records | ghw audit export | | Set default model | ghw config set default model claude-sonnet-4-6 | | Toggle LLM summary | ghw config set summary.enabled false | Tweak behavior in .gitwhy/config.yaml : backend: git-notes auto capture: enabled: true default by: agent:opencode summary: enabled: true command: llm mode: filenames Everything above happens automatically. But if you ever need to override what the hook captured — for an edge case, a CI commit, or a manual annotation — pass flags to ghw commit : | Flag | What it does | |---|---| --by | Who: human , copilot , agent: