DEV Community

yureki_lab
yureki_lab

Posted on

How I Run Multiple Claude Code Agents in Parallel Without Collisions

TL;DR

I started running multiple autonomous Claude Code sessions on the same repo at once to speed things up, and it backfired — collisions, half-written diffs, and one memorable case where two agents "fixed" the same bug in opposite directions. Git worktrees fixed it. Here's what I learned about isolating parallel agents, when isolation is actually worth the overhead, and how to merge the results back without losing your mind.

The Problem

Once you've got one autonomous coding agent working reliably, the obvious next move is: why not run three? Three agents, three tasks, done in a third of the time.

I tried it on a single checkout of the repo. Bad idea.

Here's what actually happened:

  • Agent A was mid-way through refactoring a shared utility file when Agent B started editing the same file for an unrelated task. Git status looked like a crime scene.
  • Agent C ran a full test suite while Agent A was still writing files — half the failures were real, half were just files caught mid-write.
  • Worst one: two agents independently decided the same function had a bug, and "fixed" it in contradictory ways. Neither agent knew the other existed, so neither flagged a conflict — I only caught it in review.

The root problem is obvious in hindsight: agents don't know they're sharing a filesystem. They behave as if they have exclusive ownership of the working directory, because from their perspective, they do. Give them a shared one and you've built a race condition generator.

How I Solved It

The fix is boring in the best way: one git worktree per agent.

git worktree add ../repo-agent-a -b agent-a/refactor-utils
git worktree add ../repo-agent-b -b agent-b/add-retry-logic
git worktree add ../repo-agent-c -b agent-c/fix-flaky-test
Enter fullscreen mode Exit fullscreen mode

Each worktree is a full working directory checked out from the same .git, on its own branch. Same history, same objects, zero shared working-directory state. Agent A can rewrite utils.ts into oblivion and Agent B never sees a flicker.

flowchart LR
    subgraph shared[".git (shared object store)"]
    end
    shared --> WA["worktree: agent-a\nbranch: agent-a/refactor-utils"]
    shared --> WB["worktree: agent-b\nbranch: agent-b/add-retry-logic"]
    shared --> WC["worktree: agent-c\nbranch: agent-c/fix-flaky-test"]
    WA --> MA["agent A runs here\nisolated files + own test run"]
    WB --> MB["agent B runs here\nisolated files + own test run"]
    WC --> MC["agent C runs here\nisolated files + own test run"]
Enter fullscreen mode Exit fullscreen mode

A few things I had to get right beyond just "add worktree, spawn agent":

1. Isolation is not free — use it selectively. Spinning up a worktree costs disk and setup time. For a one-line typo fix, that overhead isn't worth it. I only isolate when agents are (a) touching files that might overlap, or (b) running long enough that a collision would waste real work. Quick, narrowly-scoped tasks still run directly against the main checkout.

2. Each worktree needs its own dependency install. node_modules, virtualenvs, build caches — none of that is shared by default, and skipping the install step gets you confusing "works in the main checkout, fails in the worktree" bugs. I bake the install into the agent's setup step rather than assuming it inherited from the parent repo.

3. Test runs need to be scoped to the worktree too. This one bit me early — an agent would run the test suite, but some tooling in the project resolved paths relative to the original repo root instead of the worktree it was actually standing in. Always verify pwd and any hardcoded paths before trusting a green test run.

4. Merge-back is a second decision point, not an afterthought. When an agent finishes, I don't auto-merge. Each worktree's branch gets reviewed (by a human or a second verification agent) before it lands on main. Parallelism sped up the work, not the judgment call about whether the work is correct — those are separate problems.

5. Clean up worktrees when you're done. They don't disappear on their own, and a repo with a dozen stale worktrees pointing at abandoned branches is its own kind of mess.

git worktree remove ../repo-agent-a
git worktree prune
Enter fullscreen mode Exit fullscreen mode

Why not just... branches, or full clones?

I tried both before landing on worktrees, and it's worth explaining why they didn't work.

Branch switching on one checkout is the obvious first instinct — just git checkout between tasks. It falls apart the moment two agents run concurrently, because "checked out branch" is a single piece of state per working directory. Agent A checks out agent-a/refactor-utils, starts writing files, and if Agent B checks out anything else in that same directory mid-run, Agent A's uncommitted work either gets carried along, stashed unexpectedly, or blown away depending on exactly what happened when. It's not a parallelism model at all — it's a queue pretending to be parallelism, with landmines.

Full repository clones (git clone per agent) actually work, isolation-wise — each clone has its own everything. But they're wasteful: every clone duplicates the full object database, and for a repo with any real history that's real disk and real clone time. Worktrees share the object store, so spinning one up is close to instant and costs almost nothing beyond the working files themselves. Clones only started to make sense for me when an agent needed a genuinely separate .git (e.g., testing something that mutates git config or hooks), which is rare.

Worktrees hit the sweet spot: full working-directory isolation, shared object store, cheap to create and destroy, and branches stay visible in git worktree list and git branch like normal — nothing hidden in a temp directory somewhere else on disk.

A minimal wrapper

In practice I don't type git worktree add by hand for every task — there's a thin wrapper that a launcher script calls before handing a task to an agent:

spawn_agent() {
  local name="$1" task="$2"
  local branch="agent-${name}/$(slugify "$task")"
  local dir="../repo-${name}"

  git worktree add "$dir" -b "$branch"
  (cd "$dir" && npm install --silent)

  run_agent --cwd "$dir" --task "$task"
}
Enter fullscreen mode Exit fullscreen mode

Nothing fancy — the only non-obvious part is that the dependency install happens inside the new worktree directory, explicitly, rather than assuming anything carries over. That one line has saved me more debugging time than everything else in the wrapper combined.

Lessons Learned

  1. Agents assume exclusive ownership of the filesystem, even when they don't have it. If you're running more than one, isolation isn't optional polish — it's the thing that makes parallelism safe at all.
  2. Git worktrees are the right unit of isolation for this, not full repo clones. You get isolated working directories without duplicating the entire .git history, and branches stay first-class.
  3. Isolation has a cost, so gate it on task risk, not on "are there multiple agents." A tiny, well-scoped fix doesn't need its own worktree.
  4. Dependencies and paths don't automatically follow the agent into the worktree. Treat every worktree as a fresh environment until proven otherwise.
  5. Parallelism should speed up work, not skip review. Merging back is still a deliberate checkpoint — don't let concurrent execution turn into concurrent, unreviewed merges.

What's Next

I'm looking at whether it's worth automating the "does this task need isolation" decision instead of eyeballing it — right now it's a judgment call based on file overlap and task length, which works but doesn't scale past a handful of concurrent agents.

Wrap-up / CTA

If you're running more than one AI coding agent against the same repo, worktrees are a five-minute fix that'll save you a very confusing afternoon. If this was useful, follow me here on Dev.to — I'm writing up more of these lessons as I go. And if you haven't tried Claude Code yet, it's worth a look for exactly this kind of agentic workflow.

Top comments (0)