How to stop AI coding agents from overwriting each other's files
Run Claude Code and Codex in the same repo at the same time and one of them will eventually stomp the other's edits — an uncommitted change vanishes, or two versions of the same function land in one merge. This is the practical fix list, ranked from free-and-simple to full coordination, written from the seat of someone who runs three agents on one codebase every day.
Updated 2026-08-08
What actually goes wrong when two agents share a repo?
One agent's edit disappears, or two agents write conflicting versions of the same file and you find out at merge time. Those are the two failure modes, and they're different. The first is a genuine race: agent A reads route.ts, agent B reads the same file, both edit their in-memory copy, both write — B's save lands last and A's change is silently gone, no conflict marker, no warning. The second is quieter and worse: each agent commits its own coherent version on its own branch, everything looks fine, and the collision surfaces as a merge conflict full of AI-authored code you now have to referee.
The first time I ran three agents on this very codebase — Claude Code, Codex CLI, and Cursor, one repo — I lost about twenty minutes of work to exactly the first case before I'd built anything to stop it. Two of them decided the same API route was theirs. Nobody was wrong; nobody knew the other existed.
Why do AI agents overwrite each other in the first place?
Because each agent assumes it's the only writer. A coding agent's edit loop is read-file → reason → write-file. It has no notion of a peer holding the same file open, and the filesystem won't stop it — last write wins, always. Git doesn't save you either: it only sees a conflict once both changes are committed on divergent branches. In the window between two agents reading and two agents writing, there is no referee at all. The more capable the agents, the faster they collide, because they move through files quicker than you can watch.
Fix 1 — split the work by directory and use git worktrees
If the tasks are genuinely separate, give each agent its own directory and its own branch. This is free and it works. A git worktree is a second checkout of the same repository backed by one object store, so agent A edits in ../repo-a and agent B in ../repo-b and neither ever sees a half-written file from the other. Combine that with a hard task split — one agent owns apps/web, another owns packages/db, and they do not cross — and the overwrite problem evaporates.
git worktree add ../repo-web feature/web-agent git worktree add ../repo-db feature/db-agent # point one agent at each directory; keep their paths disjoint
The honest caveat: worktrees isolate the filesystem, not the intent. They stop the mid-edit race, but if both agents end up touching lib/shared-util.ts anyway, you've just deferred the collision to merge time. Worktrees are the right answer when — and only when — you can carve the work into provably non-overlapping directories. When you can, stop here; you don't need anything else. The full worktrees comparison walks through exactly where that line is.
Fix 2 — run one agent at a time
The zero-infrastructure answer: never have two agents editing at once. Safe, and slow. Let one agent finish a task, review and commit, then hand the next task to the next agent. There is no race because there is only ever one writer. For a lot of solo work this is genuinely the correct call — coordinating two agents that finish in five minutes each is more overhead than just running them back to back.
The cost is the whole reason you wanted parallel agents. You bought three agents to get three things done at once; serializing them throws that away. It also doesn't scale to more than one human — the moment a teammate points their own Claude Code at the same repo, you are back to two uncoordinated writers and "just take turns" stops being enforceable.
Fix 3 — claim an advisory path lock before editing
Keep every agent in one shared working tree and make each one claim the paths it's about to touch. A conflicting claim is refused at claim time, before a single byte is written. This is what Befall does. An agent announces intent — "I'm taking apps/web/app/api/**" — and the room checks that glob against everyone else's active claims. No overlap: the lock is granted with a TTL. Overlap: refused with a 409 and the name of the agent already holding it, so the second agent picks non-overlapping work instead of racing into a file that's already being edited.
Locks are advisory and first-writer-wins: they auto-release on a TTL (default 30 minutes) or the moment an agent goes offline, and a lead can force-release a stuck one. Crucially, they don't require the agents to be well-behaved — even when an agent skips the protocol and just starts editing, the daemon's git heartbeats stream its dirty-path list to the room, so overlap gets flagged anyway, all the way out to a PR check that says which agent holds locks touching the pull request.
What does it look like when it works?
Here is a real refusal from the day-one log — the first day I ran three agents on this codebase, before any of this was polished:
$ agent A → lock apps/web/app/api/** ✓ held $ agent B → lock apps/web/app/api/route.ts ✗ refused → conflicts with A · ttl 8m
Agent B never wrote a byte into that path. It saw the refusal, went and did something else, and the twenty minutes I'd lost the first time around simply didn't happen again. That's the entire pitch: the collision is caught at claim time, not merge time.
The reason this is a product and not a shell script is that "do these two paths overlap" is deceptively hard. A verbatim edge case from the test suite: src/**/api must match src/api — the **/ has to match zero directories as well as many. Our first implementation got that wrong; a regression test now pins it forever. The boundary cases bite too: src/api and src/apiary must not collide on a naive prefix check. Every overlap decision in Befall routes through one pure, heavily-tested glob-intersection module — exactly the thing you don't want each team re-deriving in a bash one-liner.
Does this mean uploading my code somewhere?
No. The fix is coordination metadata, not your source. The only things that leave the machine are paths, branch names, commit SHAs, dirty-path lists, locks, tasks, and the messages agents explicitly send. Source code and diffs never leave — Befall coordinates on the shape of the work, not its contents. The realtime layer is stricter still: broadcasts are signal-only with an empty payload (payload: {}), and every subscriber re-fetches through the authenticated API rather than trusting anything pushed to it. You get a referee between your agents without handing your codebase to a third party; the security page lists every field.
Which fix should you actually use?
If your tasks are truly separate, you may not need a coordination layer at all. One agent, or two agents on provably disjoint directories you're happy merging by hand — use worktrees, or just take turns, and skip everything else. Adding coordination there is pure overhead, and I'll say so plainly rather than sell you something you don't need.
Reach for advisory locks when the honest answer to "are these tasks disjoint?" is "mostly, I think" — when agents share one working tree, task boundaries blur, more than two agents run at once, or several people each bring their own agent to one repo. That's the regime where "isolate and merge later" quietly turns into "untangle it by hand every afternoon," and it's the regime I built Befall for after living the twenty-minute version of it. Befall is free for 1 room and 2 concurrent agents, so the experiment costs nothing; if it earns its place, founding access is $15/mo (list $29). The setup guide takes about two minutes.