Back to Blog
·6 min read

Give Every AI Agent Its Own Git Worktree

building-in-publicclaude-codegitparallel-tabsai-agents
Give Every AI Agent Its Own Git Worktree
Table of Contents

I ran five Claude Code agents in parallel one morning this week. By the time the dust settled I'd had three separate git collisions, one branch with two unrelated tabs' commits tangled together, and a recovery that needed a force-push I had to explicitly approve. Everyone's work survived. But the lesson cost me an hour I didn't plan to spend, and it comes down to a single file.
This is a follow-up to the seven-PRs-before-lunch morning. That post was the highlight reel. This one is the bug I hit running the same pattern without one rule in place.

The setup

The parallel-tab pattern looks like this: one coordinator tab holds the day's context and makes merge decisions, and several satellite tabs each carry one scoped piece of work. That morning I had five satellites going — Phase 2 of a content feature, Phase 3 of another, a Stripe doc fix, a Supabase security pass, and an investigation tab chasing a separate bug.
Two of those tabs were dispatched correctly: each got its own git worktree, a separate working directory linked to the same repository. The other two were not. They both ran their git commands inside the shared main checkout, because the work seemed small enough not to bother.
That shortcut is where it went wrong.

What actually broke

Three collisions, in order:
One. A satellite tab made its first commit — and it landed on a different tab's branch. The other tab had run git checkout -b mid-task, which moved the shared HEAD, and the committing tab never knew. Recovery was a --mixed reset back to the right commit, then re-separating the branches. The misplaced work was preserved, but only because I caught it before pushing.
Two. The Stripe-fix tab ran git checkout -b fix/stripe-checklist-doc-rot and moved the shared HEAD again. The security tab's next commit then landed on the Stripe branch. The remote branch ended up with two unrelated commits stacked on it — the security work and the doc fix — entangled on a branch that was supposed to hold one trivial change. Untangling it needed:

git rebase --onto db0f96c 93d66ce
git push --force-with-lease

A force-push is destructive. My own rule is that those need an explicit go-ahead, so the recovery stopped and waited for me before it touched the remote.
Three. While recovering, the security tab had to inspect the Stripe tab's working tree just to understand what had gotten mixed in. Two agents reading each other's uncommitted state to reconstruct who did what.
None of this was the model being dumb. Every individual command was correct. The problem was the environment they all shared.

The technical heart: it's one file

Here's the whole thing in one sentence. .git/HEAD is a single file, and there's one of it per checkout.
Every tab that cds into the same directory reads and writes that same file. So:

  • Tab A runs git checkout branch-a. HEAD now points at branch-a.
  • Tab B, in a different terminal but the same directory, runs git status. It sees branch-a — not whatever it thinks it's on.
  • Tab B commits. The commit lands on branch-a.

There's no race condition exotic about it. It's git working exactly as designed. A checkout is shared mutable state, and I had five agents writing to it concurrently. Parallel processes plus shared mutable state is the oldest bug in the book; I'd just never hit it with git because humans don't usually run five checkouts in one directory at the same time.
Git already ships the fix. Worktrees give each agent its own working directory and its own HEAD, all linked to the same object store:

git worktree add ../wt-stripe-fix -b fix/stripe-checklist-doc-rot origin/main

Now that tab has a private directory, a private HEAD, and a private checked-out branch. It cannot move another tab's HEAD because it isn't touching the same one. The two tabs I had set up with worktrees that morning had zero collisions. The two I didn't accounted for all three.

The numbers

| Metric | That morning |
|---|---:|
| Parallel agents running | 5 |
| Tabs given their own worktree | 2 |
| Tabs sharing the main checkout | 3 |
| Collisions from the isolated tabs | 0 |
| Collisions from the shared tabs | 3 |
| Recoveries needing a force-push | 1 |
The split is the entire argument. Zero from isolation, three from sharing. There was no middle ground and no "it's a small change so it's fine."

The rule

So the rule is now absolute, and it has no exceptions:
Every satellite agent gets its own git worktree. No exceptions — not even a three-line doc fix.
The Stripe fix that caused collision two was a three-line doc fix. That's exactly why "this one's too small to bother" is the trap. The size of the change has nothing to do with it. The git checkout -b moves the shared HEAD whether the diff is three lines or three hundred.
Two mechanical guardrails enforce it now:
1. The coordinator's dispatch prompt always includes a worktree-setup block with the exact git worktree add command. The agent doesn't get to decide whether it needs one.
2. Every satellite's first step is git rev-parse --show-toplevel. If that returns the shared main path instead of its own wt-* directory, the agent halts and says "shared worktree detected" before it commits anything. The check is a few seconds. The collision it prevents is an hour.
The coordinator tab itself stays in the main checkout — that's safe, because the coordinator's job is merging and docs, and it never creates feature branches there. Isolation is for the tabs doing branch work.
This is the same shape as a lesson I keep relearning: a rule that lives only as good intentions gets violated the moment something seems small. The fix is never "remember to be careful." It's to make the safe path the only path the tooling offers. That's why the dispatch prompt carries the command and the first step is a hard check — the discipline moved out of my memory and into the process, the same way I moved my whole operating manual from prose into structure a couple of weeks back.

The pattern, if you're running parallel agents

1. One worktree per agent, always. git worktree add ../wt- -b origin/main. The shared store keeps it cheap; the separate HEAD keeps them from fighting.
2. Make the first action a location check. git rev-parse --show-toplevel as step one. Wrong directory means halt, not proceed-and-hope.
3. Clean up at the end. git worktree remove and git branch -D once merged, so the next session starts clean.
Running agents in parallel multiplies your throughput. It also multiplies every shared-state bug you'd never trip as a single human at a single checkout. The worktree isn't an optimization. It's the isolation boundary that makes the parallelism safe at all.
Five agents, one HEAD, three collisions. Five agents, five HEADs, zero. Give every agent its own worktree.
---
I build ConnectEngine OS in production, in public, most mornings. The scan tool is free if you want to see what it does.

ShareXLinkedIn
TK

Tobias Koehler

Founder, ConnectEngine