This is not a post about blaming an AI instead of a human. It’s about a lightweight way to provide limited line-by-line AI attribution in source code history without obscuring which human is actually accountable. If you use coding agents and a simple Git flow, you might find it useful.
This post is for people who want to understand later how their code came to be, and who may want to evolve the way they create and review it.
Git Blame
git blame shows you detailed most-recent-commit info for each line in a file. Using it is as easy as git blame someFile.txt, and you’ll see the whole file with each line prefixed by the line’s latest commit information.
The command name “blame” is funny and memorable, and implies a “who wrote this garbage” kind of use case. It gets WAY funnier when the answer is “Oh. I did.” β which can happen humblingly often.1
The command’s obvious practical use for me has always been for “who should I talk to about this” scenarios.
AI Provenance
If you’re using AI to help write code, it can be important to make the AI-assisted code easier to identify later. There are a bunch of reasons you might want this, like:
- simple curiosity
- accountability and trust
- comparing different AI tools over time
- policy or compliance needs
- providing additional context for code reviews and quality control
- being honest with yourself in the future about where AI helped
My Simple Approach
Here’s what I’m doing. It’s not perfect, but it’s automatic and adds zero friction, so I can’t forget to do it. And for my purposes, “good enough and actually used” beats “technically perfect but forgettable.”
This approach keeps the human identity intact. Even if a developer lets an agent just go wild and commit without review, the developer is still (in my book) accountable for the result. So I use a simple wrapper that appends [AI:$TOOL] to the developer’s name via GIT_AUTHOR_NAME when launching the agent.
Here’s the full source at the time of this post, and here’s the repo, which might have a more recent version.
As a result, git blame output for a simple short script might look like:
mlamb on lrrr in .../some-project on ξ main
β― git blame reverse.sh
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 1) #!/bin/bash
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 2)
9b021e1b (Marty Lamb [AI:Codex] 2026-05-05 06:54:43 -0400 3) # Reads up to CHAR_LIMIT characters from stdin or from the first argument,
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 4) # and prints them to stdout in reverse order.
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 5)
9b021e1b (Marty Lamb [AI:Codex] 2026-05-05 06:54:43 -0400 6) CHAR_LIMIT="${REVERSE_CHAR_LIMIT:-20}"
9b021e1b (Marty Lamb [AI:Codex] 2026-05-05 06:54:43 -0400 7)
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 8) if [ "$#" -gt 0 ]; then
9b021e1b (Marty Lamb [AI:Codex] 2026-05-05 06:54:43 -0400 9) input=${1:0:CHAR_LIMIT}
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 10) else
9b021e1b (Marty Lamb [AI:Codex] 2026-05-05 06:54:43 -0400 11) IFS= read -r -n "$CHAR_LIMIT" input || true
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 12) fi
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 13)
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 14) reversed=""
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 15) for ((i=${#input}-1; i>=0; i--)); do
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 16) reversed+="${input:i:1}"
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 17) done
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 18)
^9e70ef8 (Marty Lamb 2026-05-05 06:51:55 -0400 19) printf '%s\n' "$reversed"
Note how it makes the AI-assisted lines jump out without hiding who is responsible for them. And I only had to launch codex the way I normally do.
Imperfect but Pretty Good
This works because I had codex make the commits. If I had committed its changes myself, outside of the agent, the AI indicators would not be present. For small teams this may be enough. For personal projects it’s definitely enough. I can get into the habit of letting agents commit after I review their work.
There are two main shortcomings here:
First, git blame only shows the most recent commit info for each line. If an agent writes and commits a line of text and someone else makes a change to that line in a later commit, the original AI attribution will no longer be visible in git blame output.
Second, this won’t work as-is with squash commits (and probably other Git workflows) - if you or the agent squash and then:
- the agent commits, all lines in all of the squashed commits will get the AI marker (regardless of their true source.)
- you commit, none of the lines from any of the squashed commits will get the AI marker (regardless of their true source)
For personal projects, this tradeoff is fine. That’s less certain for small teams.
Potential Tweaks & Improvements
Some folks might want to add provenance info in commit trailers, too. That can be done with a prepare-commit-msg hook that looks for the $AI_TOOL environment variable that is set by this tool.
A more sophisticated tweak for squash commits might record the set of commits being squashed and add the union of their indicators to commit trailers or even to the author’s name, so git blame would show something like Some Developer [AI:Codex,Claude].
Of course versions for other shells and environments besides Bash might be useful, too.
If you’d like to take a swing at any of these or have other ideas for improvements, please shoot me a message or submit a PR to with_ai.
Epilogue
This is clearly not a final answer for AI provenance, and Iβm not trying to turn it into one.
But I do think thereβs real value in a simple convention that works with normal Git history, keeps the human name on the commit, and makes AI assistance visible later in git blame.
That seems like a pretty good place to start.
That was an artisanally hand-authored em dash. ↩︎
