A Git Merge Rule for Append-Only AI Changelogs
I noticed the problem while comparing git status and recent commits across two checkouts.
One checkout had a commit the other checkout had never seen. Another had a run of local commits that had not reached the private remote. The auto-push job refused to move because it was fast-forward only.
That refusal was correct. A scheduled sync should not pull, merge, and push through a real source conflict just because the clock says it is time.
The surprise was the file that caused the fork: an append-only changelog.
Two AI coding sessions had described the same deployment in separate sessions. They used different labels for accurate descriptions of the same event. Both entries were legitimate. Neither line should have been deleted. Git treated the end of the log like normal prose and asked for a human merge.
For source files, that interruption is useful. For this changelog, I wanted the reconciling merge to preserve both additions and move on.
The file had a contract
I already expect AI coding sessions to disagree sometimes. The bug was that I had an unwritten file contract.
The changelog is a ledger. Each new entry belongs on its own line at the end. Old entries remain as written unless there is an explicit correction.
Once I named that contract, the fix was small:
CHANGELOG.md merge=union
That .gitattributes line records the merge rule for this path. During the reconciling merge, Git uses a textual union merge and preserves lines from both sides instead of making me choose one label over the other.
The sync job stays strict. It still blocks when history diverges. The difference is what happens when I reconcile the append-only file: the merge preserves both additions, and the next push can go back to being a fast-forward.
I did not apply that rule to the whole repo. Source files still use normal conflict detection. Design docs still need human review if two agents change the same paragraph, so those files stay on the default merge path.
Why not just let the bot merge?
The tempting fix is to make the sync job smarter: fetch, merge, resolve, push.
A sync job should move known-good commits from one place to another. If it starts deciding which side of a conflict wins, a bad merge can look like a clean automation run.
The better version is narrower. Keep the sync job strict. Change the merge behavior only for the file whose semantics justify it.
The useful pattern
Some files are ledgers. Deployment notes, event journals, audit trails, and run logs often have the same shape: entries are added, not revised. If two processes add different entries, losing either one is worse than keeping both.
The caveat matters. merge=union works at the line level. It preserves both sides, but it does not understand chronology or intent. If exact order matters, or if duplicate noisy lines are worse than a human stop, the file is not a good candidate.
A config file, migration, routing table, or business rule should still interrupt you when two sessions touch the same place. In this case, the Git setting was safe because the changelog already had an append-only convention, and the rest of the repo kept the normal stop-and-read behavior.