Two Agents, One Checkout: Why Parallel Work Needs a Git Worktree Each

Date:

Run two automated agents against the same working directory and one will quietly commit the other’s half-finished files. The fix is one working tree per line of work, not more discipline.

The short answer: A single git checkout has exactly one HEAD and one staging index, so two lines of work running in it at once share both. A broad git add -A in one stages every untracked file in the directory – including the other worker’s in-progress files – and the next commit carries them onto the wrong branch. The fix is a git worktree: git worktree add gives each parallel line of work its own directory, HEAD, and index while sharing the repository’s object store. One worktree per agent, branch, or task, and neither can touch the other’s tree.

What actually goes wrong when two workers share one checkout

We hit this running two automated agents in parallel against one repository checkout. Each was editing different files for different branches. One agent finished a change and ran the ordinary git add -A && git commit – and its commit silently included the other agent’s untracked, work-in-progress files, on a branch those files did not belong to. It happened twice before we understood it. Nothing was lost, but the result was files on the wrong branch, a confusing pull request, and a manual untangle.

A shared checkout does not merge two workers politely; the first to stage sweeps up whatever the second left lying around.

The trigger is mundane: broad staging. git add -A, git add ., and git commit -a do not stage “your” files – they stage every changed and untracked path in the working tree. In a directory only one worker touches, that is exactly what you want. With two workers, it is a trap.

Why one working tree cannot safely hold two lines of work

A git repository separates two things people usually conflate: the object store (the history, under .git/) and the working tree (the checked-out files you edit). A normal git clone gives you one working tree bound to one HEAD – the “current branch” – and one index, the staging area a commit is built from.

That single HEAD and single index are the problem. Two workers in one tree share them. If one runs git checkout other-branch, the tracked files change under the other worker’s feet. If one stages broadly, it stages the other’s files too. There is no per-worker view; the working tree is one global mutable surface, and git has no idea two independent tasks are using it.

One checkout is one HEAD and one index – a single global surface that two tasks cannot safely share.

The fix: a worktree per parallel line of work

Git’s built-in answer is git worktree. It attaches additional working trees to the same repository, each in its own directory, each with its own HEAD and index, all sharing the one object store underneath:

# from the main checkout, create an isolated tree for a task, on a new branch
git worktree add ../wt-featureA -b featureA origin/main
git worktree add ../wt-featureB -b featureB origin/main
Code language: Bash (bash)

Now each agent works in its own directory. A git add -A in wt-featureA can only ever see wt-featureA‘s files; a git checkout there moves only that tree’s HEAD. The two lines of work are physically incapable of capturing or overwriting each other, because they no longer share a tree or an index – only immutable history. Branch each worktree off origin/main so it starts from the current base rather than whatever the shared checkout happened to have checked out.

How a worktree isolates parallel work that a shared checkout cannotOne object store underneath, a separate working tree and index per task
1. One shared checkout

One HEAD, one index. Two workers share both; add -A stages everyone's.

2. git worktree add

A separate directory per task, each branched off origin/main.

3. Isolated by construction

Own dir, HEAD, index; one shared object store. No capture. Clean removal.

Git refuses the same branch in two worktrees at once — that alone blocks the double-checkout that corrupts a shared tree.

A worktree, not a second clone – and why it is cheaper

You could give each worker its own full git clone, and it would also isolate them. A worktree is better on three counts. It shares the object store, so a second tree costs kilobytes of metadata, not a full copy of history. It stays one repository, so branches, tags, and fetches are shared – no juggling several remotes or re-fetching the same objects. And it is disposable: git worktree remove <path> cleans a tree up in one command, and git worktree list shows every tree and the branch it holds.

A worktree shares history and costs almost nothing; a clone duplicates everything and drifts.

The one rule git enforces for you: the same branch cannot be checked out in two worktrees at once. That is a feature – it stops the exact double-checkout that corrupts a shared tree.

The discipline that makes it stick

Tooling helps, but the habit is the real fix. Three rules cover it:

  • One worktree per parallel line of work. Before an agent (or a second person) starts editing, give it its own tree off origin/main. Never let two independent tasks share the main checkout.
  • Never broad-stage a shared checkout. In any directory more than one worker might touch, git add -A and git commit -a are unsafe – stage explicit paths instead.
  • Guard it, but keep the escape hatch open. A pre-commit or wrapper check can block a state-mutating git command (commit, checkout, merge) while another process is active in the same tree. Critically, it must exempt git worktree add – creating a new tree never moves the shared HEAD, so it is the safe way out of the conflict, not into it. A guard that blocks the escape hatch traps you in the shared tree.

This matters far beyond AI agents: two engineers sharing a checkout over SSH, a CI job and a human on the same runner, or a script and a shell in one directory hit the identical collision. It is the same family of shared-resource surprise as a database’s connection limit that is not its concurrency limit – and the mirror image of a resource you should share, like a Playwright browser cache that belongs shared, not per project. The test is always whether the shared thing is immutable history (safe) or a mutable surface (not).

How to verify and clean up

Confirm the isolation and tidy up with three commands:

git worktree list                  # every tree + the branch each holds
git worktree remove ../wt-featureA # delete a finished tree (add --force if dirty)
git worktree prune                 # drop admin records for trees deleted by hand
Code language: Bash (bash)

If you ever see a task’s files appear in another branch’s diff, that is the shared-checkout smell – check git worktree list and make sure each line of work actually has its own tree.

The short version

A single git checkout has one HEAD and one index, so two lines of work running in it share both – and a broad git add -A in one commits the other’s untracked files onto the wrong branch. Give each parallel agent, branch, or task its own git worktree, branched off origin/main: separate directory, separate HEAD, separate index, shared object store. Never broad-stage a shared checkout, and if you add a guard, exempt git worktree add so the escape hatch stays open. Clones work too but cost far more; the worktree is git’s built-in, near-free isolation.

Frequently Asked Questions

What is a git worktree?

A git worktree is an additional working directory attached to the same repository, with its own checked-out branch (HEAD) and its own staging index, sharing the repository’s object store. git worktree add <path> -b <branch> creates one. It lets you have several branches checked out at once, each isolated, without cloning the repository again.

Why did my commit include files I did not create?

Because a broad git add -A / git add . / git commit -a stages every untracked file in the working tree, not just the ones you touched. If another process or person left files in the same checkout, they get staged and committed too. In a shared working tree this is how one worker captures another’s in-progress files onto the wrong branch.

Is a git worktree better than cloning the repository again?

Usually, yes, when the goal is isolating parallel work on one machine. A worktree shares the object store (a second tree costs almost nothing), keeps a single set of branches and remotes, and is removed in one command. A second clone duplicates all history and gives you a separate repository to keep in sync. Clone when you need a genuinely separate copy; worktree when you need isolated trees of the same repository.

Can two worktrees check out the same branch?

No, and deliberately so. Git refuses to check out one branch in two worktrees simultaneously, which prevents the double-checkout that corrupts a shared tree. Give each worktree its own branch (-b newbranch), typically created off origin/main.

How do I remove a git worktree safely?

Run git worktree remove <path> (add --force if the tree has uncommitted changes you intend to discard). If you deleted the directory by hand, run git worktree prune to clear the stale bookkeeping. git worktree list shows every tree currently attached so you can see what is safe to remove.

Share post:

Subscribe

Popular

More like this
Related

How to Merge Spreadsheets by URL (and Why Our Tool Reported 5 Misses That Weren’t)

Matching two spreadsheets on a URL column is the same job as matching a stock list to a price list by product code. It goes wrong in the same two places: how you clean the code, and how you count what didn't match.

Many-to-One: Why a Mapping Backlog Outgrows Its Target Taxonomy

You're mapping a messy source vocabulary onto a clean target taxonomy. The target has about 5,600 slots, so the job must be roughly 5,600 decisions — right? The backlog is already past 8,900, and still climbing.

The Fix That Didn’t Take: When a Content-Hash Gate Skips Your Code Change

You shipped a fix to how one field is computed, re-ran the pipeline across everything, and it reported a clean run. Then the consistency check still flags those records as wrong. The fix is in the code, the rebuild "ran" — and the records never changed. Your own optimisation skipped them.

Your Dedup Scan Is Over-Counting — Because It Forgot What You Already Fixed

The report said 80 duplicate groups still to resolve. You'd been resolving them for days. You run it again after a big batch and it *still* says 80-ish. The backlog isn't stubborn — the scan is counting work you already finished.