Git worktrees for parallel coding agents: what they isolate and what they share
A worktree gives each agent its own files and branch. The stash, hooks, config, ports and databases are still shared. Here is what to set up for each.
Two coding agents in one checkout edit the same files, and sooner or later one of them runs git checkout under the other. The fix is git worktree: one repository, several working directories, each on its own branch. Every agent gets a folder to itself.
A worktree isolates less than it seems to. The files and the branch are separate. The stash, the hooks, the config, and everything outside Git (ports, databases, containers) are shared. This guide shows each one, with the output from a real repository, and a setup script that handles them.
The three commands you need
# A new branch in a new folder beside the repository
git worktree add --no-track -b agent/auth ../app-auth origin/main
# Where every worktree is, and what it has checked out
git worktree list
# Done: remove the folder and Git's record of it
git worktree remove ../app-authBranching from origin/main starts the agent on the latest shared code. Git normally makes a branch created from a remote-tracking branch track it, so --no-track keeps agent/auth from treating main as its upstream. The agent sets its own upstream on its first git push -u.
Start the agent inside the new folder. With Claude Code you can skip the first step: claude --worktree auth creates a worktree under .claude/worktrees/auth on a branch named worktree-auth and starts the session there.
What is separate and what is shared
A linked worktree has a .git file, not a folder. It points back into the main repository:
$ cat ../app-auth/.git
gitdir: /tmp/wt-demo/app/.git/worktrees/app-authThat private folder holds what belongs to this worktree alone. Git's repository layout documentation lists the rest as read from the common directory, shared by every worktree:
| Thing | Per worktree or shared |
|---|---|
| Checked-out files, HEAD, the index | Per worktree |
| Branches and tags | Shared, and a branch can be checked out in only one worktree |
| The stash | Shared |
.git/config | Shared, unless you turn on extensions.worktreeConfig |
Hooks and info/exclude | Shared |
Ignored files: .env, node_modules, build output | Not there at all in a new worktree |
| Ports, databases, containers | Shared by the whole machine; Git has no say |
Each row causes a specific problem when several agents run at once.
1. A branch can only be in one place
Ask for a branch that another worktree has checked out and Git refuses:
$ git worktree add ../app-auth2 agent/auth
fatal: 'agent/auth' is already used by worktree at '/tmp/wt-demo/app-auth'Two working copies of one branch would overwrite each other's commits. Give every agent its own branch. For a throwaway experiment that should not make a branch at all, use git worktree add -d <path>, which checks out a detached HEAD.
2. Your .env did not come along
A worktree is a fresh checkout of tracked files. Anything in .gitignore is missing, so the app fails to start for reasons that look unrelated: no API keys, no local config. Copy what the app needs when you create the worktree. Claude Code automates this with a .worktreeinclude file in .gitignore syntax. It copies only files that match a pattern and are also ignored, so tracked files are never duplicated.
3. Dependencies install again
node_modules is ignored too, so each worktree needs its own install. How much that costs depends on the package manager. pnpm keeps one content-addressable store on disk and hard-links packages from it into each node_modules, so a second worktree with the same lockfile mostly links files that are already on disk. Avoid sharing one node_modules between worktrees with a symlink: the moment an agent changes the lockfile on its branch, every other worktree runs against the wrong dependencies.
4. Two dev servers, one port
Two agents that each run npm run dev both reach for port 3000, and the second one fails or quietly picks another port that nothing points at. Give each worktree its own port range when you create it, and make the app read the port from its environment.
5. Containers and databases
Docker Compose already does part of the work. With no project name set, Compose names the project after the folder that holds the Compose file. Two worktrees in different folders already get separate containers and separate named volumes, so their databases do not mix.
Two things still collide:
- Published ports. Two Postgres containers that both publish
5432:5432cannot start together. Put the host port in a variable. - Folder names. Worktrees from two repositories that share a folder name, such as
fix, get the same project name. SetCOMPOSE_PROJECT_NAMEexplicitly. Compose reads it from the.envfile in the working directory, and project names must be lowercase letters, digits, dashes and underscores.
6. The stash and the hooks are everyone's
Every ref under refs/ is shared, and the stash is one of them. Stash in one worktree and it shows up in another:
$ cd ../app-auth && git stash
$ cd ../app-billing && git stash list
stash@{0}: WIP on agent/auth: 7f63581 initAn agent that runs git stash pop can apply another agent's work to its own branch. Tell your agents not to use the stash (a line in CLAUDE.md or AGENTS.md does it), and have them commit to their branch instead.
Hooks are shared the same way: a pre-commit hook in the main repository ran when we committed from the billing worktree. A hook that runs the whole test suite now runs once per agent per commit, all at the same time. Keep hooks fast, or scope them to the files that changed.
A setup script that handles all six
Run it from the main checkout. The second argument is a slot number that keeps each worktree's ports apart. It assumes a Node project with pnpm and an app that reads PORT and DB_PORT from .env; change those lines to match your stack.
#!/usr/bin/env bash
# new-agent-worktree.sh <name> <slot>
set -euo pipefail
# Compose project names must be lowercase.
name=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')
slot=$2
repo=$(basename "$(git rev-parse --show-toplevel)" | tr '[:upper:]' '[:lower:]')
dir="../$repo-$name"
git fetch --quiet origin
# --no-track: the branch should not treat origin/main as its upstream.
git worktree add --no-track -b "agent/$name" "$dir" origin/main
# Ignored files are not checked out: copy what the app needs.
for f in .env .env.local; do
if [ -f "$f" ]; then cp "$f" "$dir/$f"; fi
done
# Ports and the Compose project, unique to this worktree.
touch "$dir/.env"
grep -vE '^(PORT|DB_PORT|COMPOSE_PROJECT_NAME)=' "$dir/.env" > "$dir/.env.new" || true
cat >> "$dir/.env.new" <<EOF
PORT=$((3000 + slot * 10))
DB_PORT=$((5432 + slot))
COMPOSE_PROJECT_NAME=$repo-$name
EOF
mv "$dir/.env.new" "$dir/.env"
cd "$dir" && pnpm install --frozen-lockfileIn compose.yaml, publish "${DB_PORT:-5432}:5432" instead of a fixed port, so each worktree's database listens where its .env says.
Compose reads only .env. Many app frameworks also load .env.local and let it win, so if the copied .env.local sets PORT, remove that line or have the script write the ports there as well.
Cleaning up
git worktree removerefuses a worktree with changes:contains modified or untracked files, use --force to delete it. Read it before adding--force: the changes are usually an agent's unfinished work.- If a folder was deleted by hand, Git still lists it as
prunable.git worktree prune -vremoves the stale record. git worktree lockstops a worktree from being pruned or removed. Claude Code locks the worktrees it creates for subagents and background sessions while they run, so a cleanup cannot pull one away mid-task.- Submodules are the weak spot. Git's own documentation calls submodule support in multiple checkouts incomplete. A worktree with submodules cannot be moved with
git worktree moveand needs--forceto remove.
The short version
- One branch and one folder per agent.
- Copy ignored config in; install dependencies per worktree.
- Give each worktree its own ports and Compose project name.
- Keep agents off the stash, and keep shared hooks fast.
- Remove worktrees with Git, not with
rm.
If you would rather not script it, Tenslo Code creates the worktree, runs the setup command you name in tenslo.json, and opens a terminal and agent in it.
Sources
- git-worktree documentation, Git
- gitrepository-layout documentation, Git
- Run parallel sessions with worktrees, Claude Code docs
- Motivation, pnpm
- Specify a project name, Docker docs
- Set or change pre-defined environment variables in Docker Compose, Docker docs