◉CLAUDE LABJP
●2.1.302 — Fork subagents are now on by default in claude -p, the Agent SDK and the desktop app. Turn them off with CLAUDE_CODE_FORK_SUBAGENT=0 (10/16)●2.1.301 — Unattended retries now stop on their own when a usage limit is hit instead of waiting (10/15). Latest is 2.1.304 (10/19)●SONNET 4.5 — Retires on the Claude API on 11/30, 40 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.302 — Fork subagents are now on by default in claude -p, the Agent SDK and the desktop app. Turn them off with CLAUDE_CODE_FORK_SUBAGENT=0 (10/16)●2.1.301 — Unattended retries now stop on their own when a usage limit is hit instead of waiting (10/15). Latest is 2.1.304 (10/19)●SONNET 4.5 — Retires on the Claude API on 11/30, 40 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/Claude Code
⟐ Claude Code/2026-04-02Intermediate

Running Claude Code Per Worktree: Notes From Actual Use

Implementation notes on splitting branches into Git worktrees and launching Claude Code per directory. What is shared and what is isolated, measured on a real repository, plus the untracked files nobody warns you about.

Claude Code270worktree3parallel developmentGit5workflow41

✦ Premium Article

On a Friday evening I got word that a shipped app was crashing for some users. Meanwhile my working tree was half-way through a feature for a different app, in no state to commit. Switch branches and the unfinished work goes into limbo; stay put and I cannot start the fix. That fork in the road is the part of solo development that genuinely makes my stomach tighten.

From that day I started splitting the working tree itself instead. Add one more directory with Git worktrees, launch a second Claude Code session inside it, and treat the change as moving to another desk rather than swapping the contents of the one desk I have.

What follows is a set of notes I wrote while checking each behavior on Git 2.34 in front of me. Knowing what is separated and what quietly stays shared prevents far more accidents than memorizing another list of commands.

What a Worktree Splits, and What It Shares

Adding a worktree gives you another directory. It does not give you another repository. Miss that and you will be surprised when an operation in one worktree shows up in the other.

The .git entry in an added worktree is a text file, not a directory. It holds a pointer back to the parent and nothing else.

$ git worktree add ../wt-a feature/a
Preparing worktree (checking out 'feature/a')
HEAD is now at dc6dc31 a
 
$ file ../wt-a/.git
../wt-a/.git: ASCII text
 
$ cat ../wt-a/.git
gitdir: /home/me/demo/.git/worktrees/wt-a

The fastest way to feel how much is shared is the stash. Something shelved in the main working tree is visible from the added one.

# Shelve in the main working tree
$ git stash
Saved working directory and index state WIP on main: c88bbda init
 
# List it from the added worktree
$ git -C ../wt-a stash list
stash@{0}: WIP on main: c88bbda init

Local config behaves the same way. Write it on one side and read it from the other.

$ git config --local demo.key mainside
$ git -C ../wt-a config --get demo.key
mainside

Here is the full picture as I measured it.

ItemPer worktree?Notes
Files in the working directoryIsolatedThe whole point of the feature
The index (staging area)IsolatedKept at .git/worktrees/<name>/index
HEAD and the checked-out branchIsolatedThis is what lets you hold two branches open
In-progress rebase, merge, bisectIsolatedOne side can be mid-conflict while the other stays calm
Commits, branches, tagsSharedOne object database for all of them
StashSharedAnything shelved is visible everywhere
.git/config (--local)SharedPer-worktree values need extensions.worktreeConfig
Hooks and remotesSharedA hook you install applies to every worktree

Only the desk in front of you is separate. The repository's memory stays single. Internalize that one line and the "why can the other worktree see my stash" moment never happens.

A New Worktree Does Not Come With a Working Environment

This is where I lost the most time on day one. git worktree add materializes what is committed, and nothing that .gitignore covers.

$ ls -a ../wt-a
.  ..  .git  .gitignore  src
 
node_modules:                missing
.env.local:                  missing
.claude/settings.local.json: missing

The new worktree is a complete repository and an empty development environment. Dependencies, local environment variables, and your machine-local tool settings all have to be carried over deliberately. My first attempt ran npm run dev right after git worktree add, watched it fail, and spent twenty minutes suspecting Git. The dependencies simply were not installed.

So creating the worktree and provisioning it now live in one script.

#!/usr/bin/env bash
# wt-new.sh — create a worktree and carry over what Git will not
set -euo pipefail
 
BRANCH="$1"                          # e.g. hotfix/login-timeout
DIR="../wt-${BRANCH//\//-}"
ROOT="$(git rev-parse --show-toplevel)"
 
git fetch -q origin main
git worktree add "$DIR" -b "$BRANCH" origin/main
 
# Copy untracked-but-needed files by name, never in bulk
for f in .env.local .claude/settings.local.json; do
  [ -f "$ROOT/$f" ] || continue
  mkdir -p "$DIR/$(dirname "$f")"
  cp "$ROOT/$f" "$DIR/$f"
done
 
( cd "$DIR" && npm ci --prefer-offline )
echo "ready: $DIR"

The explicit list matters. Mirroring everything .gitignore hides would happily duplicate production keys and credentials for unrelated apps into a fresh directory. Whatever gets carried across, gets named. It is a small tax I would rather keep paying.

CLAUDE.md is tracked, so it arrives on its own. .claude/settings.local.json is not, so it never does. That asymmetry leads straight into the next section.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦A measured breakdown of what worktrees share (stash, config, hooks) and what they isolate (working tree, index, HEAD)
✦Why node_modules, .env.local and .claude/settings.local.json never follow a new worktree, and a script that fills the gap
✦The full path from spinning up a hotfix worktree to tearing it down without leaving prunable entries behind
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

⟐ Claude Code2026-10-20
Counting the 267 Broken Links My Build Was Silently Stripping — and Why I Skipped Fuzzy Auto-Repair
An MDX build was quietly removing broken internal links. Here is the audit script that lists them, why I decided against slug-similarity repair, and how to set a baseline so the count never grows.
⟐ Claude Code2026-10-20
You Stepped Away and Came Back to a Summary — Spotting Idle Compaction and Keeping the Details You Can't Lose
Since v2.1.299, Claude Code compacts a long conversation after about 54 idle minutes. Here is how to tell it happened, how to tune or switch it off, and how to move the assumptions you can't afford to lose into CLAUDE.md.
⟐ Claude Code2026-10-19
The Page Was 90px Too Wide at 375px, and the Culprit Never Showed Up in querySelectorAll
When a pseudo-element causes horizontal overflow on phones, scanning elements finds nothing. A probe script that confirms the culprit by diff, and overflow fixes that keep sticky intact, with measurements.