CCLearn

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.

docs corroborate

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

ThingIsolated per worktree?Why it bites
Tracked source filesYesThis is the part that works
The branch you are onYesEach worktree has its own
.git directoryNo — shared by designFine; documented
Local dev databaseNoTwo branches' migrations, one schema
localhost portsNoSecond dev server fails to bind
OS-level secret storesNoSame keychain/credential entries
Docker containers and volumesNoOne postgres container for all worktrees
node_modules / build artifactsAbsent, not sharedGitignored, so a fresh worktree has none
Claude's auto memoryNo — one dir per repositoryAll worktrees write to the same notes
Saved permission approvalsNo — saved to the main checkoutAn 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

  1. 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.
  2. 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.json or equivalent pins a port, that pin follows every copy of the project.
  3. 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.
  4. Use .worktreeinclude — carefully. It copies gitignored files such as .env into 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 .env pins one DATABASE_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:

bash
# 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)'
done

Two 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 answer

    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 .worktreeinclude copies .env into every worktree. Under what circumstance does that make the database problem worse rather than better?
    Show answer

    When the copied .env pins one DATABASE_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 .worktreeinclude is skipped entirely when a WorktreeCreate hook 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 answer

    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 .git directory, 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.

Where this came from

Official pages that back this