DEV Community

yureki_lab
yureki_lab

Posted on

How I Run 4 Claude Code Agents in Parallel on One Repo Without Chaos

TL;DR

I run up to four Claude Code agents at the same time on a single repository by giving each one its own git worktree, a tightly scoped task, and a merge queue at the end. Done right, parallel agents turned a week of sequential refactoring into an afternoon. Done wrong (I did it wrong first), you get four agents editing the same file and a merge disaster that eats every minute you "saved." Here's the setup, the failure modes, and the rules I follow now. πŸš€

The Problem

One Claude Code session is great. But last quarter I stared at a backlog that was embarrassingly parallel:

  • Migrate ~30 API handlers to a new validation library
  • Add missing JSDoc to the public surface of a shared package
  • Replace a deprecated logging call in ~80 files
  • Write characterization tests for a module nobody dared touch

None of these tasks depended on each other. Running them one at a time through a single agent session meant babysitting my terminal for days, mostly watching an agent do work that didn't need my attention.

The obvious idea: run four agents at once. The obvious problem: they'd all be working in the same working directory. Two agents running npm test simultaneously clobber each other's build artifacts. Two agents editing neighboring lines of the same barrel file produce garbage. One agent runs git checkout mid-task and yanks the floor out from under the other three.

My first naive attempt β€” four terminal tabs, same directory, "they probably won't collide" β€” lasted 40 minutes before agent #2 committed agent #3's half-finished changes along with its own. I reverted everything and started over with an actual design.

How I Solved It

The fix has three parts: isolation (git worktrees), decomposition (non-overlapping task contracts), and integration (a serial merge queue). I'm on Claude Code v2.x and git 2.44 here, but nothing below is version-sensitive.

Part 1: One worktree per agent

Git worktrees are the underrated feature that makes this whole thing work. A worktree is a second (third, fourth...) checkout of the same repository that shares one object database but has its own working directory, its own index, and its own checked-out branch:

# From the main checkout
git worktree add ../repo-agent-1 -b agent/validation-migration
git worktree add ../repo-agent-2 -b agent/jsdoc-pass
git worktree add ../repo-agent-3 -b agent/logging-swap
git worktree add ../repo-agent-4 -b agent/characterization-tests
Enter fullscreen mode Exit fullscreen mode

Now each agent gets launched with its own directory as the working root:

cd ../repo-agent-1 && claude "Migrate the API handlers in src/api/ to zod validation. Task spec is in TASK.md."
Enter fullscreen mode Exit fullscreen mode

Each agent can run tests, install dependencies, create commits, even make a mess β€” and the other three never see it. No shared index, no shared working tree, no git checkout rug-pulls. The failure mode from my naive attempt is structurally impossible.

Two practical notes that bit me:

  • Dependencies don't come along for free. A fresh worktree has no node_modules. Either let each agent install its own (slow, safe) or symlink from the main checkout (fast, occasionally cursed when an agent modifies a lockfile). I install fresh. The 90 seconds of npm ci per worktree is nothing next to debugging a shared-node_modules heisenbug.
  • Ports and databases are still shared. Worktrees isolate the filesystem, not the network. If two agents both start a dev server on port 3000, they collide. I pass each agent an env var (PORT=3001, PORT=3002, ...) and a scratch database name in its task spec.

Part 2: Task contracts that can't overlap

Isolation stops agents from stepping on each other's working directories, but it doesn't stop them from editing the same logical files β€” which just moves the collision from runtime to merge time. Way better, still bad.

So every parallel task gets a short task contract β€” a TASK.md dropped into the worktree before the agent starts:

# Task: Replace deprecated log.info() calls

## You own (may edit):
- src/**/*.ts EXCEPT src/api/** and src/shared/logger/**

## You must NOT touch:
- src/api/**            (owned by agent-1 this session)
- package.json, any lockfile
- CI config

## Definition of done:
- `grep -r "log.info(" src` returns 0 hits outside src/api
- `npm test` passes
- Work is committed on this branch with a descriptive message
Enter fullscreen mode Exit fullscreen mode

The load-bearing part is the ownership map. Before launching anything, I spend ten minutes deciding which agent owns which paths, and the union must be disjoint. If two tasks genuinely need the same file, they don't run in parallel β€” one of them waits. That sounds obvious written down; it took a mangled merge for me to actually start doing it.

Shared "junction" files (barrel exports, route tables, config) deserve special paranoia. My rule: no agent touches a junction file during a parallel session. If a task needs a new export added to an index file, the agent leaves a note in its final commit message and I do the two-line edit myself during integration.

Part 3: A serial merge queue

Parallel work, serial integration. When agents finish, I never merge branches simultaneously or in arbitrary order. The flow is:

graph LR
    A[agent/validation] --> Q{merge queue}
    B[agent/jsdoc] --> Q
    C[agent/logging] --> Q
    D[agent/tests] --> Q
    Q -->|one at a time| I[integration branch]
    I -->|full CI green| M[main]

Concretely:

  1. Pick the branch with the highest blast radius first (usually the one touching the most files).
  2. Merge it into an integration branch, run the full test suite.
  3. Rebase the next agent branch onto integration, run that branch's definition-of-done check again, merge.
  4. Repeat. main only ever receives integration after everything is green together.

Step 3 matters more than it looks. Each agent validated its work against the repo as it was when the session started. After the first merge, that assumption is stale. The rebase-and-recheck catches interactions β€” like the validation migration changing an error message format that the new characterization tests had snapshotted. If the ownership map was truly disjoint, rebases are conflict-free and this whole phase is 20 minutes of watching CI. When it's not conflict-free, that's a signal my decomposition was wrong, and I treat it as a lesson for next session's ownership map, not as a merge problem to power through.

What a session actually looks like

My end-to-end loop for a four-agent afternoon:

# 1. Decompose: write four TASK.md files, check ownership is disjoint
# 2. Spin up worktrees + branches (script does this in ~10s)
./scripts/spawn-worktrees.sh validation jsdoc logging tests

# 3. Launch agents, one terminal tab each, non-interactively
cd ../repo-validation && claude -p "$(cat TASK.md)" &

# 4. Check in every ~20 min; answer questions, unstick anyone stuck
# 5. Integration: merge queue, one branch at a time
# 6. Tear down
git worktree remove ../repo-validation  # etc.
git worktree prune
Enter fullscreen mode Exit fullscreen mode

While agents run, I do interrupt-driven supervision instead of continuous babysitting: glance at each tab, unstick whoever's stuck, and otherwise do my own work. Four tasks that would have serialized into roughly four days of elapsed time landed in main the same evening.

Lessons Learned

  1. Parallelism amplifies your decomposition skills β€” in both directions. With a clean ownership map, four agents β‰ˆ 3.5x throughput. With a sloppy one, four agents produce merge conflicts faster than one agent produces code. The ten minutes of upfront path-ownership planning is the highest-leverage ten minutes of the whole session.

  2. Worktrees beat clones, and both beat shared directories. Full git clones per agent also work but waste disk and drift from your local branches. Worktrees share the object store, so they're near-instant to create and trivially cheap. Shared directories are not an option; don't let "it's just two quick tasks" tempt you.

  3. Four is my ceiling, and the bottleneck is me. Agents don't get slower with more parallelism β€” supervision does. Each additional agent adds another stream of questions, another integration branch, another definition-of-done to verify. At five or six I stop actually reviewing and start rubber-stamping, which defeats the point. Your ceiling might differ; you'll know you've passed it when you stop reading diffs.

  4. Not every backlog is parallel. I now sort tasks into "embarrassingly parallel" (mechanical migrations, test backfills, doc passes β€” disjoint by nature) and "inherently serial" (anything touching core abstractions that everything else imports). Forcing serial work into parallel sessions is how you end up re-doing three branches after the fourth changes the interface they all depend on.

  5. Make agents commit early and often on their own branch. My task contracts require a commit at every meaningful checkpoint. When an agent goes sideways (one decided mid-task to "improve" an unrelated module ⚠️), git log on its branch tells me exactly where the plot was lost, and I reset to the last good commit instead of restarting the whole task.

What's Next

Two things I'm actively experimenting with:

  • Automating the ownership check. Right now "the union of owned paths is disjoint" is verified by me squinting at four TASK.md files. A 50-line script that parses the globs and fails loudly on overlap would remove the last human error source in the setup phase.
  • A cheaper supervision loop. I want each agent to append one status line to a shared file every few minutes, so a single watch cat status.log replaces tab-hopping. Interrupt-driven supervision is good; glanceable supervision would be better.

Wrap-up

Running agents in parallel isn't a Claude Code feature you turn on β€” it's a workflow you design. Isolate with worktrees, decompose with explicit ownership, integrate serially. Get those three right and the multiplier is real.

If you've built your own multi-agent setup β€” especially if you've pushed past four agents without losing the plot β€” I'd genuinely love to hear how you handle integration. Drop a comment πŸ‘‡, and follow me here on Dev.to for more write-ups on running AI coding agents against real codebases. βœ…

Top comments (1)

Collapse
 
raknaos profile image
Raknaos

This matches what I landed on running a small agent fleet on a VPS: the worktrees are the cheap part, the merge queue is where the actual time goes. My agents fought over shared files (lockfile, router registration, test fixtures) far more than over feature code, so now I carve tasks explicitly along file boundaries before spawning anything β€” if two tasks touch the same generated file, they run sequentially by design.

How do you handle the rebase drift when four branches sit on the same base for hours? Do agents rebase onto main before entering your queue, or does the queue do it serially?