The setup
One repository, one working tree, one index — however many branches exist. That is the problem: git switch rewrites the single working tree, and the index is shared, so two agents on one checkout corrupt each other’s staged state. A linked worktree gives each one its own (git-worktree).
| Command | Effect |
|---|---|
git worktree add -b agent-x ../wt-x | New directory, new branch, its own index and working tree. One per agent. |
git worktree list | Every working tree, its checked-out commit and branch. The first line is the original clone. |
git worktree remove ../wt-x | Cleans up. Does not delete the branch — the commits are all in the one object database. |
git worktree prune | Tidies the bookkeeping after a directory you deleted by hand. |
cat ../wt-x/.git | One line: gitdir: …. In a linked worktree .git is a file, not a directory — that is the whole mechanism. |
The branch lock
Git enforces the one rule that makes this safe: a branch can be checked out in at most one worktree. add “refuses to create a new worktree when <commit-ish> is a branch name and is already checked out by another worktree”, and switch refuses for the same reason:
fatal: 'agent-retry' is already used by worktree at '/tmp/l09/wt-retry'
Two agents cannot end up on one branch by accident. Overriding it takes --force, which is a thing you have to mean.
The lock is a property of one repository, not of a branch name — so it does not extend to people. Two separate clones can both sit on feature with no complaint at all, which is the ordinary state of any team and is why the guardrail that protects your agents from each other does nothing for your colleagues. Verified 2026-08-01.
The cheap screen
Two branches can only conflict where the sets of files they touched overlap. Compute each set and intersect the pairs. Each set is a three-dot diff — what that branch did since it left, not how it compares to main’s current tip:
comm -12 <(git diff --name-only main...agent-retry | sort) \
<(git diff --name-only main...agent-timeout | sort)
Non-empty means escalate. <(…) is process substitution: bash and zsh have it, sh and dash do not.
A shared file means a conflict is possible, not certain — two edits in one file merge silently unless their regions are adjacent. And an empty intersection only proves something if both branches have commits: an empty result because a branch is empty looks identical to one because two branches are disjoint.
The real check, without touching anything
git merge-tree --write-tree <a> <b> “performs a merge, but does not make any new commits and does not read from or write to either the working tree or index” (git-merge-tree). It is not an approximation — it “will use the same features as the ‘real’ git merge”, including rename detection and virtual merge bases. Safe to run in a loop while every agent is mid-task.
| Outcome | What you get |
|---|---|
| Clean | “For a successful merge, the output from git-merge-tree is simply one line” — the OID of the tree it would produce. Exit status 0. |
| Conflicted | That tree, then the stage table from lesson 08 for each conflicted path, then the informational CONFLICT lines. Exit status 1. |
| Could not run | Any other exit status. So a supervision loop never has to parse the output at all. |
Which makes the whole supervision loop two lines of shell over each pair of agent branches:
git merge-tree --write-tree "$a" "$b" >/dev/null 2>&1
case $? in 0) echo "$a $b clean";; 1) echo "$a $b COLLIDE";; *) echo "$a $b error";; esac
Note the ; rather than && — a conflicted merge-tree exits non-zero, and chaining would swallow the very thing you are reading. With --stdin the exit status is 0 either way, so do not use that form for this.
What you still have to duplicate
Git shares its own data and nothing else. Each worktree needs its own node_modules, its own build output and its own .env, because those live in the working tree and each worktree has a separate one. Untracked files do not travel between them either — usually what you want, occasionally a surprise.
| Decision | The honest answer |
|---|---|
| Worktrees or separate clones? | Worktrees, unless you need different remotes or a submodule-heavy superproject — the docs call multiple checkout “still experimental” and submodule support “incomplete”. |
| How many agents? | An empirical question, not a preference: run the file-intersection check over the branches they would work on. Heavily coupled code collides at two. |
| Which branch lands first? | The smaller change. The second one has to resolve against the first, so you want the harder resolution to be the one with more context. |
- git-worktreeREFS for the sharing rule, CONFIGURATION FILE for the shared config, DETAILS for the
.gitfile, and BUGS for the honest limits. - git-merge-treeThe supervision command: output format, exit statuses, and the guarantee that it uses the same machinery as a real merge.
- git-diff — the three-dot formWhy
main...branchanswers “what did this branch do since it left” andmain..branchdoes not. - Git worktrees for parallel AI coding agents — UpsunA practitioner write-up of this exact pattern. Treat as a pattern source; verify mechanics against the docs above.