AI commit messages: what an agent's commit should record

Stefan-Iulian Tesoi · · 6 min read

A 1906 Western Union telegram from Theodore Roosevelt to the governor of California, its sender, place and time recorded around a short message, as a commit's trailers are

The reason for the change, the work item it was for, and the fact that an agent wrote it, with the item and the agent recorded in trailers a script can parse. Restating the diff adds nothing. The reason and the link are what nobody can rebuild from the code later, so they are what AI commit messages exist to carry.

Agents are good at the part that matters least. Ask one for a commit message and it summarises the diff accurately, in the imperative mood, under 72 characters. The summary was never the scarce part.

What should AI commit messages contain?

Four parts, each answering a question someone will ask later:

PartAnswersExample
SubjectWhat changed, in one linefix(export): sum invoice totals before rounding
BodyWhy it changed, and what was rejectedTotals were off by cents against the PDF
Work item trailerWhich request it servedRefs: BILL-412
Authorship trailerWhat wrote itCo-authored-by: <agent>

The subject can come from the diff. The body and both trailers cannot. The body needs the intent, which lives in the work item and in the conversation before the change. The trailers need facts the coding agent was told: the item it was working on, and its own identity. Put together:

fix(export): sum invoice totals before rounding

Totals were summed from line amounts already rounded to two decimals,
so an invoice with many lines could differ from its PDF by a few cents.
The total is now summed at full precision and rounded once. Rounding
in the database query was rejected: the API reads the same query and
must keep full precision.

Refs: BILL-412
Co-authored-by: Claude <[email protected]>

Why restating the diff is the default failure

Because the diff is the only input the agent is sure to have. A model asked for a message sees the staged changes, and a summary of them is the message it can write with confidence. "Update export service and add tests" is accurate, and it tells a reader nothing git show would not.

The reason is usually within the agent's reach, in one of three places:

It reaches the message only when the agent is told to put it there. One line in the agent's instructions, such as "the body says why and cites the item; never describe what the diff shows", changes the output more than any template does. The rest of the conventions an agent needs to fit a team, from branches to review, are in running coding agents in a team workflow.

How should a commit point to its work item?

With an identifier in a trailer, not a phrase in the subject. A git trailer is a Key: value line at the end of the message, after a blank line, and Git parses them natively: git interpret-trailers reads and writes them, and git log --format='%(trailers:key=Refs)' lists every item a range of commits touched.

Linking commits to issues this way survives what other links do not. Branches are deleted after merge, and a pull request description lives on the hosting service rather than in the repository. A trailer travels with the commit through rebases, cherry-picks and a move to another host.

GitHub's closing keywords deserve a warning. "Fixes #123" in a commit closes the issue when the commit reaches the default branch. For agent work that is usually wrong, because merged is not the same as verified. A non-closing key such as Refs: links without deciding anything.

Laimonade uses both kinds of signal. A coding agent working over MCP links its session to an item before its first commit, so the commits that follow attach to that item, and pushes are also matched to open items by what their messages say. A message that names the item and gives the reason gives the matching something to work with. "wip" gives it nothing.

Should the agent be named as an author?

Yes, in a trailer, with the person who directed the work as the author. Git records two identities on a commit, author and committer, and neither fits an agent: the person answers for the change, and the agent wrote most of it. The Co-authored-by trailer covers the gap. Claude Code ends its commits with a Co-Authored-By trailer naming the Claude model by default, and GitHub documents the format: one line per co-author, after a blank line.

The co-authored-by trailer earns its place in an audit. When someone asks which changes an agent wrote, git log -i --grep='co-authored-by: claude' answers in seconds, and the answer can be checked against what the team believed it shipped, the exercise in auditing what your agents actually shipped. Without the trailer, the only way to answer is to read every diff. The case for keeping that record at all is in audit trails for agent changes.

Do Conventional Commits help or get in the way?

They help, because the prefix is written for machines and agents follow formats reliably. Conventional Commits puts a type and optional scope before the subject, feat(export): or fix(auth):, and its footers follow the git trailer convention, so a work item reference fits without bending the spec. Release tools read the types to write changelogs and choose a version bump.

Laimonade's own matcher depends on the same structure. Before matching a pushed commit to a backlog item, it skips any whose subject reads build(deps), because a dependency bump matched to an open item by topic would look like delivery. It skips reverts for a similar reason: matched to the item it undid, a revert would move a just-reopened item back to In Review.

The convention gets in the way when the type becomes the whole message. chore: update files is valid Conventional Commits and as empty as "wip". The spec makes the body optional. For agent commits it should not be.

Frequently asked questions

Should an agent squash its commits before review?

Only when the individual commits would not stand alone. A run of "fix lint" and "try again" commits from an agent's retries is noise worth squashing. A series where each commit is one reasoned step is a record worth keeping. When the team squash-merges anyway, the squash message needs the reason and both trailers, because only that message reaches the main branch.

Does GitHub show Co-authored-by trailers?

Yes. GitHub reads Co-authored-by lines at the end of a commit message and shows each co-author on the commit. The trailers need a blank line before them and one co-author per line. A commit counts toward a co-author's contributions only when the email address in the trailer is associated with their GitHub account.

Who is the author when a person edits an agent's commit?

Whoever Git already recorded, unless someone changes it. Amending or rebasing keeps the original author and records the editor as committer, and git commit --amend --reset-author makes the editor the author. When an agent committed under its own bot identity and a person rewrote the substance, reset the author and keep the agent as a Co-authored-by trailer, so the history records both.