What Happened When I Let Several AI Agents Loose in One Repo
I thought running agent sessions in parallel would multiply productivity. What I actually got, in the space of one week, was a string of real incidents: hijacked branches, commits landing on someone else's branch, orphaned commits rescued from the reflog, and the same feature implemented twice. A typology of the accidents parallel agents cause when they share one checkout, and the rules I built after living through them.
TL;DR
The real enemy of parallel agent sessions isn't model skill but the shared working directory. HEAD is everyone's global variable, so one session switching branches lands another session's commit in the wrong place. Four accident types from one week and the recoveries, plus three lines of defense: worktree isolation, re-checking the branch right before committing, and narrowing the verification gate's scope.
On this page
Work with AI agents for a while and the ambition comes naturally. While one session fixes a bug, another can refactor, and a third can investigate an issue, right? You can spin up as many models as you like, so productivity should scale to match.
That's how I started too. And within a week I learned that the real enemy of parallel agents isn't the models' skill. It's the working directory they share.
HEAD is a global variable
The cause fits in one sentence. When multiple sessions share a single git checkout, the current branch becomes everyone's global variable.
Picture two people working on one computer at the same time and the absurdity is obvious, but that thought never occurred to me while spinning up agents. With one session per terminal tab, they look isolated from each other. But there is one filesystem, and one HEAD. The moment one session runs git checkout, the ground shifts under every other session.
The incidents from that week fell into clear types.
Branch hijacking. While session A was working on a topic branch, session B switched branches to do its own work. A committed without knowing, and the commit landed on top of B's branch. It happened in the other direction too: right as A was about to commit, the branch had been switched to develop, and only the hook that blocks direct commits to protected branches saved it. Without the hook, it would have gone straight in.
Orphaned commits. Session B deleted session A's topic branch during a cleanup pass. A's commits became orphans belonging to no branch, and I dug through the reflog, found the commit hashes, and recovered them with cherry-pick. Lucky that it worked; if the reflog had expired or I hadn't found them, the work would have simply evaporated.
Staging contamination. At the moment session A was creating a commit, a file deletion that session B had staged was sitting in the staging area alongside it. Committed as-is, B's deletion would have been folded into A's commit. It only got filtered out because an unfamiliar change showed up while skimming the diff. If the agent hadn't looked at the diff right before committing, nobody would have known.
Duplicate implementation. The most deflating type. Two sessions, each unaware of the other, implemented the same feature independently. Both did a diligent, solid job, and one had to be thrown away wholesale. The time saved by parallelizing was handed straight back.
They even trip each other's verification gates
Files and branches weren't the only problem. Our harness has a gate that runs the repo-wide static analysis and tests before a session can finish, so work can't end in a broken state. For a solo session, it's an excellent mechanism.
With parallel sessions, it became a trap where they trip each other. Session A only touched documentation, but A's gate fails because of a compile error in a file session B is midway through fixing. A burns turns proving "this isn't my change," and on a bad day waited 30 minutes for B to clean up. The worst incident was a session modifying code that another session was working on, just to get its own gate to pass. The gate itself became an incentive to touch someone else's work.
The check isn't what's wrong. The check's scope being "the whole repo" became wrong the moment the repo stopped belonging to one person.
Three lines of defense
After going through the accidents type by type, I put up three layers of defense.
- Worktree isolation as the default. Give each session its own independent working directory with
git worktreeand HEAD is no longer a shared variable. Branch hijacking, orphaned commits, and staging contamination disappear at the root. Three of the four accident types above vanish with this one move. It isn't free, though. In a monorepo, every worktree needs its own dependency install and code generation, and in a repo using tools that get along badly with worktrees, like git-crypt, it's hard to enforce. We keep one repo on a shared checkout because of exactly that constraint, which is why the other two defenses exist. - Re-check the branch right before committing. When a session starts work, it records "I am working on this branch," and right before committing it compares that against the current HEAD. If they differ, stop the commit and figure out what happened first. The rule is almost embarrassingly simple, but every branch-hijack incident boiled down to "the HEAD at commit time wasn't the HEAD I knew about." A human would notice the branch name sitting in their prompt; an agent doesn't check unless explicitly told to.
- Scope the verification gate to my changes. Narrow what the session-exit gate inspects, from the whole repo down to the files that session actually modified. No more getting my exit blocked by someone else's WIP, and no more incentive to touch someone else's code just to get the gate to pass. The health of the whole repo is something CI re-checks against committed state anyway. The session gate never needed to look at everything.
On top of these, one operating rule. Before spinning up a session, skim the currently open branches and PRs and check for overlapping scope. Duplicate implementation isn't a problem git can block; it's a dispatching problem, so the only way to prevent it was habit, not tooling.
Concurrency problems were never just a database thing
Laying it all out, the picture looks familiar. A shared resource, lock-free concurrent access, race conditions, and isolation levels. The exact problem we spent decades learning about in databases and multithreaded code, replayed on top of a working directory.
With a single agent, the problem doesn't exist. Even when a human and an agent take turns, the human implicitly plays coordinator. The trouble starts the moment there are multiple agents and the human lets go of the coordination. From that point on, the working directory is a shared resource that needs concurrency control, and if you run it without isolation, you will lose data, just as databases did.
This is not an argument for giving up on parallel agents. I still spin up multiple sessions every day. Only one thing changed: I no longer cram them all into the same room.
Related posts
- 💻 Dev
A Pipeline Where a Second AI Tries to Disprove the First One's Fix
Ask a model to review its own code and it just re-approves its own conclusions. A solo developer who writes almost all code with AI explains how telling one AI to "try to refute" another AI's work hardened into a permanent cross-check pipeline. Real cases where it caught bugs that self-review missed three rounds in a row, plus the prompt design.
- 💻 Dev
When the Agent Says "It's Done," Don't Take Its Word for It
AI coding agents report a job as finished when it isn't. They say tests pass without running them, and say a bug is fixed without ever reproducing it. A solo developer who writes almost all code with AI shares the verification gate that made "done" something you prove with evidence, not something you can just say.
- 💻 Dev
I Stopped Trusting the Agent's "Done" - prove-it, a verify.sh Gate
AI coding agents report "tests pass" without running the tests. That's not lying - it's a structural problem. What I learned building prove-it, an open-source gate that turns reports into checks - and the four ways a hand-rolled five-line hook fails silently.