joxo

Blog / claude code

Several Claude Code sessions, one repo: how to keep them apart

Worktrees stop agents from overwriting each other's files. They don't stop duplicate work or merge conflicts. The setup that handles all three.

On this page
  1. One session, one worktree
  2. Worktrees don't prevent conflicts, they postpone them
  3. Claim the task before you start it
  4. How many is too many
  5. The setup, in short

The first time you run two Claude Code sessions in the same checkout, nothing goes wrong. That is the trap. The trouble shows up later, in small ways you can't trace.

Session A is halfway through a refactor. Session B runs the test suite, sees a failure caused by A's half-written file, and "fixes" code that was never broken. Then someone runs git add -A, and one commit now holds two unrelated pieces of work and a diff no reviewer can read.

Nobody did anything silly. Two agents shared a working directory, and a working directory is a single shared mutable object. Here is how we keep sessions apart, in the order the problems tend to appear.

One session, one worktree#

A git worktree is a second working directory backed by the same repository. Each one has its own branch and its own files on disk, so a half-finished edit in one is invisible to the other.

bash
git fetch origin
git worktree add ../shop-auth -b feat/auth origin/main
git worktree add ../shop-search -b feat/search origin/main
cd ../shop-auth && claude

Claude Code can do the same for you. claude --worktree auth creates .claude/worktrees/auth on a branch named worktree-auth, and claude --worktree with no name picks one. The worktree docs cover cleanup and how subagents can run in their own worktree too.

Three things bite people on day one.

Untracked files don't come along. A fresh worktree has no .env, no local config. Claude Code reads a .worktreeinclude file at the repo root, a list of gitignored files to copy into each new worktree:

text
.env
.env.local
config/secrets.json

Ports and databases are still shared. Two dev servers both want port 3000, and two sessions running migrations against one local database will corrupt each other just as surely as two sessions editing one file. Give each worktree its own PORT and its own database name, and say so in your project instructions.

Git refuses a branch checked out twice. That is a feature. If a session says a branch is already in use, another worktree has it. git worktree list shows you which.

When a branch lands, clean up: git worktree remove ../shop-auth, then git worktree prune for any directory you deleted by hand.

Some teams skip worktrees altogether and use virtual branches in one directory; Trigger.dev wrote up why they moved to GitButler. That works too. It still leaves the next two problems, because they have nothing to do with the filesystem.

Worktrees don't prevent conflicts, they postpone them#

Isolation means the collision happens at merge time instead of edit time. That is better, but a conflict in package-lock.json or a route table still costs you an hour.

The files that conflict are predictable: lockfiles, database schema and migrations, the file that registers every route, any index.ts that re-exports things, and generated code. Agents love to touch all of them.

So decide ownership before the sessions start. One owner per area, written down where every agent will read it. In CLAUDE.md:

md
## Parallel work
- You are working in a git worktree. Never edit files outside it.
- Stay in the directories named in your task. If you need a change elsewhere, stop and say so.
- Do not touch `package-lock.json`, `db/migrations/`, or `src/routes/index.ts`.
  Ask the session that owns shared files to make the change.
- Commit small and often. Never use `git add -A`; add the files you changed.
- Rebase on `origin/main` before you open a PR.

The shared files get one owner, usually you. If a feature needs a new route, the feature session does everything except registering it, and tells you the line to add. It feels slow. It is far cheaper than a three-way merge in a lockfile.

You can also see a conflict coming. Since Git 2.38, git merge-tree can merge two branches in memory:

bash
git merge-tree --write-tree feat/auth feat/search

It exits non-zero and lists the conflicted files if the merge wouldn't be clean, and it touches nothing on disk. Run it before you start the review, not after.

Claim the task before you start it#

Worktrees separate the files. Nothing yet separates the intent. Tell two sessions "fix the flaky checkout test" and both will, in different ways, on different branches, and both will be correct. You find out when you review.

The fix is the same one a human team uses: nobody starts work that somebody else holds. Even a file works for one person juggling sessions:

md
<!-- TASKS.md -->
- [ ] checkout test flake    (unclaimed)
- [ ] search pagination      claimed: session-2, branch feat/search
- [ ] auth token refresh     claimed: session-1, branch feat/auth

The rule in your instructions is two lines: read TASKS.md before starting, write your claim before touching code.

Be honest about what this is. A markdown file is not atomic. Two sessions that read it in the same minute will both see "unclaimed". For one person who starts sessions one at a time, that's fine. For several people, each with their own agents on their own machines, it isn't, because the claim has to be refused at the moment it's made, with the name of whoever holds it. This is the part Joxo's task board does: a second claim on the same task is turned away and the agent is told who has it, so it goes and picks something else.

How many is too many#

For most people the number is about three, and the limit isn't the agents. It is you. Every session produces a diff that someone has to read, and reading is the part that doesn't parallelise. Past three, you will start approving things you haven't understood.

A few habits make three workable:

  • Give each session a task you can describe in two sentences. Vague tasks wander across directories.
  • Land the shared change first. If two features both need a new field on User, merge that alone, then branch the features from it.
  • Rebase early and often: git fetch origin && git rebase origin/main at the start of every session.
  • Keep branches short. A branch that lives for a day conflicts far less than one that lives for a week.

The setup, in short#

  1. One session per worktree, branched from origin/main.
  2. A .worktreeinclude for the untracked files, and a port and database per worktree.
  3. A CLAUDE.md section that names what each session may touch, and what it may not.
  4. A claim before work starts, somewhere every session can see.
  5. git merge-tree before review, shared changes first, and no more than three sessions.

None of this is clever. It is what you would tell a new teammate on their first day, written down so the agent reads it on every start.