- name
- git-worktree
- description
- 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.
- argument-hint
- [create|list|switch|cleanup] <name>
- user-invocable
- true
# 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
```bash
${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:
```bash
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](#ruvector-db-sharing) below)
- Fails safely if worktree already exists
**Examples:**
```bash
# 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:
```bash
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:
```bash
worktree-manager.sh switch <name>
worktree-manager.sh go <name>
```
**Note:** Prints `cd` command for shell evaluation:
```bash
# 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:
```bash
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 `cd`s 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:
```bash
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
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 lock`ed. 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:
```bash
# 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:
```bash
# 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:
```bash
# 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
```bash
# 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
```bash
# 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](https://mise.jdx.dev/) (`.mise.toml`,
`.tool-versions`) or [direnv](https://direnv.net/) (`.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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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](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
View on GitHub