Skip to content

parallel-dev worktrees — second-developer bootstrap (CVN-N014-EC-S16)

scripts/new_worktree.sh spins up a second Claude Code "developer": a fully equipped parallel workspace on its own branch, identical to the primary checkout. Two parallel devs = two git worktrees + two sessions (one window each) — not a second git clone.

Why a worktree (and not a clone)

A second clone duplicates the .git object store, drifts on fetches, and forces you to re-wire every local-only secret. A git worktree shares one .git (objects / refs / remotes), so a commit or push on one side is instantly visible on the other — and the script symlinks the gitignored local environment so the second session is a faithful clone of the first.

What it sets up

new_worktree.sh <branch> [dir] [base]
  ├─ git worktree on a dedicated branch (new from <base>, or attach existing)
  ├─ .venv_airflow      → symlink to the primary venv      (no duplication)
  ├─ .env               → symlink to the primary env        (single secrets source)
  ├─ .claude/settings.json + settings.local.json → symlinks (same perms / hooks / MCP)
  └─ shared project memory: ~/.claude/projects/<worktree>/memory → primary's memory

Tracked artefacts (skills, hooks, documentation/ADR.md, CLAUDE.md) follow the branch for free at checkout — no extra wiring. ADRs and deployments need nothing special: they are repo edits + gh / kubectl / helm, all present in the worktree.

Usage

# new branch off main (default dir ../champollion-<branch-slug>)
scripts/new_worktree.sh feat/CVN-N001-EI-S06-foo

# explicit dir, and/or attach an EXISTING branch (e.g. a PR branch)
scripts/new_worktree.sh chore/airflow-xcom-reader ../champollion-dev2
positional default meaning
<branch> — (required) branch to check out — created from <base> if it doesn't exist, else attached
[dir] ../champollion-<branch-slug> worktree path
[base] main base ref for a new branch

Then open the new dir in a fresh VSCode window (the Claude extension starts its own session) or cd "$DIR" && source .venv_airflow/bin/activate && claude.

Iron rules (worktree gotchas)

  • One window per worktree. A session is bound to the folder it was launched in; it cannot "become" the other folder. Two windows on the same folder = same worktree = HEAD/index collision.
  • Different branch per worktree. Git refuses to check out the same branch in two worktrees — each dev is on a different branch. That is the point.
  • Branch new stories from origin/main after git fetch — the local main may be stale relative to the remote.
  • Do not git stash in a worktreegit stash uses one shared stack (refs/stash in the common git dir) across all worktrees. A stash pushed in one worktree and popped in another applies the wrong diff to the wrong tree and silently corrupts it. See Parking WIP without stash below. new_worktree.sh prints this warning in its completion banner.
  • Per-worktree identity is auto-pinned. .git/config is shared, so identity set with plain git config leaks between worktrees. new_worktree.sh enables extensions.worktreeConfig and writes the primary's user.name / user.email with git config --worktree, so each worktree's authorship is isolated and explicit — no manual step.
  • Serialise cluster runs. Both workspaces share one prod cluster (Airflow, MLflow, Postgres ftf_config, Loki). Code and plan work parallelises; DAG / FTF runs must be serialised (max_active_runs=1, shared compute pool + ftf_config).

Parking WIP without git stash

Because the stash stack is shared, use one of these worktree-safe alternatives instead — each keeps WIP bound to the worktree it belongs to:

Instead of git stash Do this
Quick "set it aside" git commit -am wip (amend or git reset --soft HEAD~1 to resume)
Switch context, keep WIP visible git worktree add ../champollion-scratch <branch> — a third throwaway worktree
Move uncommitted work to another branch git switch -c <branch> (the working changes follow you, no stash)

If you absolutely must shelve without committing, scope it to one worktree and pop it before touching any other worktree — never leave a stash live while two devs are active.

Memory sharing

The Claude auto-memory dir is keyed by the sanitised cwd, so a worktree at a different path would otherwise get a different, empty memory. new_worktree.sh symlinks ~/.claude/projects/<worktree>/memory → the primary's memory, so MEMORY.md + handoffs are shared — the durable cross-session channel between the two devs. The live conversation context is not shared: only what is written to memory crosses between them.

Reference: memory reference_parallel_dev_worktrees · plan dossier ../reviews/2026-06-05-cvn-n014-ec-s16-dev-productivity-tooling-plan.md.