Git worktrees expose the real cost structure of JavaScript repositories
The rule I use with Git worktrees is simple: share caches, not working directories.
Worktrees are one of the cleanest ways to run parallel branches, experiments, and coding-agent tasks. They also expose the ugliest part of large JavaScript repositories: every isolated checkout wants its own dependency universe.
A worktree gives me another working directory attached to the same repository. Instead of constantly switching branches, stashing local changes, or letting an agent mutate my main checkout, I create a separate directory per task.
git worktree add ../wt/refactor-router -b refactor/router
cd ../wt/refactor-router
The win is obvious: branch isolation without cloning the repository again. The trap is less obvious until the repo has a serious frontend dependency tree. Each worktree is a separate project root, so package managers normally create a separate node_modules tree for each one.
repo-main/
node_modules/
wt/refactor-router/
node_modules/
wt/agent-pricing/
node_modules/
wt/bugfix-vite/
node_modules/
The mistake is mutable sharing
The naive optimization is to symlink node_modules from one worktree into another. That feels clever for about ten minutes. Then one branch upgrades Vite, another changes a generated client, a third pulls a native dependency, and the filesystem lies about what each branch actually depends on.
Do not share mutable project directories across worktrees. Share immutable caches. Keep each worktree's working directory honest.
That is the boundary that matters.
The pattern I prefer
The clean pattern is one worktree per task, dependencies installed per worktree, and a content-addressed package store underneath so the machine does not pay the full disk and network cost every time.
This is where pnpm is much better suited than plain npm for large worktree-heavy repositories:
corepack enable
pnpm config set store-dir ~/.pnpm-store
mkdir -p ~/code/product/wt
cd ~/code/product/main
git worktree add ../wt/agent-123 -b agent/123
cd ../wt/agent-123
pnpm install --frozen-lockfile
I still expect to see a node_modules directory in every worktree. That is fine. The important part is that package contents are reused through pnpm's store instead of copied as a completely independent dependency tree every time.
For npm-only repositories
If the repo is still on npm, worktrees are not wrong. They are just more expensive.
Use npm ci instead of casual npm install so the worktree matches the lockfile exactly:
npm ci --prefer-offline
The npm cache avoids redownloading packages, but it does not remove the fundamental disk cost: every worktree gets a physically materialized node_modules tree. For two worktrees, that may be acceptable. For ten agent branches, it gets painful.
The agent workflow
Worktrees are especially useful when multiple coding agents or experiments are in flight. My rule is that no agent works in the main checkout. Every task gets a branch, a worktree, a clean install, and a disposable lifecycle.
main/ # stable human checkout
wt/codex-issue-1842/ # agent task
wt/claude-router-v7/ # agent task
wt/manual-cleanup/ # human experiment
Cleanup matters. Dead worktrees are not harmless. They keep dependency installs, build output, test output, generated files, and stale local state around long after the branch stopped mattering.
git worktree list
git worktree remove ../wt/agent-123
git worktree prune
pnpm store prune
The rule I use
Share the pnpm store, npm cache, Docker layer cache, or remote build cache. Do not share node_modules, dist, generated clients, coverage output, or local database state.
That boundary keeps the workflow fast without destroying correctness.
Related essays
- AI made code cheap. It did not make merge decisions cheap
- Frictionless remote access to a home Linux workstation