- AI
- Claude-Code
- tooling
- git
- worktrees
- VSCode
Branches Were Built for One of You. Agents Aren't.
Branch-switching is a single-threaded habit. Once Claude Code sessions run in parallel and PR reviews happen alongside your own work, git worktrees stop being optional. The tips, alternatives, and editor tooling that actually make the discipline stick.
Tuesday morning, three things are happening at once. A Claude Code session is twelve minutes into rewriting your subscription FSM on feature/INT-3004. A second session is reviewing a teammate’s PR on pr-247 and writing review notes you’ll skim before approving. You yourself want to ship a one-line config fix to main because the staging environment is down and somebody needs to do it now.
In the old world this is impossible without violence. You stash the FSM work, lose the editor state, switch the branch, do the fix, switch back, unstash, re-resolve the half-broken merge, re-run pnpm install because the lockfile moved, and twenty minutes later you’ve shipped one line of YAML and disturbed two agents mid-thought. The PR review never happened.
The new world makes this trivial. Three directories, three branches, three agents, zero collisions. git checkout doesn’t enter the picture.
The thing nobody quite admits is that the old workflow wasn’t bad — it was matched to a constraint that quietly disappeared. Branch-switching assumes one developer, one working tree, one task at a time. Agents broke that assumption. Most of us are still using the tools as if they hadn’t.
The single-threaded assumption
git checkout and modern git switch were designed around a human attention budget. One person, one keyboard, one focus. The branch is a state your one working directory is currently in, like which window has focus on your desktop. You hold one branch at a time because you only think about one thing at a time.
That model leaks the moment your effective parallelism exceeds one. The classic leak is reviewing a PR: you gh pr checkout 247, the tool replaces your working state, you lose your in-progress work, and you’ve now coupled “I want to look at someone else’s code” to “I have to abandon mine.” Stashing papers over this but it’s a workaround, not a fix. The stash is a debt you have to repay, often during the worst possible interruption.
Pre-agent, you could absorb this. You only had one head; one branch was the rate limit anyway. Pair programming was the parallelism, and the partner came with their own laptop.
Agents change the math. A Claude Code session is, for practical purposes, another developer working on your codebase. You can run two. You can run five. Each one needs its own consistent view of the tree, its own commit lineage, its own state that doesn’t shift under it because you decided to look at something else. Sharing one working directory between two agents is the same mistake as sharing one working directory between two humans — except faster, because agents type faster than humans.
The branch-switching habit isn’t just inefficient under agents. It’s actively dangerous. An agent that comes back to a working directory and finds it on a different branch than it left it on will commit to the wrong branch, edit files it doesn’t recognize, or — most insidiously — keep going as if nothing changed and silently corrupt its own context.
The answer is older than the problem
git worktree shipped in Git 2.5 in 2015. It’s been quietly capable for a decade. Most developers know it exists; few use it daily. That was a defensible position when you had one head. It isn’t anymore.
The mental model is one line: a worktree is a second checked-out copy of your repository, sharing the same .git database but living in its own directory with its own HEAD, index, and working tree.
What this buys you:
| Thing | Old way | Worktree way |
|---|---|---|
| Switch to another branch | git checkout clobbers your working state | cd ../project-other-branch |
| Review a colleague’s PR | Stash, fetch, checkout, run, stash pop | New worktree, new window, leave it alone |
| Ship a hotfix mid-feature | Stash + commit-WIP + checkout main + fix + push + checkout back + unstash | Fresh worktree on main, fix, push, delete the worktree |
| Run two agents in parallel | Impossible | One agent per worktree, full isolation |
| Compare two branches side-by-side | Two git diff invocations | Two editor windows |
Branches stop being states your one directory is in. They become directories. That sentence is the entire pivot.
What stays shared, and what doesn’t, is worth keeping straight from the start:
| Shared across worktrees | Per worktree |
|---|---|
The .git object database, refs, reflog, hooks, stashes, config (mostly) | HEAD, index, working files, untracked files, ignored files |
| Branches and tags themselves | Which branch is currently checked out |
| Submodule URLs and pointers | Each worktree’s submodule checkout |
Fetched remote state (one git fetch updates them all) | In-progress rebases, merges, cherry-picks (each worktree has its own .git/worktrees/<name>/ state dir) |
Two consequences worth internalising. First, git fetch in any worktree refreshes refs everywhere — you don’t need to fetch per worktree. Second, an in-progress rebase in worktree A does not block a rebase in worktree B; their merge state is isolated. Two agents can rebase concurrently without stepping on each other.
A small primer
Three commands cover 90% of the workflow.
git fetch origin
git worktree add ../project-INT-3004 -b feature/INT-3004 origin/main # create
git worktree list # inspect
git worktree remove ../project-INT-3004 # destroy
The other commands matter when something has gone sideways or you want lifecycle control. git worktree move <old> <new> relocates a worktree (use this rather than mv, otherwise you’ll need git worktree repair to fix the back-pointers). git worktree lock <path> --reason "..." prevents pruning when a worktree sits on a removable disk or a slow network mount. git worktree prune clears stale admin entries after you rm -rf’d a worktree directory outside Git. git worktree repair re-links worktree metadata after you moved the primary repo.
A handful of forms cover the variants you’ll actually want:
git worktree add ../project-feature-x -b feature-x # new branch from HEAD
git worktree add ../project-hotfix -b hotfix main # new branch from main
git worktree add ../project-review feature-y # existing local branch
git worktree add ../project-pr-247 -b pr-247 origin/their-branch # remote branch
git worktree add --detach ../project-v1.2 v1.2.0 # inspect a tag read-only
Three rules to internalize.
First: each worktree has its own node_modules, .venv, .next, target, __pycache__. This is a feature, not a bug. Symlinking node_modules between worktrees breaks builds in subtle ways (peer deps, native bindings, pnpm hardlink layout). Just reinstall. The pnpm store, uv cache, and Go module cache deduplicate the heavy bytes at the user level; you’re only paying for linking time, not for re-downloads. Disk is cheap, hours of debugging shared-state corruption are not.
Second: stashes are shared, but you mostly stop stashing. Stashes live in the common refs database, so git stash push in worktree A is visible from git stash list in worktree B. The reason to stash — “I need to switch context but my changes aren’t ready to commit” — evaporates when you can just leave the changes where they are and cd somewhere else. Stashes become an emergency tool, not a daily one. Prefer a WIP commit you can amend later; it survives stash corruption and is visible in git log.
Third: same branch can’t be checked out twice (and that’s fine). Git refuses by default because two working trees committing to the same branch is a race condition with no winner. The escape hatch is git worktree add --force --detach <path> <commit> for read-only inspection at a specific commit. Don’t reach for --force to enable concurrent development on one branch; you will regret it. The right answer is two branches that merge later, not one branch in two places.
A few quieter caveats that bite people once and then never again:
- Hooks are shared.
.git/hooks/lives in the primary’s.gitdirectory, so the same hooks run in every worktree. A hook script that assumes a specific path will misbehave. Inside hooks, derive the active worktree root fromgit rev-parse --show-toplevelrather than hardcoding paths. - Submodules are per worktree. After
git worktree add, rungit submodule update --init --recursiveinside the new worktree. Each worktree maintains its own submodule checkout. This multiplies disk usage but isolates submodule state correctly. - Per-worktree git config exists, if you need it. Most config is fine global. Occasionally you want a different signing key, GPG identity, or email per worktree (open-source vs day-job in adjacent directories). Enable once:
git config extensions.worktreeConfig true, thengit config --worktree user.email "..."in the worktree where it differs. Without enabling the extension,--worktreefails.
For everything else — git worktree --help is short and accurate.
Recovery: things that look broken but aren’t
Worktrees produce a few error messages that look scarier than they are.
fatal: 'feature-x' is already checked out at '/.../project-feature-x' — Git is doing its job. Use the existing worktree (cd to it), or remove the stale one (git worktree remove ...), or, as a last resort, git worktree add --force for a second concurrent checkout. The first two are nearly always right.
fatal: '../project-feature-x' contains modified or untracked files, use --force to delete it — inspect before forcing. cd in, git status, then commit, stash, or accept the loss with --force.
You rm -rf’d a worktree directory by accident. Branch and commits are safe in the shared .git. Run git worktree prune to clear the stale admin entry; that’s it.
You moved the primary repo and now linked worktrees are broken. cd to the new primary location and run git worktree repair. It re-links the .git files in each linked worktree.
You think you lost a commit. The reflog is shared across worktrees, so git reflog from any worktree shows it. Recovery is a one-liner: git checkout -b recovered <sha>.
Organization patterns: pick one and stick to it
Switching organization patterns mid-stream is what burns the day. Decide once per machine.
| Pattern | Layout | When it fits |
|---|---|---|
| Sibling directories | ~/code/project, ~/code/project-feature-x, ~/code/project-hotfix | Default. Simplest. Works for the 80% case where you have one or two worktrees alongside the primary. |
| Dedicated worktrees folder | ~/code/project/{main,feature-x,hotfix} | When one project produces many worktrees and you want them all under one umbrella. Setup: git clone <url> project/main; cd project/main; git worktree add ../feature-x .... |
| Bare repo + worktrees | ~/code/project/.bare, ~/code/project/{main,feature-x,hotfix} | When you mostly think in branches, not in “one checkout plus diversions.” Setup: git clone --bare <url> ~/code/project/.bare; echo "gitdir: ./.bare" > ~/code/project/.git; cd ~/code/project && git worktree add main main. Cleanest pattern for heavy worktree users. |
There’s no objectively right pattern. The bare-repo pattern eliminates the awkwardness of a “primary worktree” sitting next to its siblings — every directory under ~/code/project/ is equal — and is the most consistent if you run five-plus worktrees per project. The sibling pattern requires zero setup and matches how most cloned repos already live on disk. Start with sibling; graduate to bare-repo if you find yourself reaching for it.
Stacked PRs
Worktrees pair naturally with stacked PRs — chains of small, dependent PRs that review and merge independently. Each branch in the stack lives in its own worktree:
git worktree add ../project-INT-3004-a -b feature/INT-3004-a origin/main
git worktree add ../project-INT-3004-b -b feature/INT-3004-b feature/INT-3004-a
git worktree add ../project-INT-3004-c -b feature/INT-3004-c feature/INT-3004-b
When feature/INT-3004-a updates, rebase b onto it, then rebase c onto b. Tools like git-stack, Graphite, or Sapling automate the cascade. The pairing matters because stacked PRs are exactly the workflow that breaks under single-directory development — three dependent branches in one working directory is a stash-juggling nightmare; three dependent branches in three worktrees is just three directories you cd between.
The parallel-agent pattern
Here is the workflow worktrees were waiting to enable.
You’re working on feature/INT-3004. A colleague drops PR #247 in Slack with “can you take a look?” An incident pings about a production config that needs a one-line patch.
Old workflow: pick one. The other two wait.
New workflow:
# Terminal 1 (already running)
cd ~/code/project-INT-3004
claude # implementing the FSM
# Terminal 2 (new tab)
cd ~/code/project
git fetch origin pull/247/head:pr-247
git worktree add ../project-pr-247 pr-247
cd ../project-pr-247
claude # "review this branch, list concerns, no fixes"
# Terminal 3 (new tab)
cd ~/code/project
git worktree add ../project-hotfix -b hotfix/staging-config origin/main
cd ../project-hotfix
# you handle this one yourself — one line of YAML
Three things now happen simultaneously. The FSM agent is committing on its branch and seeing only its tree. The review agent is reading PR #247’s tree and writing notes that won’t touch your work. You’re shipping the config fix. Nobody steps on anybody.
When the hotfix lands, git worktree remove ../project-hotfix && git branch -d hotfix/staging-config. Five seconds. No stash to recover, no merge to redo, no pnpm install to re-run, no editor state to rebuild.
The Claude Code half of this matters. Claude identifies projects by absolute working directory path, so each worktree is a separate project context: separate conversation history (~/.claude/projects/<encoded-path>/), separate per-project permissions, separate trust prompts on first run. Two Claude sessions in two worktrees can’t see each other. They can’t accidentally commit to each other’s branches. They can’t load each other’s context. The isolation is real and it’s exactly the boundary you need.
A couple of details that come up once you start running this pattern in earnest:
CLAUDE.mdlives with the branch. BecauseCLAUDE.mdis tracked, changes ride along with merges. Worktrees on different branches can have meaningfully different instructions, and you can iterate onCLAUDE.mdon a branch without affecting other worktrees’ Claude sessions. Treat it like code.- User-scope MCP servers are shared; project-scope MCPs travel with the branch. Things you always want available (Atlassian, Sentry, Gmail) belong in user scope (
claude mcp add --scope user ...). Project-specific MCPs belong in committed.mcp.jsonso they ride with the branch. Personal experiments live in gitignored.claude/settings.local.jsonand stay per-worktree. - First run in a new worktree prompts for trust and permissions. The absolute path is new, so Claude treats it as a new project — by design. Pre-approve common tools in a committed
.claude/settings.jsonso the setting follows the branch into every worktree.
Anthropic shipped a flag for the throwaway case. claude --worktree feature-auth (or -w shorthand) launches a Claude session in an isolated worktree with its own branch, no manual git worktree add required. Same primitive underneath, less ceremony when you spin up a one-shot exploration.
The deeper unlock is agent fan-out. Three subtasks of one epic, three worktrees, three Claude sessions, three PRs. Merge sequentially or stack them. You orchestrate from the primary worktree; the work happens elsewhere. The primary becomes a control plane, not a work surface — a habit shift worth making explicit.
What changes in VS Code and VSCodium
If you use a graphical editor, the worktree story splits in two: how the editor handles multiple checkouts of one repo, and what tooling exists to manage them without leaving the editor. Both got materially better in the second half of 2025.
One window per worktree is the right default. VS Code identifies a window by its top-level folder. Open a worktree at ~/code/project-INT-3004 and you get a window with its own integrated terminal (cwd is the worktree root), its own Source Control panel, its own Claude Code extension panel, its own extension host process. Two windows on two worktrees can’t collide. The branch indicator in the status bar always reflects that window’s worktree. This is what you want for parallel agentic work.
The alternative — multi-root workspaces (File > Add Folder to Workspace or code --add ../feature-branch) — puts multiple worktrees in one window:
| Multi-root workspace | Separate windows |
|---|---|
| One extension host (lower RAM) | One extension host per window (more RAM) |
| Single search across all worktrees | Search scoped per window |
| Side-by-side diff/compare across branches | Diff requires switching windows or git diff |
| Single Source Control sidebar listing all repos (noisy) | Source Control scoped to the active worktree |
| One Claude Code panel for the whole workspace — agents fight over context | One panel per window — each agent has its own |
| Terminal cwd defaults to the most recently focused folder (easy to misfire) | Terminal cwd is the worktree root, unambiguous |
Reserve multi-root for the moments a human wants a side-by-side diff. For agentic work, separate windows is the right default.
Built-in worktree commands shipped in VS Code 1.103 (August 2025). The Command Palette now exposes Git: Create Worktree, Git: Open Worktree in New Window, Git: Open Worktree in Current Window, and Git: Delete Worktree. The Source Control > Repositories view adds right-click parity. For 90% of the workflow, you don’t need any extension at all — and these commands work identically in VSCodium, because they’re upstream code that VSCodium inherits on each rebase.
For the remaining 10%, the extension ecosystem has matured. Here’s the honest landscape — none of these are required, all of them solve a specific friction:
| Extension | Marketplace ID | What it does |
|---|---|---|
| GitLens | eamodio.gitlens | Dedicated Worktrees view in the sidebar; Worktrees node in the Commit Graph; create worktrees from commits, branches, or remote branches directly. Free tier covers worktree management. |
| Git Worktree Manager | jackiotyu.git-worktree-manager | Status-bar picker; quickly switch between worktrees; works across repos. Also published on Open VSX. |
| Git Worktrees | GitWorktrees.git-worktrees | Interactive worktree operations wrapper from alexiszamanidis. |
| Git Worktree | PhilStainer.git-worktree | Lightweight add/list/remove. |
| Worktree Tools | padjon.vscode-worktree-tools | Init + main-workdir sync for linked worktrees. |
| Git Worktree Menu | CodeInKlingon.git-worktree-menu | Pop-up menu for view/switch/create/remove. |
| Git Worktree View | kaih2o.worktrees | Sidebar tree view. |
| Workforest | 8ctavio.workforest | Visualizes worktrees graphically and switches between them. |
| Git Worktree Assistant | derSicking.git-worktree-assistant | Creation wizard with templates. |
| Git Worktree Window Title | garytyul.vscode-worktree-window-title | Injects the worktree name/path into the window title automatically. |
Pick at most one or two. GitLens is the safe pick if you already use it; Git Worktree Manager is the safe pick if you don’t. Workforest is worth a look if you mentally need the spatial map. The rest fill niches.
The “which window am I in” problem is real. With three worktrees open you will, at some point, type a command into the wrong window. Three fixes, in order of preference:
-
Set
window.titleonce in a committed.vscode/settings.json:{ "window.title": "${rootName} (${activeRepositoryBranchName}) ${dirty}— ${activeEditorShort}" }${activeRepositoryBranchName}resolves to the current branch in each worktree.${dirty}marks unsaved changes. One setting, every window self-identifies. -
Use a multi-root workspace
.code-workspacefile with"name"set on each folder entry to override Explorer labels. -
Install
garytyul.vscode-worktree-window-titleif you don’t want to maintainwindow.titleacross repos.
On Windows, taskbar grouping can hide window titles. If you keep confusing windows there, launch separate VS Code instances with --user-data-dir <path> per worktree — it’s heavy and gives each window its own settings + extension state, so only do it as a last resort.
Opening worktrees from the CLI is the fastest path once you’ve created them:
code --new-window ../project-INT-3004 # VS Code
codium --new-window ../project-INT-3004 # VSCodium — identical flags
code -r ../project-other # reuse current window
code --add ../project-other # add as folder to multi-root workspace
If code (or codium) isn’t on your PATH, run “Shell Command: Install ‘code’ command in PATH” from the Command Palette once. Without -n/--new-window, behaviour depends on window.openFoldersInNewWindow — set it to "on" if you want CLI invocations to always spawn a new window.
Per-worktree settings are mostly a non-issue. .vscode/settings.json, launch.json, tasks.json are normal tracked files — they live with the branch. Two worktrees on two branches see the two branches’ versions, as they should. Two traps to know:
- Hardcoded absolute paths in
launch.jsonbreak across worktrees. Use${workspaceFolder}for everything that resolves relative to the worktree root. - If
.vscode/is gitignored on your project, newly created worktrees won’t have it. Either commit a minimal shared.vscode/settings.jsonandextensions.json(recommended-extensions list), or run a post-create script that copies your shared.vscode/template into the new worktree.
VSCodium has one substantive caveat: Open VSX coverage. VSCodium defaults to the Open VSX registry rather than Microsoft’s marketplace, because Microsoft’s terms restrict their marketplace to Microsoft products. Coverage is partial — as of 2024 figures, roughly 6% of MS Marketplace extensions are mirrored to Open VSX (around 3,500 of 57,000+). For worktree work specifically:
- GitLens is on Open VSX — fine.
jackiotyu.git-worktree-manageris on Open VSX — fine.- Smaller third-party worktree extensions are hit-or-miss — check Open VSX before assuming.
- The Claude Code VS Code extension’s Open VSX status has been inconsistent. If you need it on VSCodium, download the
.vsixfrom Anthropic’s GitHub releases and install it manually via “Extensions: Install from VSIX”. Verify current marketplace coverage at publish time.
Running multiple Claude Code sessions in one editor. The Claude Code extension officially supports parallel sessions: “Open in New Tab” or “Open in New Window” from the Command Palette spawns independent sessions, each with its own context. Sessions are workspace-scoped — a session started in worktree A cannot be resumed in worktree B’s window, which is the correct isolation. Across separate windows (one per worktree) you get full isolation; within one window you get tabbed sessions sharing the workspace.
The deep-link form is occasionally useful for scripting: vscode://anthropic.claude-code/open?prompt=...&session=... opens or resumes a session in the current VS Code instance. The session parameter requires the session to belong to the workspace currently open, so it composes correctly with the worktree model.
One unverified concern from forums (not in official docs): running many Claude Code instances against the same Anthropic account can hit per-account rate/concurrent-request limits during heavy tool use. The exact threshold isn’t documented. If you fan out to four-plus parallel agents and start seeing throttling, that’s the likely cause — slow the fan-out, not the worktrees.
Monorepos: file watcher and search limits. Each worktree adds another file tree under inotify watch. On Linux, three or four worktrees on a large monorepo can blow past the default and produce cryptic “ENOSPC: System limit for number of file watchers reached” errors. The honest fix is to raise the kernel limit once:
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
VS Code’s files.watcherExclude helps but is not always honored (see also #50408) — the kernel-limit raise is the durable fix. Configure search.exclude separately (different setting, different behaviour) to keep search fast across many worktrees:
{
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/.git/worktrees/**": true,
"**/node_modules/**": true,
"**/dist/**": true,
"**/.next/**": true,
"**/.turbo/**": true,
"**/coverage/**": true
},
"search.exclude": {
"**/node_modules": true,
"**/dist": true,
"**/.git/worktrees": true
}
}
One organizational tip: place worktrees outside the main repo directory (~/code/project-worktrees/feature-x rather than ~/code/project/.worktrees/feature-x). Keeps the parent repo’s watchers and search from picking up sibling worktrees, and prevents agents from accidentally cd-ing into the wrong tree.
Aliases and automation worth setting up once
The discipline gets easier when the commands shorten. A few one-time setups pay back fast.
Shell aliases for the daily commands:
# ~/.zshrc or ~/.bashrc
alias gwl='git worktree list'
alias gwa='git worktree add'
alias gwr='git worktree remove'
alias gwp='git worktree prune'
A git alias that creates a worktree off origin/main in one shot:
# ~/.gitconfig
[alias]
wt = worktree
wtnew = "!f() { git fetch origin && git worktree add \"../$(basename $(pwd))-${1//\\//-}\" -b \"$1\" origin/main; }; f"
Usage: git wtnew feature/INT-3004 — creates ../project-feature-INT-3004 with a fresh branch tracking origin/main.
A fuzzy-finder for jumping between worktrees with fzf (worth its weight in saved keystrokes):
wt() {
local dir
dir=$(git worktree list --porcelain | awk '/^worktree /{print $2}' | fzf) || return
cd "$dir"
}
A bulk-update for refreshing every worktree against its upstream:
git-pull-all() {
git fetch --all --prune
git worktree list --porcelain | awk '/^worktree /{print $2}' | while read -r wt; do
echo "→ $wt"
git -C "$wt" pull --rebase --autostash || echo " (skipped)"
done
}
A cleanup pass that removes worktrees for branches already merged into main:
git-clean-merged() {
local default=${1:-main}
git fetch --prune
git branch --merged "origin/$default" | grep -v "^\*\| $default$" | while read -r br; do
br=$(echo "$br" | xargs)
local wt
wt=$(git worktree list --porcelain | awk -v b="$br" '
/^worktree /{w=$2}
$1=="branch" && $2=="refs/heads/" b {print w; exit}
')
if [ -n "$wt" ]; then
git worktree remove "$wt" && echo "removed worktree $wt"
fi
git branch -d "$br" && echo "deleted branch $br"
done
}
Run git-clean-merged weekly. It costs nothing and prevents the slow accumulation of stale worktrees.
The habits that have to change
Adopting worktrees is technically trivial. The hard part is unlearning a decade of single-threaded reflexes. A few that have to be retrained explicitly:
Stop reaching for git checkout to “look at” another branch. If you need to look at it for more than thirty seconds, make a worktree. The cost is one command; the saving is your in-progress state and your agent’s context.
Stop stashing. Stashes solve a problem worktrees no longer have. The mental shift from “let me preserve this work so I can switch” to “let me leave this work where it is and go elsewhere” is the same shift, deeper.
Name worktrees consistently. <project>-<ticket-or-topic> is sortable, searchable, and survives ls. ~/code/project-INT-3004, ~/code/project-pr-247, ~/code/project-hotfix-auth. Future-you needs to grep this list at midnight.
Treat the primary worktree as control, not work. The directory holding the real .git should be where you orchestrate from — listing worktrees, deciding what to spawn, cleaning up — not where you write code. Write code in the primary and forget to make a worktree for the next feature, and you’ll be back to single-threaded reflexes within a week.
Delete worktrees aggressively. After a PR merges, git worktree remove and git branch -d immediately. Stale worktrees accumulate disk and cognitive load. The good news: removing is fast and reversible — your branch’s commits remain in the shared .git; only the working directory dies.
Watch the absolute path in your prompt. When you have four worktrees open, the shell prompt is the only thing telling you which one is which. Most prompt frameworks show the basename plus branch; some only the branch. Make sure yours shows both, and make sure your tmux/Warp/Ghostty tabs are named after the worktree, not the project.
Audit disk usage periodically. Ten worktrees with node_modules can hit 5–10 GB. git worktree list | awk '{print $1}' | xargs -I{} du -sh {} shows you where the bytes are. Combine with git-clean-merged and you’ll rarely have more worktrees than you need.
When not to use worktrees
Worktrees are not free. Skip them when:
- You’re making a one-line fix and will be back on your branch in 60 seconds.
- The project’s install step is genuinely slow and unavoidable (huge monorepo, no caching, no pnpm store). At some point reinstalling becomes more expensive than stashing.
- The repo uses absolute paths in committed config that break when the directory name changes (rare, but it happens with poorly-written build scripts).
- You only ever work on one branch at a time, never review others’ PRs locally, and have no parallel agents.
For everything else — and especially for any workflow that includes parallel agentic work — worktrees beat git checkout. The branch-switching tax is real, and worktrees eliminate it.
The deeper shift
For most of the last decade, the cost of being “context-switched” was paid by you, the human. Stashing was an irritation. Re-running pnpm install was a coffee break. Losing five minutes per branch flip felt like a tax you couldn’t avoid because the alternative — caring about working directory hygiene — was worse.
Agents flip the cost. They don’t drink coffee while pnpm install re-runs. They don’t recover gracefully from a working tree that switched under them. They don’t politely tolerate stashes that mutate the codebase mid-thought. The fixed cost of a context switch, paid once by a human, becomes a per-agent cost that compounds with parallelism.
This is the AI-reverses-our-world pattern in miniature. Workflows we tolerated because the cost was bounded by human serial-attention become unworkable once the bound is gone. The fix is rarely the AI tool itself; it’s the underlying discipline the AI exposed. git worktree was always the right answer to “two streams of work on one repo.” We mostly didn’t have two streams. Now we do.
Type safety became table-stakes when the cost of undefined is not a function got too high to ignore. Test coverage became table-stakes when “ship and pray” stopped scaling. Worktree-per-task is the next quiet one — once you’ve watched a Claude session ruin twenty minutes of work because you gh pr checkout-ed a colleague’s branch over the top of it, the old workflow stops feeling acceptable.
The tools are old. The habit shift is new. Worth making.
If your agentic workflow keeps colliding with itself — agents stepping on agents, PRs that can’t be reviewed without abandoning what you were doing, branches piling up because the cost of switching is too high — that’s a conversation I’m happy to have.