When your AI team keeps breaking its own git history
We run several AI personas in parallel against one repository — each in its own worktree, on its own branch, merging into a shared wip integration branch that flows wip → work → main. The branch model was fine. The mechanics weren't: merge conflicts kept appearing, and almost none of them were caused by what anyone wrote.
They were caused by how the agents operated git.
What actually conflicted
Submodule pins. Two personas each commit inside a shared knowledge submodule and bump the parent's pointer. Git sees two divergent commits and calls it a conflict — even though both pins are perfectly valid and a merge inside the submodule resolves it cleanly. Agents were forbidden from resolving conflicts, so every pin collision became a human ticket. The fix: a sync script that arbitrates automatically — it merges both pinned commits inside the submodule on the persona branch, pushes upstream, and pins the result. Only a real content conflict inside the submodule still escalates.
Append-only files. Daily logs and the impediments log live in shared files that every persona appends to. Two appends on adjacent lines = a guaranteed conflict, every single day. The fix is one line of .gitattributes: merge=union. Union merge keeps both sides' added lines — correct by construction for append-only data. The convention now states it explicitly: add rows, never edit another persona's.
Pins to nowhere. The nastiest failure: a persona commits its parent-repo pin before pushing the submodule branch. The pin then points at a commit that exists on one machine only — nobody else can even fetch it, let alone resolve it. The integration script now refuses to push until every pin is verifiably on the submodule's upstream.
Drift and leftovers. Long-lived persona branches accumulated weeks of divergence; abandoned conflicted merges sat in worktrees for the next session to trip over; a stale gitlink from a deleted submodule resurfaced in every upstream merge. The sync script now does the hygiene a distracted agent skipped: refuses on a dirty tree or in-progress merge, merges origin/main and origin/wip at session start, realigns submodules, and warns about detached checkouts.
Racing automation. Two GitHub workflows were both merging wip → work — one on push, one on a cron — pushing the same branch within minutes of each other. A push-wip.bat Task Scheduler hack was still running despite being documented as temporary. Deleted both; one workflow, one path.
The principle
The rule "agents never resolve integration conflicts" is right — a conflict that needs judgment belongs to a human. But we were applying it to conflicts that needed mechanics. The redesign splits them by class:
- Mechanical (pins, appends, non-ff retries) → scripted, correct every time.
- Semantic (two competing implementations in one file) → abort, log an impediment, escalate.
Fewer conflicts reach the merge at all — and the ones that do are the ones a human actually needs to see.