From git blame to AI blame: per-line provenance for AI-era code
git blame answers "who wrote this" with a name, a SHA, and a date. In an AI-assisted
codebase that is necessary but no longer sufficient. Here's the upgrade, per-line lineage
that includes the prompt, the model, the agent, and the test result that produced each line.
- git blame resolves a line to the human who committed it; for agent code the committer is not the author.
- h5i recall blame --show-prompt adds the agent and test status per line, and the prompt per commit boundary.
- Attribution is commit-granular and OID-keyed, so it is exactly as durable as the commit graph — squash and rebase collapse prompts.
Provenance, who asked, with what prompt, from what context, is a core property of an
auditable workspace. Per-line AI blame
is how that provenance reaches the developer who later needs to understand a line of code.
git blame is one of the most-googled git commands ever shipped, and for good reason. The
sequence "see suspicious line → run blame → read commit message → understand the change" is a
foundational debugging primitive. It works because in a pre-AI codebase, the author's name
plus the commit message is enough context, the human author knew why they wrote that line,
and the commit message captures the why with reasonable fidelity.
That model breaks the moment a line was written by Claude. The git author is the human who
ran git commit, but they didn't write the line, they reviewed it. The commit message
summarizes the user-visible intent ("add retry logic") but loses the actual prompt, the model
version, the agent identity, and the tests that ran. When you're debugging at 2am, the
difference between "Alice wrote this in 2026" and "Alice prompted claude-sonnet-4-6 with
'add exponential backoff' and 42 tests passed afterwards" is the difference between an
hour of guessing and a one-line confirmation.
h5i extends git's blame model with that data. Same command shape, more answers.
The two-output upgrade
Standard git blame on a line:
$ git blame -L 88,90 src/http_client.rs a3f9c2b9 (Alice 2026-03-27 14:02:11 +0000 88) async fn send_with_retry(req: Request) -> Result<Response> { a3f9c2b9 (Alice 2026-03-27 14:02:11 +0000 89) let mut delay = Duration::from_millis(100); a3f9c2b9 (Alice 2026-03-27 14:02:11 +0000 90) for attempt in 0..MAX_RETRIES {
Same lines under h5i recall blame:
$ h5i recall blame src/http_client.rs --show-prompt STAT COMMIT AUTHOR/AGENT | CONTENT prompt: "add exponential backoff with jitter to the HTTP cli…" ✅ a3f9c2b9 AI:claude-code | async fn send_with_retry(req: Request) -> Result<Response> { ✅ a3f9c2b9 AI:claude-code | let mut delay = Duration::from_millis(100); ✅ a3f9c2b9 AI:claude-code | for attempt in 0..MAX_RETRIES {
The first column is the test status carried by the commit that owns each line
(✅ passing, ❌ failing, blank when no test
metrics were captured). The AUTHOR/AGENT column reads AI:<agent> for an
AI-tagged commit and Human otherwise — so a quick scan tells you which spans of a file
are human-written. With --show-prompt, the human prompt is printed once per commit
boundary (not once per line), keyed by the line's owning commit, so it stays readable on a long
function. That's the difference between knowing that Alice landed the change and knowing
what she actually asked for.
Two honest caveats on this view. The AI:<agent> label is the agent identity
(claude-code, codex, …), not the model — the model name, token count, and
integrity severity ride along in the commit's provenance record (next section) and surface in
h5i recall log rather than in the per-line blame. And attribution is
commit-granular: blame resolves a line to its owning commit, and provenance hangs
off that commit. If you batch ten prompts into one commit, all ten lines share that commit's single
recorded prompt. Per-prompt resolution is exactly as fine as your commit boundaries.
Where the data lives: a git note per commit
The provenance fields are stored in refs/h5i/notes as JSON keyed by commit OID — a git
note, not a parallel database, so it travels with the object graph and can never drift out of sync
with the commit it describes. Each AI-tagged commit has an H5iCommitRecord attached:
{
"git_oid": "a3f9c2b9...",
"parent_oid": "7216039...",
"ai_metadata": {
"model_name": "claude-sonnet-4-6",
"agent_id": "claude-code",
"prompt": "add exponential backoff with jitter to the HTTP client...",
"usage": { "prompt_tokens": 312, "total_tokens": 1180, "model": "claude-sonnet-4-6" }
},
"test_metrics": {
"tool": "pytest",
"passed": 42, "failed": 0, "skipped": 0, "total": 42,
"duration_secs": 1.23, "exit_code": 0
},
"timestamp": "2026-04-12T14:02:11Z"
}
Integrity is the one field that is not stored here: rather than freezing a possibly-stale
verdict, h5i recomputes it on demand by re-running its rule set against the commit's own
parent→commit diff (verify_commit_integrity), grading each commit
Valid / Warning / Violation with the
specific rule findings (credential leak, code execution, sensitive-file change, …). That triaged
view is what h5i audit review surfaces across many commits.
The notes ref isn't pulled by a plain git fetch — it would clutter remotes that don't
care, and most CI never asks for it. h5i share push and h5i share pull sync
the h5i refs alongside your code, so teammates who opt in see the full provenance and everyone else
sees ordinary git behavior. Provenance is additive, never load-bearing for the build.
How h5i capture commit populates the record
You can capture provenance manually:
$ h5i capture commit -m "add retry logic to HTTP client" \ --model claude-sonnet-4-6 \ --agent claude-code \ --intent "add exponential backoff to the HTTP client" \ --tests \ --audit ✔ Committed a3f8c12 add retry logic to HTTP client model: claude-sonnet-4-6 · agent: claude-code · 312 tokens
In practice you rarely type --intent at all. Installing the hooks with
h5i hook setup wires a UserPromptSubmit hook that captures the verbatim prompt
you send to Claude Code and attaches it to the next commit — and the auto-captured prompt
wins over an agent-supplied --intent, so what gets recorded is what the human
actually asked, not the agent's paraphrase. (--intent stays as the fallback for Codex,
CI, or manual commits where no prompt-capture hook is running; Codex instead mines its own session
transcript.) The --tests flag attaches the captured test metrics and --audit
runs the integrity rules before the commit lands.
The new question: per-line prompt history
Standard blame stops at the line's introducing commit. h5i recall log --ancestry
<file>:<line> walks backward from HEAD, re-blaming the file at each step and following
the line as it moves through edits, so it can show every prompt that ever shaped that line — not just
the last one:
$ h5i recall log --ancestry src/http_client.rs:89 ── Prompt ancestry for src/http_client.rs:89 [3 of 3] a3f9c2b9 Alice · 2026-04-12 14:02 UTC line: let mut delay = Duration::from_millis(100); prompt: "add exponential backoff with jitter to the HTTP client" [2 of 3] 72160394 Alice · 2026-03-08 09:14 UTC line: let delay = Duration::from_millis(100); prompt: "fix off-by-one in retry counter" [1 of 3] 9eff0012 Alice · 2026-02-24 11:30 UTC line: let delay = 100; prompt: (none recorded) (Human)
Entries print most-recent first ([3 of 3] down to [1 of 3]), each with the
line's exact content at that point in history. This answers "why is this line the way it
is?" across its whole life, not just at the most recent edit. When a regression bisects to a refactor,
the ancestry view tells you whether the line you're staring at was genuinely rewritten or merely moved
— and which prompt did the moving. The walk is bounded (it stops at the commit that introduced the
line, or after a depth cap) and gracefully interleaves untagged historical commits, which simply read
(none recorded).
How it compares to what you already have
Provenance for AI code isn't a brand-new idea — there are two conventions you've probably already reached for. Both help; both have a ceiling.
| Question | git blame | Commit trailers ( Co-authored-by, Assisted-by) | h5i AI blame |
|---|---|---|---|
| Which commit owns this line? | Yes | No (commit-level only) | Yes (same git algorithm) |
| Who committed it / when? | Yes | Yes | Yes |
| Which agent produced it? | No | Sometimes (free-text, unenforced) | Yes (AI:<agent> per line) |
| Which model / token cost? | No | No | Yes (in the record) |
| What prompt produced it? | No | No | Yes (--show-prompt) |
| Did its tests pass? | No | No | Yes (--tests) |
| Full prompt history of one line? | No | No | Yes (--ancestry) |
Survives git push with no setup? | Yes | Yes (it's in the message) | No — needs h5i share push |
Good, about trailers: a Co-authored-by: Claude <…> trailer is
zero-infrastructure, travels in the commit message, and is enough to answer "was a model involved at
all?" — which is sometimes all you need. Gap: it is commit-granular free text. It
can't tell you which of the 14 files in the commit the model touched, carries no prompt or test
outcome, and nothing validates it — an agent that forgets the trailer leaves no trace. AI blame trades
that zero-setup portability (the notes ref needs an explicit h5i share push) for
structured, line-resolved, queryable provenance.
Failure modes worth knowing before you rely on it
AI blame inherits git blame's mechanics, so it inherits git blame's blind spots — plus a couple of its own. Knowing them up front keeps you from over-trusting a clean-looking annotation.
- Squash and rebase rewrite OIDs. Provenance is keyed by commit OID. A squash merge collapses many commits — and many prompts — into one new commit with one new OID; the original notes no longer match it, so blame attributes every squashed line to the single record the new commit carries. If per-prompt resolution matters to you, keep the granular commits (a merge commit, or rebase that preserves boundaries) rather than squashing.
- Moved lines look like new lines. Plain blame credits the commit that last touched
a line, so a pure move or reformat reassigns it.
git blame -C -Mmitigates this for the standard view; the--ancestrywalk helps for h5i by distinguishing a genuine rewrite from a move, but a large mechanical reformat will still re-stamp lines with the reformatting commit's provenance. - Attribution is commit-granular, not keystroke-granular. The prompt attached to a line is the prompt recorded for its owning commit. One commit, one prompt field — batching work coarsens the resolution.
- Untagged commits read blank. Anything committed with plain
git commit, or before you adopted h5i, has no record; those lines showHuman/(none recorded)regardless of how they were actually written. Absence of a record is not evidence of a human author.
Practical usage patterns
Three concrete situations where AI blame earns its keep:
1. Triaging an incident
An error is firing in parse_response. h5i recall blame on the function shows it was authored
by claude-sonnet-4-6 with the prompt "handle the new v2 envelope format" two weeks ago.
The original v1 parser was untouched. Fastest path to root cause: check whether the error
payload is v1 (the parser doesn't handle it) or v2 (the parser is buggy). Without provenance,
you'd have read both code paths first.
2. Reviewing your own past decisions
Six months later you don't remember why retry_max is 5. h5i recall blame shows the prompt
was "…cap retries at 5 to avoid infinite loops". Decision recovered without re-reading the
PR thread, the design doc, or your Slack history. Particularly valuable for solo developers
whose "team handoff" is to themselves three months later.
3. Vetting an inherited codebase
You're given a service to take over. h5i recall log --limit 200 tells you the AI ratio (60% AI?
20%?), which agents wrote which subsystems, and what kinds of prompts were used. Combined with
h5i audit review, you get a triaged list of "files most likely to harbor unreviewed AI
assumptions." A 30-minute orientation that previously took days.
h5i audit vibe is the explicit command for the inherited-repo case.
It scans recent commits and prints the AI footprint (what fraction carries AI metadata), which models
appear, the directories with the highest AI concentration, and the riskiest files (high AI ratio, no
tests, blind edits). For credential leaks and prompt-injection hits, reach for the integrity rules via
h5i audit scan / h5i audit review — that's a different lens on the same
provenance.
What this is not trying to be
AI blame is not a copyright assertion. The git author is still legally and organizationally
the one accountable for the commit, they ran the prompt, reviewed the diff, and merged. The
AI fields are provenance, not attribution. They tell you what tools shaped the line, the
same way git log tells you what compiler version the CI ran. Useful for debugging,
auditing, and triage; not for ownership.
It's also not a substitute for code review. The fact that claude-sonnet-4-6 wrote a line
doesn't make it correct or incorrect. It just makes the next reviewer faster.
Adopt incrementally
h5i blame works the moment you start using h5i capture commit. Pre-existing commits keep their
standard git blame; new commits gain the AI fields. There's no migration, no backfill, no
one-time event. The provenance accrues on the commits you make from here forward, and the
ancestry view interleaves it gracefully with the historical commits you've never tagged.
$ curl -fsSL https://raw.githubusercontent.com/h5i-dev/h5i/main/install.sh | sh $ cd your-project && h5i init $ h5i hook setup # auto-captures prompts via UserPromptSubmit # From your next commit forward: $ h5i capture commit -m "..." --model claude-sonnet-4-6 --agent claude-code --tests # prompt auto-attached by the hook $ h5i recall blame src/foo.rs --show-prompt
FAQ
What is the difference between git blame and AI blame?
git blame maps each line to the commit that last changed it and shows the committer's
name, SHA, and date. For agent-written code the committer is the human who ran git commit,
not the model that wrote the line. AI blame keeps the same line-to-commit mapping and adds, per commit
boundary, the coding agent, the human prompt, and the captured test status; the model name, token
count, and integrity grade live in the commit's provenance record.
Does git blame show which AI model wrote a line?
No. A git commit object only records author and committer identity — there is no field for the model,
the agent, or the prompt. That has to be captured at commit time. h5i stores it in a git note on
refs/h5i/notes keyed by commit OID.
Where does h5i store per-line AI provenance?
In a git note attached to each commit on refs/h5i/notes, serialized as JSON (an
H5iCommitRecord with ai_metadata, test_metrics, and a
timestamp). It is not carried by a plain git push or git fetch; use
h5i share push / h5i share pull to sync it alongside your code.
What happens to AI blame when commits are squashed or rebased?
Provenance is keyed by commit OID, so rewriting history changes the OID and the original notes stop matching. A squash collapses several prompts into one new commit, and blame then attributes every squashed line to that commit's single record. AI blame is exactly as durable as the commit graph it annotates — preserve commit boundaries if you want per-prompt resolution.
How do I see the prompt that produced a line?
Run h5i recall blame <file> --show-prompt to annotate each commit boundary with its
prompt, agent, and test status, or h5i recall log --ancestry <file>:<line> to
walk the full prompt history of one line across every commit that touched it.
The bottom line
git blame was designed around an assumption that quietly stopped holding: that the person
who committed a line is the person who decided what it should say. For agent-written code those are two
different actors, and the gap between them — the model, the prompt, the test outcome — is precisely the
context a debugger needs at 2am. None of it is recoverable after the fact from a git commit alone; it
only exists if something captured it at commit time.
That's the whole proposition, and also its honest limit. AI blame is not attribution, not a copyright claim, and not a substitute for review — it makes the next reader faster, nothing more. It resolves no finer than your commit boundaries, and it survives only as well as you preserve those boundaries. But within those limits it turns "who touched this line" into "which model, from what prompt, with what test result" — and on a codebase where most lines were written by an agent, that is the question actually worth asking.
Per-line provenance for the AI era
Try h5i on your next AI-assisted branch: create a sandboxed workspace, capture the run, and post a review-ready PR brief.
Star on GitHub Back to docs