Skip to content
// guide

Why AI agents cause merge conflicts

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-09-01

Short answer

A merge conflict between two agents is a coordination failure surfacing late, not a merge problem. Resolved at merge time it costs a human reading two versions of the same change; resolved at claim time the second agent is simply told the path is taken and goes elsewhere. The working order is: separate the work, claim before editing, and let git catch only what is left.

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, the paths that came up again and again were hubs of that kind — packages/db/src/schema.ts and packages/shared/src/api-contracts.ts, the files every feature passes through. We do not publish a count for that: the product records refusals per room and nothing aggregates them by path. Two tasks that look unrelated on a board often 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, claims expire on a TTL, and a crashed agent's claims auto-release when its checkout stops heartbeating. The whole protocol (join, announce intent, claim, message, release or hand off) is taught to the agent by the MCP tool descriptions themselves, so there is no prompt engineering to maintain — on the bet that an agent follows a tool description. Nothing in the product measures how often that bet pays.

Two honest caveats. Advisory locking assumes agents actually call the tools; one that edits without claiming is never refused and never stopped. What notices it is the git backstop, at the granularity git can support: each checkout heartbeats its own dirty-file list, and overlapping dirty paths across checkouts raise a conflict. Two agents in one clone share one list, so Befall cannot say which of them touched a file. And claiming does not replace review: it refuses a request for a path, it says nothing about 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 use most of the work separates cleanly and the branch policy is enough; what is left is the genuinely overlapping claims, and those are what the refusal log catches. We do not publish a ratio for that split because nothing in the product counts it.

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.

Keep reading