A git worktree checks out tracked files only, so node_modules, .env, and build output do not come across. Install per worktree. To make that nearly free, use a package manager with a content-addressed store: pnpm hard-links from one global store, and bun uses clonefile on macOS and hardlink on Linux. To carry the gitignored config files automatically, use a .worktreeinclude file if Claude Code creates your worktrees, git.worktreeIncludeFiles if VS Code does, or three lines of shell if you do. Do not symlink a shared node_modules.
- Worktrees carry tracked files only. Everything gitignored is absent by design.
- pnpm is the highest-leverage fix: one store, hard-linked into every worktree.
- The store must be on the same filesystem, or pnpm copies instead of linking.
.worktreeinclude(Claude Code) andgit.worktreeIncludeFiles(VS Code) copy your.envfor you.- Symlinking one shared
node_modulesfails silently and later. Do not. - Rust, Python, and Go already share their caches. Only the build output is per worktree.
What a new worktree does not have
A worktree is a checkout, and git can only check out what git is tracking. Everything in your .gitignore is, by definition, not tracked, so it is not there. Nothing is broken; the directory is exactly as complete as a fresh clone would be.
A fresh worktree has none of these, and each has a different fix.
| Missing | Why | Fix |
|---|---|---|
node_modules/ | gitignored | Install per worktree, from a shared store |
.env, .env.local | gitignored | Copy on create (see below) |
Build output (dist/, .next/) | gitignored | Rebuild. It is per branch anyway |
.venv/, target/, vendor/ | gitignored | Recreate; the package caches are shared |
| Editor local settings | Usually gitignored | Copy if you care about them |
| Uncommitted changes | Not committed | Nothing. That is the isolation you asked for |
# every ignored path in the current checkout
git status --ignored --short | grep '^!!'
# what that costs, biggest first
git status --ignored --porcelain | sed -n 's/^!! //p' | xargs -I{} du -sh {} 2>/dev/null | sort -rh | head
The source checkout is not the cost
Git objects are shared across every worktree of a repository. Adding a worktree adds one checkout of the tracked files and a few kilobytes of administrative state, not a second copy of your history. On a repository with a long history that difference is enormous, and it is why git worktree add returns in under a second where git clone takes a minute.
# shared: history, objects, packs. Paid once, whatever the worktree count.
du -sh .git
# per worktree: tracked files only
du -sh --exclude=.git --exclude=node_modules .
# per worktree: the number that actually matters
du -sh node_modules
The honest arithmetic is that N worktrees cost one repository, N source checkouts, and N dependency trees. The first is free, the second is usually trivial, and the third is the entire objection. Everything below is about collapsing the third.
pnpm: one store, hard-linked everywhere
pnpm keeps every version of every package once, in a content-addressable store. In pnpm's own words, "all the files are saved in a single place on the disk. When packages are installed, their files are hard-linked from that single place, consuming no additional disk space." A second, third, and tenth worktree install therefore add close to nothing.
npm install -g pnpm
# in each worktree
pnpm install
# where the shared store is
pnpm store path
# reclaim space from versions nothing references any more
pnpm store prune
# check the store has not been corrupted by an editor or a build tool
pnpm store status
Default pnpm store location, as documented in August 2026.
| Platform | Store path |
|---|---|
| Linux | ~/.local/share/pnpm/store |
| macOS | ~/Library/pnpm/store |
| Windows | ~/AppData/Local/pnpm/store |
How each package manager behaves
What a second worktree install actually does on disk.
| Manager | Mechanism | Second worktree costs |
|---|---|---|
| npm | Tarball cache, full extract per project | A full copy, every time |
| yarn (classic) | Tarball cache, full extract per project | A full copy, faster than npm |
| yarn (PnP) | Zip archives, no node_modules at all | Almost nothing, if your tooling supports PnP |
| pnpm | Content-addressed store, hard links | Close to zero, same filesystem |
| bun | clonefile on macOS, hardlink on Linux | Close to zero, with a copy fallback |
bun documents the same idea from the other direction: it "uses the fastest installation method available on the target platform: clonefile on macOS and hardlink on Linux", falling back to copyfile where neither is available. Copy-on-write and hard links solve the same problem, so the practical answer is: if the project is already on npm and you have started running worktrees, moving to pnpm or bun is the change that pays for itself.
Copy the gitignored config automatically
Forgetting .env is the single most common way a new worktree looks broken. The application starts, fails to reach a database, and the failure has nothing to do with worktrees. Three tools now do this copy for you, and they all take the same shape: a list of gitignored paths to bring across.
Claude Code: .worktreeinclude
Put a .worktreeinclude file in the project root. It uses .gitignore syntax, and only files that match a pattern and are gitignored are copied, so tracked files are never duplicated. It applies to every worktree Claude Code creates with git, including subagent worktrees and desktop parallel sessions.
.env
.env.local
config/secrets.json
VS Code: git.worktreeIncludeFiles
The same idea as a setting, applied to worktrees created from the Source Control Repositories view. It takes glob patterns.
{
"git.worktreeIncludeFiles": [
".env",
"node_modules/**"
]
}
Everyone else: three lines of shell
for f in .env .env.local .env.development; do
[ -f "$f" ] && cp "$f" "$dir/"
done
Symlinking, and exactly when it breaks
ln -s ../app/node_modules ../app-auth/node_modules
When a shared node_modules survives and when it does not.
| Situation | Verdict |
|---|---|
| Identical dependencies, identical versions | Works, until one branch changes |
| A branch adds or bumps a dependency | Breaks. Two lockfiles, one tree |
| Native modules, different Node versions | Breaks confusingly. ABI mismatch |
Tooling that writes into node_modules | Cross-contaminates. Prisma, Next, .bin shims |
| Monorepo with workspace links | Do not. The links point at the other worktree |
| Two agents installing at once | Do not. Concurrent writes to one tree |
The same question in other ecosystems
Node is the loud case because node_modules is enormous and per project. Most other toolchains already put the expensive part in a machine-wide cache, so a worktree costs you the build output and nothing else.
| Ecosystem | Shared already | Per worktree |
|---|---|---|
| Rust | Registry and sources under ~/.cargo | target/ |
| Go | Module cache and build cache | Nothing meaningful |
| Python (uv) | Global cache at ~/.cache/uv | .venv/, linked from the cache |
| Python (pip) | Wheel cache | .venv/, extracted per environment |
| Java (Gradle, Maven) | ~/.gradle, ~/.m2 | Build directories |
uv has the same same-filesystem condition pnpm does, and states it directly: "It is important for performance for the cache directory to be located on the same file system as the Python environment uv is operating on. Otherwise, uv will not be able to link files from the cache into the environment and will instead need to fallback to slow copy operations."
Automate it once and stop thinking about it
Worktrees are only worth it if creating one is free. If it takes four commands and a two-minute install, you will stop doing it inside a week and go back to one directory and one agent.
#!/usr/bin/env bash
# wt <branch> - create a worktree, carry the config, install.
set -euo pipefail
name="$1"
repo="$(basename "$PWD")"
dir="../${repo}-${name//\//-}"
git worktree add -b "$name" "$dir"
for f in .env .env.local .env.development; do
[ -f "$f" ] && cp "$f" "$dir/"
done
cd "$dir"
if [ -f pnpm-lock.yaml ]; then pnpm install --prefer-offline
elif [ -f bun.lock ] || [ -f bun.lockb ]; then bun install
elif [ -f yarn.lock ]; then yarn install
elif [ -f package-lock.json ]; then npm ci
fi
echo "ready: $dir"
Questions people ask
Because a worktree checks out tracked files only, and node_modules is gitignored, so git does not have it to check out. Run your install command in the new worktree, or use a package manager with a shared content-addressed store so that install is nearly free.
With pnpm, effectively yes: one global store hard-linked into each worktree, costing almost no extra disk. Symlinking a single node_modules directory is a different thing and only works when the branches have identical dependencies, failing silently when they do not.
One shared repository, one source checkout per worktree, and one dependency tree per worktree. The first two are cheap because git objects are shared. The third is the whole cost, which is why the package manager decides the answer.
With Claude Code, add a .worktreeinclude file listing the paths; it uses gitignore syntax and only copies files that are both matched and gitignored. In VS Code, set git.worktreeIncludeFiles to a list of globs. Otherwise, copy them in your worktree-creation script.
If you run several worktrees regularly, it is the single highest-leverage change available, because it turns the main objection into a rounding error. Switch the package manager first and confirm CI is green before you start creating worktrees.
Almost always because the store is on a different filesystem from the worktrees. Hard links only work within one filesystem, so pnpm falls back to copying. Check pnpm store path and make sure it is on the same volume as the checkouts.
Create the venv per worktree; uv links it from a global cache when the cache is on the same filesystem. Keep target/ per worktree too: cargo locks the build directory, so one shared CARGO_TARGET_DIR makes parallel builds queue behind each other.
Yes. Commits, branches, tags, and remotes are shared, which is why adding a worktree is close to instant while cloning is not. Only working files, HEAD, and the index are per worktree.
Sources
Every figure above was read from these pages on August 2026. Vendors reprice without notice; if you find a stale number, tell us.