Worktrees isolate files, not your database or ports
Git worktrees isolate files only: parallel Claude Code sessions still share one local dev database, one set of localhost ports, and one credential store, so migrations from two branches leave a schema matching neither and no git conflict appears.
The official documentation describes the mechanism this relies on. The technique itself is community practice.
What this costs you if it is wrong
The fixes are stack-specific and untested here: the source writeup is .NET (LocalDB, launchSettings.json, dotnet ef), so adapt rather than paste. Two of the remedies can do damage if applied carelessly — pointing a worktree at "its own" database means the data in your main dev database is not there, and `.worktreeinclude` copies real secrets (`.env`, `config/secrets.json`) into every new worktree directory, multiplying the number of places a credential sits on disk and in any backup or search index that reaches `.claude/worktrees/`.
You have two Claude sessions running: one in .claude/worktrees/feature-auth,
one in your main checkout. The file edits stay apart — that is what worktrees
are for. Then the second session's dev server refuses to come up, or a test
suite starts failing on a column that does not exist on this branch, and
nothing in git status explains either.
Worktree isolation is filesystem isolation. Everything your app touches that lives outside the working tree is still one shared thing.
What is actually shared
| Thing | Isolated per worktree? | Why it bites |
|---|---|---|
| Tracked source files | Yes | This is the part that works |
| The branch you are on | Yes | Each worktree has its own |
.git directory | No — shared by design | Fine; documented |
| Local dev database | No | Two branches' migrations, one schema |
localhost ports | No | Second dev server fails to bind |
| OS-level secret stores | No | Same keychain/credential entries |
| Docker containers and volumes | No | One postgres container for all worktrees |
node_modules / build artifacts | Absent, not shared | Gitignored, so a fresh worktree has none |
| Claude's auto memory | No — one dir per repository | All worktrees write to the same notes |
| Saved permission approvals | No — saved to the main checkout | An approval in one worktree applies everywhere |
The last two are in the docs. The middle block is not, and that middle block is where the confusing failures live, because none of it produces a git conflict. A schema that matches neither branch just quietly produces wrong test results.
The four fixes, roughly in order of payoff
- Give each worktree its own database. A database name derived from the
directory name is enough —
myapp_dev_feature_auth— or a separate container per worktree. The point is that a migration can only reach its own branch's schema. - Stop hardcoding the port. Read it from an environment variable with a
default, so a second worktree can be handed a different one. If your
.claude/launch.jsonor equivalent pins a port, that pin follows every copy of the project. - Install dependencies per worktree. The docs say this outright: a
worktree is a fresh checkout, so ask Claude to run your setup there or run
it yourself.
node_modules,target/,venv/are gitignored and therefore simply absent. - Use
.worktreeinclude— carefully. It copies gitignored files such as.envinto every new worktree Claude Code creates with git. That solves "my new worktree has no config" and can cause the database collision: if the copied.envpins oneDATABASE_URL, every worktree now points at the same database with full confidence.
What is unverified here
Nobody has reproduced this against a controlled setup for this entry, and the
detailed writeup it comes from is .NET-specific — LocalDB, launchSettings.json,
dotnet ef. The shape is stack-independent, because it follows from what a
git worktree is rather than from anything Claude Code does; the specific
remedies are not. Treat the fixes above as a checklist to adapt, not commands
to paste.
The official worktrees page has a section listing what a worktree shares with
the main checkout, and it stops at Claude Code's own state: the .git
directory, project-scope plugins, saved permission approvals. It says nothing
about databases, ports, or credential stores — not because those are isolated,
but because they are outside its scope. Reading that list as exhaustive is the
mistake this tip is about.
What would settle it
Look at what your worktrees actually point at before you run two sessions:
# List every worktree, then show the DB/port config each one carries
git worktree list --porcelain | awk '/^worktree /{print $2}' | while read -r w; do
printf '\n== %s\n' "$w"
grep -hEs '^(DATABASE_URL|DB_NAME|PORT|VITE_PORT)=' "$w/.env" "$w/.env.local" 2>/dev/null \
|| echo ' (no .env values found)'
doneTwo worktrees printing the same DATABASE_URL or the same PORT is the bug,
visible before it costs you anything. To confirm the port half, start your dev
server in one worktree and then in another and read the second one's error.
Checkpoint
- Which of the two shared-state failures — database or port — produces no error
message at all in the session that caused it?
Show answerAnswer
The database. A port collision is loud — the second dev server simply fails to bind and hands you the error. A migration run against the shared dev database succeeds, and the schema it leaves is a merge of two branches that exists on neither: no git conflict, nothing in
git status, just tests failing later on a column that does not exist on this branch. Rolling back is per-branch, and the branches disagree about what the previous state was. - Your
.worktreeincludecopies.envinto every worktree. Under what circumstance does that make the database problem worse rather than better?Show answerAnswer
When the copied
.envpins oneDATABASE_URL. Solving "my new worktree has no config" by cloning the file means every worktree now points at the same database with full confidence — the exact collision you were trying to avoid — and it multiplies the number of places a live credential sits on disk. Pair it with the first fix: a database name derived from the directory,myapp_dev_feature_auth, or a container per worktree. Note too that.worktreeincludeis skipped entirely when aWorktreeCreatehook replaces the default git behavior, in which case the copying belongs inside the hook script. - A colleague says "worktrees isolate everything except
.git." Which two pieces of Claude Code's own state does that claim already get wrong?Show answerAnswer
Claude's auto memory — one directory per repository, so every worktree writes to the same notes — and saved permission approvals, which live in the main checkout, so an approval granted in one worktree applies everywhere. Both are in the docs, but only the second is on the worktrees page: its list of what a worktree shares runs
.gitdirectory, project-scope plugins, saved permission approvals. That list stops at Claude Code's own state and says nothing about databases, ports or credential stores — not because those are isolated, but because they are outside its scope. Reading it as exhaustive is the mistake this tip is about.