KingInYellows

git-worktree

"Git worktree management for isolated parallel development, and restacking a stack whose branches live in separate worktrees. Use when reviewing PRs in isolation, working on multiple features simultaneously, when workflows offer worktree option, or when a restack fails with already used by worktree."

KingInYellows 0 Updated 2d ago

Resources

3
GitHub

Install

npx skillscat add kinginyellows/yellow-plugins/git-worktree

Install via the SkillsCat registry.

SKILL.md

Git Worktree Manager

Manage git worktrees for isolated parallel development with a simple, safe
interface.

What It Does

Creates and manages git worktrees — separate working directories from the same
repository that allow you to:

  • Work on multiple branches simultaneously without stashing
  • Review PRs in complete isolation from your current work
  • Test features without switching branches in your main working directory
  • Keep separate .env configurations per worktree

Critical Rule

ALWAYS use the manager script. NEVER use raw git worktree add directly.

The manager script provides:

  • Automatic .env file copying
  • .gitignore management
  • Safety validations
  • Consistent directory structure
  • Color-coded output

Manager Script Location

${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh

Replace ${CLAUDE_PLUGIN_ROOT} with the actual plugin installation path.

Usage

create

Create a new worktree:

worktree-manager.sh create <branch-name> [from-branch]

Behavior:

  • Creates worktree in .worktrees/<branch-name>/
  • Branches from main by default (or specify from-branch)
  • Copies all .env* files from main repo
  • Adds .worktrees/ to .gitignore if missing
  • Symlinks the main repo's .ruvector/ into the worktree if it exists
    (see ruvector DB Sharing below)
  • Fails safely if worktree already exists

Examples:

# Create worktree for feature branch
worktree-manager.sh create feature-auth

# Create worktree from specific branch
worktree-manager.sh create hotfix-security develop

list / ls

List all worktrees:

worktree-manager.sh list
worktree-manager.sh ls

Output:

Worktrees:
  main            /home/user/repo (clean)
  feature-auth    /home/user/repo/.worktrees/feature-auth (modified)
  pr-review-123   /home/user/repo/.worktrees/pr-review-123 (clean)

switch / go

Switch to a worktree directory:

worktree-manager.sh switch <name>
worktree-manager.sh go <name>

Note: Prints cd command for shell evaluation:

# Use with eval or source
eval "$(worktree-manager.sh switch feature-auth)"

# Or create shell function
wtgo() { eval "$(worktree-manager.sh go "$1")"; }

copy-env

Copy .env files to a worktree:

worktree-manager.sh copy-env <name>

Copies all .env* files from main repo to specified worktree. Useful if you've
updated environment configuration and need to sync. Also repairs a missing
.ruvector/ symlink for worktrees created before that feature existed (see
below).

ruvector DB Sharing

When a Claude Code session runs inside a worktree, the ruvector MCP server
resolves ${PWD}/.ruvector/ against the worktree path. Because .ruvector/
is gitignored (**/.ruvector/), it is not present in a fresh worktree, so
the MCP server and hook scripts would silently no-op (no recall, no
session-start context, no post-edit indexing).

worktree-manager.sh create injects an absolute symlink
${worktree}/.ruvector -> ${main_repo}/.ruvector after the .env copy step,
so MCP and hooks reach the project's shared DB. worktree-manager.sh copy-env
applies the same fix retroactively.

Behavior:

  • Skipped silently (with info) if the symlink already exists — idempotent.
  • Skipped (with warning) if a real .ruvector/ directory already exists in
    the worktree — preserves the user's intentional isolated DB if they bypassed
    the manager script.
  • Skipped (with warning) if the main repo has no .ruvector/ yet — no
    dangling symlinks.
  • Removed safely on cleanup: [ -L ] check, then rm -- (no -r, no -f,
    no trailing slash). Cannot follow into the main repo's DB.

MCP-spawn-time qualifier: yellow-ruvector's MCP launcher resolves the
store from the git toplevel of the directory Claude Code was launched in,
once, when it spawns the server (it also heals a missing worktree symlink at
that point). The symlink only matters for sessions launched (or re-launched)
from inside the worktree. A pre-existing session started in main that
later cds into a worktree continues to write to the main repo's DB directly.

Concurrent-write caveat: When two Claude Code sessions run simultaneously
(one in main, one in a worktree, or two worktrees), both MCP server processes
write to the same shared DB without cross-process locking. Avoid running
simultaneous sessions with active hooks_remember / /ruvector:index
operations on the same project. See plugins/yellow-ruvector/CLAUDE.md
Known Limitations.

cleanup / clean

Remove inactive worktrees:

worktree-manager.sh cleanup
worktree-manager.sh clean

Behavior:

  • Lists all worktrees
  • Prompts for confirmation
  • Removes worktrees one by one
  • Skips currently active worktree
  • Removes empty .worktrees/ directory if all cleaned up

Restacking across worktrees (worktree-restack.sh)

A stack whose branches are each checked out in their own worktree cannot be
restacked in one pass: the provider has to check a branch out, and git refuses
with '<branch>' is already used by worktree at <path>. Use /worktree:restack
(or the script directly) instead of restacking worktree by worktree:

bash "${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-restack.sh" \
  <subcommand> [--provider graphite|github] [--submit]
Subcommand Does
preflight Read-only. Prints the run worktree, the stack, the worktrees to detach and any REFUSE reasons
start Lock, detach, restack, verify ancestry, restore; --submit submits after
continue Resumes a paused conflict, then verifies and restores
abort Aborts the provider's restack (Graphite rolls back the whole restack) and any in-chain git rebase in a worktree the run recorded (other worktrees are left alone and named), returns the run worktree to the start branch, then restores
status Shows the recorded restack, or a worktree stranded detached at a branch tip
restore Restore only after partial restore, failed abort, mismatch, or hand finish

Run it from the worktree whose branch starts the restack; the restack set is
that branch plus everything stacked on it. Graphite detaches the other stack
worktrees for the run. GitHub needs gh-stack 0.2.0 or newer, which rebases
across worktrees itself, so nothing is detached.

Safety rules the script holds:

  • It refuses before any change when a stack worktree is dirty, locked,
    prunable or mid-operation, when the stack forks, or when a restack is already
    in progress. An untracked .ruvector symlink does not count as dirty.
  • Its own git calls never force a checkout, stash, hard-reset or pass
    --ignore-other-worktrees. --abort runs Graphite's abort (which rolls the
    whole restack back) and git rebase --abort in each recorded worktree (run
    worktree, Graphite's detached ones, GitHub's branch owners) holding an
    in-chain rebase, discarding its in-progress state. Unrecorded worktrees are
    left alone. It keeps the state (exit 31) for a GitHub rebase in an
    unrecorded worktree that may be the run's own, a recorded worktree whose
    rebase state cannot be read (named) or that git rebase --abort did not
    clear (rebase-stuck), or a stack branch off its recorded start commit
    before or after cleanup (each gets a fix line; see /worktree:restack).
    A worktree that cannot be restored stays detached and the script prints
    the git -C <path> checkout <branch> line.
  • A conflict pauses the run (exit 10). Graphite: the stack worktrees stay
    detached and git worktree locked. Do not commit in them: a commit there
    lands on no branch, and the script refuses to restore over it. GitHub:
    nothing is detached or locked; gh-stack holds the paused rebase.
  • State lives in <git-common-dir>/yellow-core/worktree-restack/ and is
    re-validated on every read; a rejected state file runs nothing.
  • Residual: the dirty check ignores gitignored files, and a restore checkout
    overwrites an ignored file when the restacked branch now tracks that path.
    A self-consistent forged state file is constrained to this repository's
    worktrees and the recorded stack branches, not stopped.

Exit codes are in the script header and the /worktree:restack exit table.
Exit 31: a step failed with the state kept; run status, then continue,
abort or restore. Exit 40: restore kept the state (detached worktrees
remain, or a GitHub restack is still paused).

The restack engine needs the stacked-PR providers (GitHub also needs Node, jq
and the github-workflow adapter); worktree-manager.sh does not.

When to Use

Code Review (/flow:review)

When reviewing a PR and you're not on the target branch:

# Workflow offers worktree option
/flow:review

# Choose worktree option
# Skill automatically creates worktree for PR branch
# You can review without affecting your current work

Parallel Feature Development (/flow:work)

When working on multiple features:

# Start feature 1
/flow:work
# Creates worktree for feature-1

# In another terminal, start feature 2
cd .worktrees/feature-2
/flow:work
# Both features developed independently

Manual Worktree Management

When you need direct control:

# Create worktree
worktree-manager.sh create experimental-refactor

# Switch to it
eval "$(worktree-manager.sh go experimental-refactor)"

# List all
worktree-manager.sh list

# Clean up when done
worktree-manager.sh cleanup

Workflow Examples

Example 1: Code Review with Worktree

# You're on feature-auth branch, working on authentication
$ git branch --show-current
feature-auth

# PR needs review on different branch
$ /flow:review pr-123

# Workflow detects branch mismatch, offers worktree option
# Select "Create worktree for isolated review"

# Worktree created automatically:
# - Branch: pr-review-123
# - Location: .worktrees/pr-review-123/
# - .env files copied
# - Ready for isolated review

# After review, cleanup:
$ worktree-manager.sh cleanup

Example 2: Parallel Feature Development

# Main repo: working on feature A
$ pwd
/home/user/myproject

# Need to quickly prototype feature B
$ worktree-manager.sh create feature-b
Created worktree: .worktrees/feature-b

$ eval "$(worktree-manager.sh go feature-b)"
$ pwd
/home/user/myproject/.worktrees/feature-b

# Work on feature B
$ nvim src/feature-b.ts
$ git commit -m "feat: prototype feature B"

# Switch back to main repo
$ cd /home/user/myproject

# Both features isolated, no stashing needed

Integration with Workflows

/flow:review Integration

When /flow:review detects you're not on the target branch:

  1. Offers worktree option
  2. Calls this skill: /git-worktree create pr-review-<number>
  3. Switches to worktree
  4. Proceeds with review in isolation

/flow:work Integration

When starting feature work:

  1. Asks if you want isolated environment
  2. Calls: /git-worktree create <branch-name>
  3. Copies .env files automatically
  4. Starts feature work in worktree

Auto-Trust mise/direnv After Worktree Creation

If your project uses mise (.mise.toml,
.tool-versions) or direnv (.envrc), the new
worktree directory starts untrusted — the tool refuses to load
configs until the user explicitly opts in, so tooling silently fails to
load after you cd in.

Auto-trust the new worktree directory at creation time. The manager
script handles common cases; the patterns below do it explicitly (e.g.,
a CI runner creating a throwaway worktree).

mise:

# After: worktree-manager.sh create feature-auth
cd .worktrees/feature-auth

# Trust the worktree's mise config
mise trust 2>/dev/null || true

# Or trust without cd (mise --cwd is supported on recent versions)
mise --cwd .worktrees/feature-auth trust 2>/dev/null || true

direnv:

# direnv requires both `allow` and a load on entry
cd .worktrees/feature-auth
direnv allow 2>/dev/null || true

# Or non-interactively
direnv allow .worktrees/feature-auth 2>/dev/null || true

Suppress on absent tools: 2>/dev/null || true keeps the trust step
quiet when neither mise nor direnv is installed. Do not drop the
redirect — without it, missing-binary stderr leaks into worktree
creation logs.

Why this is per-worktree: mise and direnv key trust on the absolute
path of the directory containing the config. A worktree at
.worktrees/feature-auth/ is a different absolute path from the main
repo, even when the .mise.toml content is identical.

.git-Is-a-File Detection (Submodule and Worktree Cases)

In a normal repo, .git is a directory. But in two important cases it
is a plain file containing a gitdir: <path> pointer:

  1. Inside a submodule: the submodule's .git is a file pointing at
    <superproject>/.git/modules/<submodule-name>.
  2. Inside an existing worktree: the worktree's .git is a file
    pointing at <main-repo>/.git/worktrees/<worktree-name>.

Naive scripts that do [ -d .git ] to detect a git repo will misclassify
both cases as "not a git repo" and fail in confusing ways.

Detection pattern:

# Correct: handles both directory and file forms
is_git_repo() {
    [ -e .git ] && git rev-parse --git-dir >/dev/null 2>&1
}

# Or check the type explicitly
git_dir_kind() {
    if [ -d .git ]; then
        echo "directory"   # main repo
    elif [ -f .git ]; then
        # Read the gitdir pointer to know whether it's a worktree or submodule
        case "$(head -n 1 .git)" in
            "gitdir: "*/.git/worktrees/*) echo "worktree" ;;
            "gitdir: "*/.git/modules/*)   echo "submodule" ;;
            "gitdir: "*)                  echo "linked" ;;
            *)                            echo "unknown" ;;
        esac
    else
        echo "none"
    fi
}

Worktree creation inside a submodule: Avoid creating a worktree
inside a directory whose .git is the submodule pointer file —
git worktree add from a submodule treats the submodule's gitdir as
the parent, which lands the new worktree under
<superproject>/.git/modules/<sub>/worktrees/, far from where users
expect. Always run worktree commands from the superproject root, or
explicitly pass -C <superproject-root> to git worktree add.

Why this matters for the manager script: get_repo_root uses
git rev-parse --show-toplevel, which handles both forms, but a caller
that pre-checks [ -d .git ] short-circuits before reaching it. Use
git rev-parse --git-dir (which handles both forms) instead.

Troubleshooting

See troubleshooting.md for common issues and solutions.

Design Principles

KISS (Keep It Simple, Stupid)

  • One script does everything
  • Clear command names
  • No complex configuration
  • Fails fast with clear errors

Opinionated Defaults

  • Worktrees always in .worktrees/ directory
  • Always branch from main (unless specified)
  • Always copy .env files
  • Branch name = worktree name (1:1 mapping)

Safety First

  • Never overwrites existing worktrees
  • Requires confirmation for cleanup
  • Adds .gitignore automatically
  • Color-coded warnings and errors

Reference

Git worktree documentation:

man git-worktree

Common git worktree commands:

git worktree list                    # List worktrees
git worktree remove <path>           # Remove specific worktree
git worktree prune                   # Clean up stale references

But use the manager script instead for consistent, safe operations.