Use Worktrees
Worktrees give each parallel task its own working directory while sharing one repository. This skill defines where they live and the invariants that keep the primary checkout safe.
Policy (non-negotiable)
- One location: every worktree lives at
<repo-root>/.worktrees/<name>, where<repo-root>is the primary checkout of the repo being worked on. Never.claude/worktrees/, never sibling directories (../repo-feature), never temp dirs, never nested inside another worktree. - Per-repo, even cross-project: when a session in project A creates a
worktree for project B, it goes in B’s own root (
<B-root>/.worktrees/), not under A and not in any shared location. - Always ignored:
.worktrees/must be gitignored. Check withgit check-ignore -q .worktrees; if not ignored, append.worktrees/to.git/info/exclude(repo-local, safe in shared repos). Only add it to the tracked.gitignorewhen the user wants it committed for the whole team. - The default branch never moves: the primary checkout stays on the
default branch (
main/master) permanently. Nevergit switchorgit checkouta feature branch there, never commit on it directly; update it only withgit pull --ff-only. All branch work happens inside worktrees.
Create a worktree
root=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")
git -C "$root" check-ignore -q .worktrees || printf '.worktrees/\n' >> "$root/.git/info/exclude"
default=$(git -C "$root" symbolic-ref -q --short refs/remotes/origin/HEAD || echo HEAD)
git -C "$root" worktree add -b <branch> "$root/.worktrees/<task>" "$default"
Notes:
- Deriving
rootfrom the git common dir means this works from inside another worktree too: the new one still lands in the primary checkout. - Branch from
origin/HEAD(the remote default) so the local default branch is never involved. Iforigin/HEADis unset, rungit remote set-head origin --autoonce. - For another repo, run the same commands with
git -C /path/to/repo. - To work on an existing branch:
git worktree add "$root/.worktrees/<task>" <branch>.
Clean up
git worktree list # see what exists
git worktree remove .worktrees/<task> # add --force if dirty
git branch -d <branch> # after merge
git worktree prune # clear stale metadata
Claude Code specifics
- This machine automates the policy: a
WorktreeCreatehook (~/.claude/hooks/worktree-create.sh, registered in~/.claude/settings.json) makesclaude --worktreeand subagentisolation: worktreecreate under.worktrees/automatically, and~/.gitignore_globalignores.worktrees/everywhere. - EnterWorktree tool: never use its name-based mode (it defaults to
.claude/worktrees/). Create the worktree with git as above, then callEnterWorktreewithpath: <repo-root>/.worktrees/<task>. - On machines without the hook, follow the manual commands above; the policy is the same for every tool (claude-code, codex, gemini, copilot).
Gotchas
- Git refuses to check out a branch that is already checked out in another
worktree. Because the primary checkout holds the default branch, no
worktree can ever claim it: branch from
origin/HEADinstead ofmain. - A worktree is a fresh checkout: gitignored files (
.env,node_modules/,.venv/) are absent. Re-run dependency install anddirenv allowper worktree. Projects can list files to copy in.worktreeinclude(gitignore syntax); the Claude Code hook honors it. - Deleting a worktree directory by hand leaves stale metadata; always use
git worktree remove, orgit worktree pruneafterwards. git statusin the primary checkout will not show.worktrees/(it is ignored); usegit worktree listto see what exists.