Skip to content
Engineering
5 min read

Running AI coding agents in parallel: one git worktree per agent

Two AI agents shared one checkout and their commits landed on each other's branches. What the reflog showed, and the worktree rules I follow now.

Cover: running several AI coding agents in parallel, each in its own git worktree
On this page
  1. What actually happened (from the reflog)
  2. The cleanup
  3. One worktree per agent
  4. The lifecycle, and the traps inside each step
  5. Add: branch from origin/main, explicitly
  6. Install: copy what git ignores, install fresh
  7. Work: one Metro port per worktree
  8. Push, then remove
  9. Takeaways

I build seven iOS apps on my own, and a lot of the routine work now runs as AI coding agents in parallel: one agent audits a codebase for bugs while another rewrites the App Store keywords for ten locales, and a third adds a feature on its own branch. It saves hours. It also has one failure mode that has nothing to do with the code the agents write. They share the repository.

On the morning of 2026-10-01 I ran two batches across my app repos at once: an audit pass on audit/2026-10 branches and an App Store keyword (ASO) pass on aso/2026-10 branches. In three of the repos both agents worked in the same checkout. By 10:35 I had commits on the wrong branches in all three. Nothing broke on origin/main, but cleaning it up took half an hour, and it made the rule I now follow strict: every agent gets its own git worktree.

What actually happened (from the reflog)

A git checkout has exactly one HEAD. Whoever runs git checkout moves it for everyone working in that folder. Two agents in one folder means two writers sharing a single pointer, and neither of them knows the other one moved it.

Talkzy's reflog shows it clearly:

txt
10:22:56  checkout: moving from main to audit/2026-10
10:28:09  commit: fix(prompter): clear the record countdown when the prompter closes
10:30:23  checkout: moving from audit/2026-10 to aso/2026-10
10:33:46  commit: docs(store): ASO pass for all 10 locales (name, subtitle, keywords, promo)

The audit agent switched to its branch and committed a real fix. Two minutes later the ASO agent created its own branch with checkout -b, and that branches from wherever HEAD happens to be. HEAD was on the audit branch. So the ASO branch started with the audit's prompter fix in it, and a store-listing pull request would have shipped an unrelated code change.

Talkzy reflog timeline showing the ASO branch created from the audit branch
Branching from HEAD means branching from whatever the other agent left there

Stampzy failed the other way round. The ASO agent switched the shared checkout to aso/2026-10 at 10:26, and the audit agent, still thinking it was on its own branch, committed its VoiceOver fix there at 10:29. In Baton, the checkout had been moved back to main at 10:28, and the ASO commit landed directly on local main at 10:30.

None of the agents did anything wrong by its own logic. Each one ran correct git commands against a working directory that changed under it.

The cleanup

Because nothing had been pushed to main, every fix was local:

  • Stampzy: cherry-picked the VoiceOver fix onto audit/2026-10 where it belonged, then cut a clean aso/2026-10b from origin/main and cherry-picked only the ASO commit onto it.
  • Talkzy: the same aso/2026-10b treatment, so the ASO branch no longer carried the prompter fix.
  • Baton: pointed aso/2026-10 at the stray commit, then reset local main back to origin/main.

The b suffix on those branches is the scar. Both passes were merged by about 11:07. That half hour is the cost of the shortcut, and it was a cheap lesson only because the reflog keeps every move. Without it I would have been guessing which agent wrote what.

One worktree per agent

git worktree gives one repository several working directories. Each has its own HEAD, its own index and its own files, and all of them share the same object store. An agent in ../talkzy-aso can switch, commit and reset as much as it likes without touching ../talkzy-audit or my main checkout.

One shared checkout versus one worktree per agent
A shared HEAD is the bug; a HEAD per agent removes it

I had already seen this work. On 2026-09-28 Stampzy got three feature branches, feat/albums, feat/paywall and feat/report, cut from the same commit within two seconds of each other. Their commits never show up in the main checkout's reflog, because they were made somewhere else. They were merged back between 12:11 and 12:45 the same day, each followed by a small integration commit on main. The main checkout's HEAD never left main.

So the rule is simple: the main checkout belongs to the lead and only merges. Agents never run git checkout in it.

The lifecycle, and the traps inside each step

A worktree is only isolated if it is complete. Most of the problems I have hit came from the parts git does not copy.

The worktree lifecycle from add to remove
Add, install, work, push, remove

Add: branch from origin/main, explicitly

bash
git fetch origin
git worktree add -b aso/2026-10 ../talkzy-aso origin/main

Naming the base is the whole point. checkout -b with no base is exactly what put the audit fix into Talkzy's ASO branch.

There is a flip side. A worktree only sees committed history. On Lockboxy, an agent working in a fresh worktree could not see features that were still uncommitted in the main checkout. It correctly refused to add paywall rows for code it could not find. If a task depends on uncommitted work, either commit that work first or run the task in sequence, not in parallel.

Install: copy what git ignores, install fresh

A new worktree contains tracked files only. Everything in .gitignore is missing: .env files, node_modules, ios/Pods, build folders. My repos ignore .env*, so any local settings in them have to be copied over first, or the app starts with missing config and the agent wastes a round debugging it.

Then install for real:

bash
cp ../<repo>/.env* . 2>/dev/null   # if the repo has any
yarn install --immutable   # apps (Yarn 4); the Next.js landings use npm ci

Do not symlink node_modules from the main checkout to save time. I tried that on Lockboxy, and archiving the app failed in the Bundle React Native code and images phase: Metro will not resolve packages through a symlinked node_modules. A hardlink copy (cp -al) worked and cost no extra disk, but a clean install is the simplest thing that works. Copying ios/Pods has its own trap: on Minivid, Pods copied from a tree whose last build was a Release archive produced Debug link errors until the build-configuration markers were fixed.

Work: one Metro port per worktree

Two React Native worktrees both want Metro on 8081. Each worktree gets its own port, and the simulator is pointed at it. Baton's Maestro flows take the Metro address as a parameter for exactly this reason.

On Lockboxy I also learned to exclude .claude/worktrees/ (where the agent tooling puts its worktrees) from both git and Jest. Otherwise Jest's recursive discovery ran the tests of every nested copy, including half-edited ones, and mixed their results into mine.

Push, then remove

The agent pushes its branch, I review and merge in the main checkout, and the worktree goes away:

bash
git worktree remove ../talkzy-aso
git worktree prune
git branch -d aso/2026-10

Cleanup is the step that gets skipped. While writing this post I found a landing worktree from 2026-09-29 still sitting next to its repo, after its branch had been merged.

Takeaways

  • Two agents in one checkout share one HEAD. A checkout by one silently moves the other, and their commits land on each other's branches.
  • Give every agent its own worktree, created from an explicit origin/main, never from whatever HEAD is.
  • A worktree is not ready until it has the ignored files: copy .env, install dependencies fresh, and do not symlink node_modules for React Native.
  • Keep the main checkout for merging only, and remove worktrees once their branch is pushed.
  • When something does go wrong, git reflog tells you exactly which agent moved what, and when.

The apps these agents work on are all listed at apps.vanthuongdao.id.vn; Talkzy, the one with the clearest reflog, lives at talkzy.io.vn.

  • #AI Agents
  • #Git
  • #Worktrees
  • #Workflow
ShareXLinkedInFacebook
Dao Van Thuong

Mobile and fullstack engineer in Ho Chi Minh City. I build and ship my own indie iOS apps — Lockboxy, Linkeeper, Minivid, Ringsy, Talkzy, Baton and Stampzy.