Isolated workspaces with git worktrees, bare repos, and Worktrunk
Git worktrees let you work on multiple branches simultaneously without the context switching of git checkout. They provide separate working directories that share a single repository, eliminating the need to stash changes, wait for IDE reindexing, or maintain multiple clones.
This makes them useful for everyday development workflows like code review, hotfixes, and cross-branch debugging. But they really shine when running multiple AI agents in parallel, each handling a discrete task in its own isolated workspace.

Linked working directories, shared history.
Worktrees create linked working directories that reference the same .git repository. They share commits, branches, and tags while maintaining independent working files and HEAD pointers.
| Command | Description |
|---|---|
git worktree add <path> <branch> |
Create new worktree at path for existing branch |
git worktree add <path> -b <new-branch> <base> |
Create new worktree with new branch |
git worktree list |
Show all worktrees and their branches |
git worktree remove <path> |
Delete a worktree |
git worktree prune |
Clean up worktree metadata for deleted directories |
git checkout rewrites files in your working directory, updates HEAD, and forces your IDE to re-parse thousands of changed files. On large codebases, this "IDE tax" can exceed 30 seconds. Worktrees sidestep this entirely. Context switching becomes simply changing directories. Compared to multiple clones, worktrees use 15% of the disk space and create 4.5x faster.
Clones duplicate, shared checkouts collide.
The simplest approach is to clone the repo multiple times, one per agent or task. But each clone duplicates the full Git history, eats disk space, and drifts out of sync. Fetching, pruning, and coordinating branches across clones becomes its own maintenance burden.
The other extreme, running multiple agents on the same clone, is worse. Agents overwrite each other's files, compete for the working directory, and can't be on different branches simultaneously. One stash or checkout wipes another agent's context.
Make the repo coordination-only, with no privileged branch.
With a standard clone, worktrees are created as subdirectories inside the main working tree. This means tooling picks up the wrong files, and the parent worktree's state bleeds into every child. A bare repo eliminates this by containing only Git metadata, with no working directory of its own. All worktrees live as siblings outside it, each a clean checkout of a branch.
project/
├── .bare/ <- bare repo (no working files)
├── .git <- points to .bare
├── main/ <- worktree: main branch
├── feature/auth/ <- worktree: agent 1
├── feature/payments/ <- worktree: agent 2
└── hotfix/login-crash/ <- worktree: agent 3
Why this matters:
main can be a worktree, but it's opt-inInitial setup (once per project):
# Clone as a bare repo
git clone --bare https://github.com/user/project.git project/.bare
# Point .git so tooling finds it
echo "gitdir: ./.bare" > project/.git
# Fix the remote fetch config
git -C project/.bare config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
# Fetch all remote branches
git -C project/.bare fetch originMake worktree management a single command.
Raw git worktree commands are verbose and leave everything else to you: no dependency installation, no environment setup, no way to see status across worktrees at a glance. Worktrunk wraps the full worktree lifecycle into simple commands and adds the automation layer that makes worktrees practical at scale.
Key reasons we chose it:
.config/wt.toml is committed to the repo, so every agent and teammate gets identical setup automaticallyInstall and configure:
brew install worktrunk && wt config shell installShell integration lets commands like wt switch change your working directory automatically.
Global config - set the worktree layout so worktrees are created as sibling directories (works with the bare repo pattern):
# ~/.config/worktrunk/config.toml
worktree-path = "../{{ branch | sanitize }}"Project config - committed to the repo so every developer and agent gets the same setup:
# .config/wt.toml
[post-create]
install = "pnpm install"
copy-env = "cp $(git worktree list | head -1 | awk '{print $1}')/.env .env"The post-create hooks run after every new worktree, ensuring dependencies are installed and environment files are copied automatically.
If you use Worktrunk alongside Claude Code, the Worktrunk plugin adds status indicators to wt list showing which Claude sessions are actively working vs. waiting on user input.
Built-in isolation, no external tooling required.
Claude Code has first-class worktree support via the --worktree flag (or -w). It creates a worktree, starts a session in it, and cleans up on exit:
claude --worktree my-feature
# or shorthand
claude -w my-feature
# omit the name and Claude generates one (e.g., "bright-running-fox")
claude -wWorktrees are created in .claude/worktrees/ inside the repo by default. This keeps them contained (no sibling folder sprawl), hidden from normal ls output, and easy for Claude to auto-cleanup on exit.
Note that --worktree does not currently support the bare repo pattern. It expects a standard clone with a working directory.
New worktrees are missing .env files (gitignored) and node_modules. Without a hook, the agent wastes tokens debugging missing environment variables before it even starts the real task.
WorktreeCreate hooks solve this. They replace the default git worktree behavior entirely: your hook must create the worktree itself and print the absolute path to stdout.
// .claude/settings.json
{
"hooks": {
"WorktreeCreate": [
{
"command": ".claude/hooks/setup-worktree.sh"
}
]
}
}The hook script can detect the package manager, install dependencies, and copy .env files automatically on every new worktree.
# Spin up a worktree per task (hooks install deps automatically)
wt switch --create feature/auth
claude "implement OAuth 2.0 login flow per the spec in docs/auth.md"
# Meanwhile, in another terminal
wt switch --create hotfix/payment-bug
claude "fix the Stripe webhook signature validation bug in issue #342"
# Check status across everything
wt list# Check out a PR directly (resolves branch via gh CLI)
wt switch pr:147
# Review the code, then return to your work
wt switch feature/auth
# Clean up after merge
wt remove feature/fix-typoYour original workspace remains untouched: uncommitted changes, running processes, terminal history intact. Reuse this worktree for all future reviews.
# Create hotfix worktree (hooks install deps automatically)
wt switch --create hotfix/security
# Fix, commit, and push
gh pr create --fill
# Return to feature work
wt switch feature/auth
# Clean up after merge
wt remove hotfix/security# Create worktrees for each version
wt switch main
wt switch feature/auth
# Run them on different ports (3000, 3001)
# Compare behavior side-by-side in your browserMaintain dedicated worktrees for different purposes:
main for read-only referencework for active developmentreview for PRstest for long-running testsContext switching becomes wt switch work instead of disruptive git operations.
git fetch origin
git rebase origin/mainBoth pnpm and Bun use a global content-addressable cache. When you run pnpm install or bun install in a new worktree, packages are linked from the cache rather than downloaded. Installation is near-instant regardless of project size, eliminating the need to symlink node_modules between worktrees.
When running multiple worktrees simultaneously, each dev server needs its own port. The recommended approach is to set the port as an environment variable in each worktree's .env file (e.g. PORT=3001, PORT=3002). This keeps port assignment explicit and under developer control without requiring any tooling changes.
By default, worktrees share a single local database. Options include template databases (clone a base DB per worktree) and per-worktree database naming conventions.
Dites-nous ce que vous construisez. Nous vous dirons comment nous l'aborderions, ce qu'il faut, et à quelle vitesse nous pouvons avancer.
Nous vous dirons honnêtement si nous sommes le bon choix. Et si ce n'est pas le cas, nous vous orienterons vers quelqu'un qui l'est.