worktree-bootstrap
Make a git worktree run its dev server exactly like the active trunk — in one command. Project-agnostic: works for any JavaScript/TypeScript repo on macOS, Linux, or WSL.
Two modes (auto-detected)
Run the same script anywhere — it detects where you are and does the right thing:
- Inside a linked worktree → bootstrap it: catch up to the trunk, copy env, install deps, check the Next.js pins. (The common case — the rest of this doc.)
- In the primary checkout (not a worktree) → prep the current branch for worktrees: audit the branch-level things every future worktree inherits — a
.worktreeinclude file (so new worktrees get gitignored secrets at creation) and the turbopack.root / allowedDevOrigins pins — and report what's missing. When the script runs in prep mode, apply its recommendations: create or extend .worktreeinclude (safe + additive — show the user what you're adding), and propose the next.config pins for confirmation (they're source code). It's a one-time "make this branch worktree-ready" pass.
Why this exists
A linked worktree is pinned to its own dedicated branch (git won't let two worktrees share one), and that branch is frozen at creation — it never advances, so every commit on the trunk leaves the worktree silently behind ("missing commits"). On top of that, git worktree add (and claude --worktree) only check out tracked files, so a new worktree is also missing the gitignored things a dev server needs:
- Behind the trunk → the worktree's branch predates recent commits (config pins, lockfile changes, fixes). This skill fast-forwards it to the trunk first, so every later step runs on the final tree.
.env.local / .env → the app crashes at runtime because a required env var is undefined (a database URL, an API key, etc.).
node_modules → pnpm run dev fails with next: command not found (pnpm won't climb to the parent's node_modules).
And for Next.js, a worktree nested inside another repo can make Turbopack mis-resolve its workspace root to the parent and watch two dependency trees at once — which can exhaust memory and hard-freeze the machine. The fix is a one-line source pin (turbopack.root); this skill detects when it's missing — though usually it arrives automatically with the catch-up.
How to run it
Run the bundled script — it auto-detects worktree vs primary checkout:
bash scripts/setup-worktree.sh
(Use the absolute path to this skill's scripts/setup-worktree.sh if you're not in the skill directory.) Then relay its summary to the user — and in prep mode, apply the recommendations it prints (see "Two modes"). The script is idempotent — re-running when already current is a clean no-op. Its only history mutation is a strict fast-forward (git merge --ff-only) to the local trunk; it never does a non-ff merge, rebase, reset, force, fetch, or pull, and it skips and continues whenever a fast-forward isn't safe.
What it does
- Catch up to trunk — fast-forwards the worktree to the active trunk (the branch the primary worktree has checked out, resolved dynamically from
git worktree list — never hardcoded) via git merge --ff-only, so dep-install and the config checks below all run on the final tree. It skips and continues (never aborts, never auto-resolves) when the trunk can't be resolved, this worktree is the trunk, the tree has uncommitted tracked changes, or the branch has diverged (local commits not on the trunk — it tells you to git rebase manually).
- Env — copies every gitignored top-level
.env* file from the main checkout into the worktree, only if missing (never overwrites, never prints contents). (On Claude Code, a .worktreeinclude file at the repo root copies gitignored files into worktrees at creation time — for --worktree, subagent, and parallel sessions — so this step is the portable fallback and a no-op when they're already present.)
- Dependencies — if
node_modules is absent, detects the package manager from the lockfile (pnpm-lock.yaml → pnpm, package-lock.json → npm, yarn.lock → yarn, bun.lockb → bun) and runs the matching install — against the caught-up lockfile. With pnpm's shared global store this is typically a few seconds.
- Next.js workspace-root check (read-only) — if a
next.config.* exists without a turbopack.root pin and there's a lockfile above the worktree, it warns and prints the fix:// next.config.ts — inside nextConfig
turbopack: { root: import.meta.dirname },
- portless dev-origin check (read-only) — if
next.config.* exists and the portless CLI is installed, it verifies allowedDevOrigins covers portless's multi-label <branch>.<app>.localhost host. Next's default *.localhost is a single-label wildcard, so a two-label portless host gets its dev/HMR requests blocked; if uncovered, it advises the recursive wildcard:// next.config.ts — inside nextConfig
allowedDevOrigins: ["**.localhost"],
Why a worktree can be behind
A worktree's branch is frozen at creation, and claude --worktree may branch it from the fork-point of main and your trunk rather than the trunk's current tip — so it predates recent commits, including the Turbopack pins. Step 1 handles this automatically by fast-forwarding to the trunk. It only can't when your worktree has diverged (its own commits) or has uncommitted changes — then it tells you exactly what to run.
The one thing it can't do for you
If your branch has diverged from the trunk (you've committed work the trunk doesn't have), step 1 won't fast-forward — that would need a real merge or rebase, which can conflict, so it leaves that to you (git rebase <trunk>). And the config pins themselves (turbopack.root, allowedDevOrigins) are source code: if they're genuinely missing from the trunk, add them once on the trunk — every worktree then inherits them via the catch-up.
Note for monorepos: drop the turbopack.root: import.meta.dirname pin when the app lives inside a monorepo — there the root must be the monorepo root, not the app directory.
Safety
Resolves all paths and the trunk via git (git rev-parse, git worktree list) — never from user input. Quotes every path, uses no eval, and only ever runs git, cp, and the detected package-manager install. The only history-changing operation is a strict git merge --ff-only to a locally-resolved branch — no non-ff merge, rebase, reset, force, fetch, or pull. Env files are copied between two checkouts of the same repo on the same machine; their contents are never transmitted, printed, or logged.
1---2name: worktree-bootstrap3description: Provision a freshly-created git worktree so its dev server runs exactly like the active trunk. FIRST fast-forwards the worktree up to the active trunk (a worktree is pinned to its own frozen branch, so it silently falls behind every trunk commit — "missing commits"), then copies gitignored env files (.env.local, .env) from the main checkout, installs node_modules with the repo's package manager (pnpm/npm/yarn/bun), and — for Next.js — checks the Turbopack workspace-root + allowedDevOrigins pins that prevent nested-worktree dev-server freezes and portless HMR blocks. Use right after creating a worktree (git worktree add, or `claude --worktree`), or whenever a worktree is missing recent commits, fails with missing env vars, "next: command not found", a node_modules-missing error, or an "inferred workspace root" warning. Auto-detects its mode: run it in the PRIMARY checkout instead and it switches to PREP mode — auditing the current branch for worktree-readiness (a `.worktreeinclude` to copy gitignored secrets i4---56# worktree-bootstrap78Make a git worktree run its dev server **exactly like the active trunk** — in one command. Project-agnostic: works for any JavaScript/TypeScript repo on macOS, Linux, or WSL.910## Two modes (auto-detected)1112Run the same script anywhere — it detects where you are and does the right thing:1314- **Inside a linked worktree** → **bootstrap** it: catch up to the trunk, copy env, install deps, check the Next.js pins. (The common case — the rest of this doc.)15- **In the primary checkout** (not a worktree) → **prep** the current branch for worktrees: audit the branch-level things every future worktree *inherits* — a `.worktreeinclude` file (so new worktrees get gitignored secrets at creation) and the `turbopack.root` / `allowedDevOrigins` pins — and report what's missing. **When the script runs in prep mode, apply its recommendations:** create or extend `.worktreeinclude` (safe + additive — show the user what you're adding), and propose the `next.config` pins for confirmation (they're source code). It's a one-time "make this branch worktree-ready" pass.1617## Why this exists1819A linked worktree is pinned to its **own dedicated branch** (git won't let two worktrees share one), and that branch is **frozen at creation** — it never advances, so every commit on the trunk leaves the worktree silently behind ("missing commits"). On top of that, `git worktree add` (and `claude --worktree`) only check out **tracked** files, so a new worktree is also missing the *gitignored* things a dev server needs:2021- **Behind the trunk** → the worktree's branch predates recent commits (config pins, lockfile changes, fixes). This skill **fast-forwards it to the trunk first**, so every later step runs on the final tree.22- **`.env.local` / `.env`** → the app crashes at runtime because a required env var is undefined (a database URL, an API key, etc.).23- **`node_modules`** → `pnpm run dev` fails with `next: command not found` (pnpm won't climb to the parent's `node_modules`).2425And for **Next.js**, a worktree nested inside another repo can make Turbopack mis-resolve its workspace root to the parent and watch two dependency trees at once — which can exhaust memory and **hard-freeze the machine**. The fix is a one-line source pin (`turbopack.root`); this skill detects when it's missing — though usually it arrives automatically with the catch-up.2627## How to run it2829Run the bundled script — it auto-detects worktree vs primary checkout:3031```bash32bash scripts/setup-worktree.sh33```3435(Use the absolute path to this skill's `scripts/setup-worktree.sh` if you're not in the skill directory.) Then relay its summary to the user — and in **prep mode**, apply the recommendations it prints (see "Two modes"). The script is **idempotent** — re-running when already current is a clean no-op. Its only history mutation is a strict fast-forward (`git merge --ff-only`) to the local trunk; it never does a non-ff merge, rebase, reset, force, fetch, or pull, and it **skips and continues** whenever a fast-forward isn't safe.3637## What it does38391. **Catch up to trunk** — fast-forwards the worktree to the **active trunk** (the branch the primary worktree has checked out, resolved dynamically from `git worktree list` — never hardcoded) via `git merge --ff-only`, so dep-install and the config checks below all run on the final tree. It **skips and continues** (never aborts, never auto-resolves) when the trunk can't be resolved, this worktree *is* the trunk, the tree has uncommitted tracked changes, or the branch has **diverged** (local commits not on the trunk — it tells you to `git rebase` manually).402. **Env** — copies every gitignored top-level `.env*` file from the **main** checkout into the worktree, only if missing (never overwrites, never prints contents). *(On Claude Code, a `.worktreeinclude` file at the repo root copies gitignored files into worktrees at creation time — for `--worktree`, subagent, and parallel sessions — so this step is the portable fallback and a no-op when they're already present.)*413. **Dependencies** — if `node_modules` is absent, detects the package manager from the lockfile (`pnpm-lock.yaml` → pnpm, `package-lock.json` → npm, `yarn.lock` → yarn, `bun.lockb` → bun) and runs the matching install — against the *caught-up* lockfile. With pnpm's shared global store this is typically a few seconds.424. **Next.js workspace-root check** (read-only) — if a `next.config.*` exists without a `turbopack.root` pin **and** there's a lockfile above the worktree, it warns and prints the fix:43 ```ts44 // next.config.ts — inside nextConfig45 turbopack: { root: import.meta.dirname },46 ```475. **portless dev-origin check** (read-only) — if `next.config.*` exists and the `portless` CLI is installed, it verifies `allowedDevOrigins` covers portless's multi-label `<branch>.<app>.localhost` host. Next's default `*.localhost` is a *single-label* wildcard, so a two-label portless host gets its dev/HMR requests blocked; if uncovered, it advises the recursive wildcard:48 ```ts49 // next.config.ts — inside nextConfig50 allowedDevOrigins: ["**.localhost"],51 ```5253## Why a worktree can be behind5455A worktree's branch is frozen at creation, and `claude --worktree` may branch it from the **fork-point** of `main` and your trunk rather than the trunk's current tip — so it predates recent commits, including the Turbopack pins. Step 1 handles this automatically by fast-forwarding to the trunk. It only **can't** when your worktree has diverged (its own commits) or has uncommitted changes — then it tells you exactly what to run.5657## The one thing it can't do for you5859If your branch has **diverged** from the trunk (you've committed work the trunk doesn't have), step 1 won't fast-forward — that would need a real merge or rebase, which can conflict, so it leaves that to you (`git rebase <trunk>`). And the config pins themselves (`turbopack.root`, `allowedDevOrigins`) are **source code**: if they're genuinely missing from the trunk, add them once on the trunk — every worktree then inherits them via the catch-up.6061> Note for monorepos: drop the `turbopack.root: import.meta.dirname` pin when the app lives inside a monorepo — there the root must be the monorepo root, not the app directory.6263## Safety6465Resolves all paths and the trunk via git (`git rev-parse`, `git worktree list`) — never from user input. Quotes every path, uses no `eval`, and only ever runs `git`, `cp`, and the detected package-manager install. The only history-changing operation is a strict `git merge --ff-only` to a locally-resolved branch — no non-ff merge, rebase, reset, force, fetch, or pull. Env files are copied between two checkouts of the same repo on the same machine; their contents are never transmitted, printed, or logged.