| name | managing-worktrees |
| description | Create, fan out, and prune git worktrees for isolated tasks and parallel PR review. |
| user-invocable | true |
| allowed-tools | Bash(node:*), Bash(git worktree:*), Bash(git branch:*), Bash(git fetch:*), Bash(gh pr list:*), Bash(gh auth status), Bash(ls:*), Read |
| model | claude-haiku-4-5 |
| context | fork |
| metadata | {"internal":true} |
managing-worktrees
The Parallel Claude sessions rule in CLAUDE.md mandates worktrees for branch work. This skill is the helper that makes that ergonomic. Three modes, surgical, no auto-cleanup of work you didn't make.
When to use
- Starting a task that needs a branch. Spawn a worktree instead of
git checkout-ing in the primary checkout.
- Reviewing all open PRs locally. One worktree per PR, lined up under
../<repo>-pr-<num>/ so multiple Claude sessions can each take one.
- Cleaning up stale worktrees after PRs merge or branches get deleted upstream.
Never use this skill to remove a worktree that has uncommitted work. The Don't leave the worktree dirty rule applies; the dirty worktree is held until its owner commits.
Modes
Mode 1: new <task-name> (default)
Spawn a new worktree at ../<repo>-<task-name>/ based on the remote's default branch.
TASK_NAME="$1"
REPO_NAME=$(basename "$(git rev-parse --show-toplevel)")
WORKTREE_PATH="../${REPO_NAME}-${TASK_NAME}"
BRANCH="${TASK_NAME}"
BASE=$(node .claude/skills/fleet/_shared/scripts/git-default-branch.mts)
git fetch origin "$BASE"
git worktree add -b "$BRANCH" "$WORKTREE_PATH" "origin/$BASE"
echo "✓ Worktree ready at $WORKTREE_PATH on branch $BRANCH (base: $BASE)"
echo " cd $WORKTREE_PATH"
If $TASK_NAME collides with an existing branch, fail with the conflict. Never silently overwrite.
Mode 2: pr-fanout
For each open PR on the current GitHub repo, ensure a worktree exists at ../<repo>-pr-<num>/. Idempotent: skip PRs whose worktree already exists.
gh auth status >/dev/null
REPO_NAME=$(basename "$(git rev-parse --show-toplevel)")
gh pr list --json number,headRefName --jq '.[]' | while read -r pr_json; do
PR=$(echo "$pr_json" | jq -r '.number')
BRANCH=$(echo "$pr_json" | jq -r '.headRefName')
WORKTREE_PATH="../${REPO_NAME}-pr-${PR}"
if [ -d "$WORKTREE_PATH" ]; then
echo "= pr-${PR} already at $WORKTREE_PATH"
continue
fi
git fetch origin "$BRANCH:refs/remotes/origin/$BRANCH" 2>/dev/null
git worktree add "$WORKTREE_PATH" "origin/$BRANCH"
echo "+ pr-${PR} (branch $BRANCH) → $WORKTREE_PATH"
done
git worktree list
This is the multi-Claude review setup: each open PR gets its own checkout so a parallel session can take one without contention.
Mode 3: prune
Remove a worktree when its working tree is clean AND it has nothing left to land. "Nothing to land" means the branch is fully merged into the remote's default branch (every commit is already an ancestor of origin/<base>), OR the branch is 100% landed (each ahead commit is content-equivalent to the base: its work arrived via a squash-merge, auto-land, or rebase, proven per commit with in-memory git merge-tree, so a history squash can't hide it), OR the branch no longer exists on the remote AND the worktree is not ahead of the base. A worktree ahead of the base with content the base lacks is kept, because a local-only branch never pushed (e.g. an isolation worktree) reads as "branch gone from remote" yet carries unpushed work that pruning would destroy. That holds even when its branch is gone from the remote.
This is the same removability predicate (decideWorktree) the fleet-wide tidying-worktrees sweep applies — Mode 3 is the single-repo entry to that one engine, so it inherits the load-bearing aheadOfBase guard rather than re-deriving a weaker check in shell.
node .claude/skills/fleet/tidying-worktrees/lib/tidy-worktrees.mts --here
node .claude/skills/fleet/tidying-worktrees/lib/tidy-worktrees.mts --here --fix
--here resolves the current checkout's git toplevel (not a $PROJECTS sibling) and runs the engine against only that repo. The engine never discards work: a dirty tree is kept, a worktree ahead of the base is kept, and removal uses the clean-tree-gated --force only to clear the submodule-worktree guard. After pruning, pnpm i in the primary checkout — a git worktree remove can dangle the main checkout's node_modules symlinks (per the Don't leave the worktree dirty rule); the engine prints that reminder.
Mode 4: land
Move already-verified commits onto origin/<default> with the least ceremony that's still safe. This is the fast path for two cases: the primary checkout's branch has diverged from origin, because a parallel session squashed your commits onto origin via PR, leaving your local with unsquashable duplicates; or the branch is actively churned by another session, so a direct git push would be rejected and a reset --hard would discard that session's work.
The fleet lints as it edits, so a commit's diff already passed the gates the pre-commit / pre-push hooks re-run. Re-running them on land is ceremony that can wedge or crash. A pre-commit staged-test run hung 55 min in practice, and a fresh worktree has no node_modules, so the lib-importing pre-push hooks throw ERR_MODULE_NOT_FOUND. Mode 4 replaces the manual cherry-pick → fast-forward dance with one command: it re-asserts the lint gate on the landing diff (fast, deterministic, NOT a heavy test re-run), cherry-picks the commits onto a throwaway worktree branched off origin/<base> (a clean tree), confirms a clean fast-forward, then fast-forwards origin/<base>. NEVER force-pushes; if origin moved since, it aborts and tells you to re-run.
node .claude/skills/fleet/managing-worktrees/lib/land.mts --last 2
node .claude/skills/fleet/managing-worktrees/lib/land.mts --last 2 --push
node .claude/skills/fleet/managing-worktrees/lib/land.mts <sha-a> <sha-b> --push
node .claude/skills/fleet/managing-worktrees/lib/land.mts --last 2 --push --local
The cherry-pick runs per commit with an outcome table: a content-equivalent commit (already landed via a squash-merge or auto-land — the headline scenario) is DROPPED as skipped-already-landed, and only a real conflict aborts. To see per-commit landed/unlanded/superseded verdicts for every worktree before landing anything, run the read-only audit: node .claude/skills/fleet/tidying-worktrees/lib/tidy-worktrees.mts --audit.
The lint re-assert is the contract: a clean diff lands instantly; a lint failure ABORTS; the lint-as-edit contract was bypassed → pnpm run fix + re-commit. Only pass --no-verify-lint when the checkout genuinely can't run oxlint (no node_modules) AND you know the diff was lint-clean at edit time. The throwaway worktree + branch are cleaned up automatically; the git push --no-verify is deliberate — the diff is lint-verified above, and a fresh worktree's hooks can't load the lib.
Safety contract
This skill respects four CLAUDE.md rules:
- Parallel Claude sessions: only ever creates new worktrees; never
checkout-s an existing one.
- Don't leave the worktree dirty: refuses to
prune a dirty tree OR one ahead of the base with unpushed commits — Mode 3 delegates the decision to the shared decideWorktree predicate, so the guard can't drift.
- Public-surface hygiene: task names must not contain customer / company / internal-tool names. The skill does no redaction; the user picks a clean name.
- Default branch fallback: every base-branch lookup follows the
main → master → assume main chain via git symbolic-ref refs/remotes/origin/HEAD. Never hard-code one or the other.
Source
The pr-fanout pattern is borrowed from the /create-worktrees slash command in https://github.com/evmts/tevm-monorepo/blob/main/.claude/commands/create-worktrees.md, adapted to the fleet's ../<repo>-<task>/ layout convention and the parallel-Claude rule's safety contract.