- Book: Prompt Engineering Pocket Guide
- Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
Six weeks from now a login bug lands on your desk. You run git blame on the line that broke, and it shows your name on a commit called "Fix the flaky login test". You merged it. You didn't write it. Codex did, on a model you have since stopped using, at an effort level you can't remember, from a task description nobody kept.
A commit trailer is the obvious fix. Claude Code adds one to its commits, and its attribution.commit setting lets you change or hide it (settings reference). A trailer lives in the commit message, though, so it has to be there before the commit exists. Add one afterwards and you rewrite the commit. It gets a new SHA, and a merged branch now needs a force-push.
git notes covers the other case. The docs describe notes as data attached to objects "without touching the objects themselves." You attach the agent, model, effort and task to a commit after it lands, and every SHA stays where it was.
Step 1: write one note and read it back
Pick a commit an agent made. Write the note as one key: value per line (Step 2 explains why that format matters):
printf 'agent: %s\nmodel: %s\neffort: %s\ntask: %s\n' \
codex "$MODEL" high "Fix the flaky login test" |
git notes --ref=agents add -F - HEAD
Set MODEL to the model the agent ran on. -F - reads the note from standard input. --ref=agents keeps these notes in their own ref, refs/notes/agents: the docs say a name that doesn't start with refs/notes/ or notes/ gets refs/notes/ in front. Without --ref, notes go to refs/notes/commits, which other tools may already use. A dedicated ref is one you can push, fetch and merge on its own. add refuses to touch a commit that already has a note unless you pass -f, which overwrites it.
Reading it back:
git notes --ref=agents show HEAD
git log --notes=agents -3
git log shows notes under the commit message when you don't pass --pretty, --format or --oneline (git-log docs). To see the agent notes in every plain git log without typing the flag, add the ref to notes.displayRef:
git config notes.displayRef refs/notes/agents

A note hangs off a commit. The commit, and its SHA, stay exactly as they were.
Now go back to that login bug. Find the commit that last touched line 42 and ask for its note:
sha=$(git blame --porcelain -L 42,42 src/login.js |
head -n1 | cut -d' ' -f1)
git notes --ref=agents show "$sha"
The first line of --porcelain output starts with the full SHA, so cut takes the first field. You can also search the notes. The --grep docs say "When --notes is in effect, the message from the notes is matched as if it were part of the log message":
git log --notes=agents --grep='agent: codex'
Step 2: push, fetch and merge the notes ref
Notes don't travel on their own. The default fetch refspec is +refs/heads/*:refs/remotes/origin/*, which covers branches, not notes (git-fetch docs). Push the ref by name:
git push origin refs/notes/agents
For a ref that is neither a branch nor a tag, git push allows a fast-forward without --force. If a teammate pushed notes since your last fetch, your push is rejected, and that's what you want.
Fetching is the half that bites. The git-fetch docs say updates outside refs/{tags,heads}/* "will be accepted without + in the refspec." Fetch the remote's notes straight into refs/notes/agents and the remote's version replaces yours, unpushed notes included. Fetch into a side ref and merge instead:
git fetch origin refs/notes/agents:refs/notes/origin/agents
git notes --ref=agents merge -s cat_sort_uniq \
refs/notes/origin/agents
git push origin refs/notes/agents
When both sides noted the same commit, cat_sort_uniq concatenates the two notes, sorts the lines and drops duplicates. That is why the format is one key: value per line: two identical notes collapse into one. Two different ones keep every line from both, sorted together, so you can see that two agents claimed the commit but not which model went with which. On a fresh clone with no local agent notes yet, there is nothing to lose, so the first fetch can go straight to refs/notes/agents.
One more thing that helps with parallel agents. The git-worktree docs say all refs under refs/ are shared between worktrees (except refs/bisect, refs/worktree and refs/rewritten), and so is the repository config by default. A note written from any agent's worktree lands in the same refs/notes/agents.
Step 3: keep notes through rebase and amend
A rebase makes new commits with new SHAs. Notes are attached to the old ones, so by default they stay behind. Git can copy them, but only if you ask. The docs on notes.rewriteRef are blunt: it "does not have a default value; you must configure this variable to enable note rewriting."
git config notes.rewriteRef refs/notes/agents
With that set, git commit --amend and git rebase copy the agent notes to the rewritten commits. Those are the only two commands the docs list under notes.rewrite.<command>, and both default to true. git cherry-pick isn't one of them, so a cherry-picked commit arrives without its note.
When a rebase squashes two noted commits into one, notes.rewriteMode decides what happens, and its default is concatenate: both notes end up on the result. Since the config is shared across worktrees, setting it once in your main checkout covers an agent that rebases its branch in its own worktree.
Step 4: note a whole branch right after it lands
Doing this one commit at a time won't last a week. The values you need are the ones you picked when you started the task, so the moment to do it is just after the branch lands.
I build Hermes IDE, so weigh this section with that in mind. In Hermes, ⌘N (Ctrl+Shift+N on Windows and Linux) starts a task on its own hermes/<name> branch and worktree, and you pick the agent, model, effort and account for that task. Land then commits, opens a pull request or merges locally in one step, into the branch the task started from, and can be undone. Before you Land, the Review Desk (⌘G) shows the task's changes by turn or by file. Everything below is plain git, so it works the same with or without Hermes.

The ⌘N New task sheet: agent, project, worktree, approval mode, model and effort chips, and a preview of the claude command Hermes will run.
Save this as agent-note somewhere on your PATH and make it executable:
#!/bin/sh
# Usage: agent-note <range> <agent> <model> <effort> <task>
set -eu
if [ $# -ne 5 ]; then
echo "usage: agent-note <range> <agent> <model>" \
"<effort> <task>" >&2
exit 2
fi
range=$1 agent=$2 model=$3 effort=$4 task=$5
for sha in $(git rev-list "$range"); do
if git notes --ref=agents show "$sha" >/dev/null 2>&1
then
echo "skip $sha: already has a note"
continue
fi
printf 'agent: %s\nmodel: %s\neffort: %s\ntask: %s\n' \
"$agent" "$model" "$effort" "$task" |
git notes --ref=agents add -F - "$sha"
done
It skips commits that already have a note, so running it twice is safe and it never overwrites someone else's.
If Land merged locally into main, the commits it brought in are the ones main gained in its last move. main@{1} is "the immediate prior value" of main (gitrevisions), so main@{1}..main is exactly that set, provided the Land was the last thing that moved main. Look at the range before you write to it:
git log --oneline 'main@{1}..main'
agent-note 'main@{1}..main' claude-code "$MODEL" high \
"Fix the flaky login test"
git push origin refs/notes/agents
Use the agent, model and effort from the task's chips, and the task as you typed it. If Land opened a pull request instead, note the branch before it merges, with main..hermes/<name> as the range. Then check how your repo merges pull requests. GitHub's merge methods docs say a squash merge combines the commits into a single commit, and a rebase merge "creates new commit SHAs." Either way the notes stay on the branch's original commits. Pull, then note what main gained. After a squash merge that is one commit, and the range is <sha>^!, which gitrevisions defines as that commit without its parents. After a rebase merge it is one new commit per original, so use main@{1}..main right after the pull.

The Review Desk (⌘G) by turn for a Claude Code task: two turns, turn 1's diff of src/db.ts, and a commit message drafted from the turns.
What notes can't do
A note is a label, not proof. The git notes command has no option to sign one. Every notes change is an ordinary commit on the notes ref, authored "according to the usual rules" (git-notes docs), so anyone who can push refs/notes/agents can change or delete any note in it. git log -p refs/notes/agents shows the history of those changes, until someone force-pushes over it. If you need a record nobody can quietly edit later, it has to be inside a signed commit, which means the agent writes the trailer before the commit is made. Even then, the signature only proves the trailer hasn't changed since it was signed. The trailer itself can still be wrong.
A note also says only what you typed. Run the script with the wrong model and the note is wrong, with nothing to flag it.
GitHub's web UI hasn't shown notes since 2014. The original "Git Notes Display" post carries an update from August 14, 2014: "Displaying Git notes on GitHub is no longer supported" (GitHub blog). Your terminal reads them; a pull request page doesn't.
And they need a habit. Notes aren't fetched by default, a squash or rebase merge leaves them on commits that never reach main, and a cherry-pick drops them. Put the fetch-merge-push lines from Step 2 in the same script you run after every Land, and they'll stay current.
If this was useful
Once each commit carries its task line, git log --notes=agents --grep turns your history into a record of which task descriptions and settings produced code that lasted, and which ones you had to fix later. Writing those task descriptions well is most of the job, and the Prompt Engineering Pocket Guide is my short book on writing them so the model does what you meant.
If you run several agents on one repo, Hermes IDE is free, source-available (BSL 1.1), runs on macOS, Windows and Linux, and is not affiliated with Nous Research, Anthropic, OpenAI or any agent vendor. If it helps you, a star on GitHub helps other developers find it.

Top comments (0)