AI agents keep causing merge conflicts — why, and two ways out
Run two coding agents against one repository for an afternoon and you will hit it: the second agent's branch no longer merges, or worse, it quietly reverts what the first one shipped. This is not an agent quality problem. It is a coordination problem, and it has exactly two working fixes.
Updated 2026-07-26
Why agents collide more than humans do
Human teams rarely merge-conflict by accident, and it is worth being precise about why: humans coordinate out of band. You know what your teammate is on because of standup, the ticket board, or the open pull request. Agents have none of that. Each one starts from a cold context, reads the repository as it exists at that moment, and edits at machine speed. Three properties make them collide far more often than people:
- No awareness of in-flight work. An agent sees committed state, not intent. If another agent is halfway through refactoring a module, the second agent sees the old shape and cheerfully builds on it.
- They gravitate to the same hot files. Whatever the task, agents keep touching the same central files: the schema, the shared contracts, the route registry,
package.json, the lockfile. Building Befall with several agents in one repo, our refusal log was dominated by a handful of paths —packages/db/src/schema.tsandpackages/shared/src/api-contracts.tsstyle hubs that every feature passes through. Two tasks that look unrelated on a board almost always intersect on one of these. - They resolve conflicts confidently and wrongly. A human hitting a conflict stops and asks. An agent asked to "fix the merge" will pick a side. The most expensive failure we have seen is not the conflict itself — it is an agent resolving a conflict by discarding the other agent's change, which turns a visible problem into a silent regression.
The cost is asymmetric, too. A conflict caught at merge time costs one untangling session. A conflict resolved badly costs the untangling session plus a re-run of every agent-hour that was built on the wrong resolution.
The anatomy of an agent merge conflict
The typical failure needs no exotic setup. Agent A is adding an API route and edits the shared Zod contract to add a field. Agent B, started twenty minutes later on a different feature, edits the same contract file to add a different field. Both work on branches. Both pass their own tests. The first merge lands; the second one conflicts on the contract, and — if an agent is asked to resolve it — the losing field sometimes just disappears. Nothing in either agent's context ever contained the sentence "someone else is editing this file right now."
That sentence is the entire missing ingredient, and both fixes below are just different ways of supplying it.
Way out #1: partition the work structurally
The first fix is to make overlap impossible by construction: give every agent its own territory. In practice that means one branch (or git worktree) per agent and a task split along module boundaries — one agent owns apps/web, another owns packages/cli, and their branches merge in a fixed order.
Partitioning works, and you should do as much of it as your task graph allows. Befall itself leans on it: when an agent joins a room, the daemon computes an advisory branch policy and recommends a branch named befall/{handle}/{room} for that agent — it warns when you are off-policy but never switches branches for you. The limits show up quickly though:
- Real tasks do not respect module boundaries. The moment two partitions both need the schema or the shared contracts, you are back to the hot-file problem — partitioning only relocated it.
- Static partitions rot. Task three arrives, does not fit either territory, and someone (you) becomes the human router deciding who may touch what. That is exactly the coordination overhead you hired agents to remove.
- Serialized merges throw away parallelism at the end: the second branch always rebases onto whatever the first one did to the shared files.
Way out #2: coordinate claims before edits happen
The second fix keeps agents in shared territory but moves the conflict to claim time instead of merge time. Before touching files, an agent announces intent and claims the paths it is about to edit. If the claim overlaps a claim someone else already holds, it is refused on the spot — an explicit "no, agent B holds packages/shared/src/**" — and the agent can wait, pick up a different task, or message the holder. The conflict still happens, but it happens as a cheap, legible refusal before any code is written, not as a git artifact after hours of work.
This is what Befall implements as advisory path locks with first-writer-wins: claims are glob-aware (apps/web/app/api/** is a valid territory), overlapping claims get an HTTP 409 with the conflicting holder named, locks expire on a TTL, and a crashed agent's claims auto-release when its heartbeat stops. Because agents follow tool descriptions well, the whole protocol — join, announce intent, lock, message, release or hand off — is taught to the agent by the MCP tools themselves; there is no prompt engineering to maintain.
Two honest caveats. Advisory locking assumes agents actually call the tools — an agent that edits without claiming is only caught by the backstop (heartbeats carry dirty-file lists, and overlapping dirty paths across agents raise a conflict alert). And locking does not replace review: it prevents the collision, not a bad change.
Choosing between them — or stacking them
Partition first, coordinate the remainder. Split what splits cleanly: separate branches, separate modules, merge order agreed up front. Then put a coordination layer under the parts that cannot be partitioned — the hot files every task passes through. In our own dogfooding the split is roughly: branch policy handles the easy 80%, and the refusal log catches the 20% of genuinely overlapping claims that would have become merge conflicts. Those refusals are conflicts that never happened — each one is an untangling session you did not run.
If you run a single agent, none of this matters yet. The day you run two — especially two different tools on the same repo — pick your fix before the first bad merge picks it for you.