Commands
All examples use the ww alias. willow and ww are interchangeable.
<willow-base> defaults to ~/.willow. You can change it with WILLOW_BASE_DIR, the global baseDir config, or ww migrate-base <path>.
Setup
ww clone <url> [name]
Bare-clone a repo and create an initial worktree on the default branch. Required entry point — all willow-managed repos must be set up via ww clone.
ww clone git@github.com:org/repo.git
ww clone git@github.com:org/repo.git myrepo # custom name
ww clone git@github.com:org/repo.git --force # re-clone from scratch| Flag | Description | Default |
|---|---|---|
--force | Remove existing repo and re-clone from scratch | false |
What happens under the hood:
git clone --bare <url> <willow-base>/repos/<name>.git- Configure remote fetch refs
git fetch origin- Create an initial worktree on the default branch
ww shell-init [flags]
Print shell integration script.
eval "$(willow shell-init)" # bash / zsh
willow shell-init | source # fish
eval "$(willow shell-init --tab-title)" # with tab titles| Flag | Description |
|---|---|
--tab-title | Include terminal tab title hook |
Worktrees
ww new <branch> [flags]
Create a new worktree with a new branch, an existing branch, or a GitHub PR.
ww new feature/auth # create worktree
ww new feature/auth -b develop # fork from specific branch
ww new -e existing-branch # use existing branch
ww new -e # pick from remote branches (fzf)
ww new --pr 123 # checkout PR #123
ww new https://github.com/org/repo/pull/123 # checkout a PR by URL
ww new feature/auth -r myrepo # target a specific repo
ww new feature/auth # auto-cd via shell integration (tmux-aware)| Flag | Description | Default |
|---|---|---|
-b, --base | Base branch to fork from | Config default / auto-detected |
-r, --repo | Target repo by name | Auto-detected from cwd |
-e, --existing | Use an existing branch (or pick from fzf if no branch given) | false |
--pr | GitHub PR number or URL | |
--no-fetch | Skip fetching from remote | false |
--cd | Print only the path (for scripting) | false |
Existing branch picker
Running ww new -e without a branch name opens an fzf picker showing all remote branches that don't already have worktrees. Select one to create a worktree for it.
GitHub PR support
Use --pr with a PR number or pass a full PR URL as the branch argument. Willow resolves the branch name via gh, fetches it, and creates the worktree. Requires the GitHub CLI (gh).
ww new --pr 123 # by number
ww new https://github.com/org/repo/pull/123 # by URLThis also works from the tmux picker — press Ctrl-P for a dedicated PR picker, or paste a PR URL and press Ctrl-N.
ww checkout <branch-or-pr-url> (alias: co)
Smart switch-or-create. Figures out the right action based on what exists:
- Worktree exists for that branch → switch to it (like
ww sw) - Branch exists on remote but no worktree → create a worktree for it (like
ww new -e) - Branch doesn't exist → create a new branch + worktree (like
ww new) - PR URL → resolve the branch via
gh, then apply the logic above
ww checkout auth-refactor # switch if exists, create if not
ww checkout --pr 123 # checkout PR #123
ww checkout https://github.com/org/repo/pull/123 # checkout a PR by URL
ww checkout brand-new-feature # creates new branch + worktree
ww checkout brand-new -b develop # new branch forked from develop
ww checkout auth-refactor # auto-cd via shell integration (tmux-aware)| Flag | Description | Default |
|---|---|---|
-r, --repo | Target repo by name | Auto-detected from cwd |
-b, --base | Base branch (only when creating a new branch) | Config default / auto-detected |
--pr | GitHub PR number or URL | |
--no-fetch | Skip fetching from remote | false |
--cd | Print only the path (for scripting) | false |
ww sw
Switch worktrees via fzf. Shows Claude Code agent status per worktree, sorted by activity.
🤖 BUSY auth-refactor <willow-base>/worktrees/repo/auth-refactor
✅ DONE api-cleanup <willow-base>/worktrees/repo/api-cleanup
⏳ WAIT payments <willow-base>/worktrees/repo/payments
🟡 IDLE main <willow-base>/worktrees/repo/main
-- old-feature <willow-base>/worktrees/repo/old-feature
Active agents sorted first, offline sorted last. Pass a name to skip the picker: ww sw auth-refactor.
| Flag | Description | Default |
|---|---|---|
-r, --repo | Target repo by name (defaults to all repos) | Auto-detected from cwd |
ww rm [branch] [flags]
Remove a worktree. Without arguments, opens fzf picker with multi-select (TAB to toggle, Ctrl-A to select all).
ww rm auth-refactor # direct removal
ww rm # fzf picker
ww rm auth-refactor --force # skip safety checks
ww rm auth-refactor --prune # also run git worktree prune| Flag | Description | Default |
|---|---|---|
-f, --force | Skip safety checks | false |
--keep-branch | Keep the local branch | false |
--prune | Run git worktree prune after | false |
Safety checks (unless --force):
- Warns if there are uncommitted changes
- Warns if there are unpushed commits
ww ls [repo]
List worktrees with status.
BRANCH STATUS PATH AGE
main IDLE <willow-base>/worktrees/myrepo/main 3d
auth-refactor BUSY <willow-base>/worktrees/myrepo/auth-refactor 2h
payments WAIT <willow-base>/worktrees/myrepo/payments 1d
old-feature -- <willow-base>/worktrees/myrepo/old-feature 5m
When run outside a willow repo, lists all repos and their worktree counts.
| Flag | Description |
|---|---|
--json | JSON output |
--path-only | Paths only (one per line) |
Stacks
Stacked PRs
Create a chain of branches where each builds on the previous:
ww new feature-a -b main # start a stack
ww new feature-b -b feature-a # stack on top
ww new feature-c -b feature-b # third layerWhen --base points to a local branch (another worktree), willow forks from it directly and records the parent relationship in branches.json per repo. Stacked branches are shown as a tree in ww ls and the tmux picker:
BRANCH STATUS PATH AGE
├─ feature-a BUSY <willow-base>/worktrees/repo/feature-a 2h
│ └─ feature-b DONE <willow-base>/worktrees/repo/feature-b 1h
│ └─ feature-c -- <willow-base>/worktrees/repo/feature-c 30m
standalone -- <willow-base>/worktrees/repo/standalone 1d
Removing a stacked branch re-parents its children to the removed branch's parent. Use --force if the branch has children.
ww stack status (alias: ww stack s)
Show CI, review, and merge status for every PR in a stack at a glance. Fetches all data in a single gh pr list call, so it's O(1) regardless of stack depth.
ww stack status # current repo
ww stack status -r myrepo # target a specific repo
ww stack status --json # machine-readable output feature-a #42 ✓ CI ✓ Review MERGEABLE +100 -20
└─ feature-b #43 ✗ CI ◯ Review CONFLICTING +50 -10
└─ feature-c (no PR)
Each branch shows:
- PR number — links to the GitHub PR
- CI status — aggregate of all checks:
✓ CI(pass),✗ CI(fail),◯ CI(pending),- CI(none) - Review status —
✓ Review(approved),✗ Review(changes requested),◯ Review(required),- Review(none) - Mergeable —
MERGEABLE,CONFLICTING, orUNKNOWN - Diff stats — additions and deletions
| Flag | Description | Default |
|---|---|---|
-r, --repo | Target repo by name | Auto-detected from cwd |
--json | Output as JSON | false |
Requires the GitHub CLI (gh).
ww sync [branch]
Rebase stacked worktrees onto their parents in topological order. Like git machete traverse but for worktrees.
ww sync # sync all stacks in current repo
ww sync feature-b # sync only feature-b and its descendants
ww sync -r myrepo # target specific repo
ww sync --abort # abort any in-progress rebases| Flag | Description | Default |
|---|---|---|
-r, --repo | Target repo by name | Auto-detected from cwd |
--no-fetch | Skip git fetch origin | false |
--abort | Abort in-progress rebases across all stacked worktrees | false |
How it works:
- Fetches
originonce - Processes branches in topological order (parents before children)
- For root branches (parent is
main): rebases ontoorigin/main - For stacked branches: rebases onto the local parent (which was just synced)
- On conflict: stops descendants of the conflicting branch, continues other stacks
Dirty worktrees and in-progress rebases are skipped with a warning.
Inspection
ww status
Rich view of Claude Code agent status across all worktrees. Shows per-session rows when multiple Claude sessions run in the same worktree.
myrepo (4 worktrees, 3 sessions active, 1 unread)
🤖 auth-refactor BUSY 2m ago
🤖 auth-refactor BUSY 5m ago
✅ payments DONE● 12m ago
🟡 main IDLE 1h ago
old-feature --
The ● indicator marks completed sessions you haven't reviewed yet. Switching to a worktree via ww sw marks it as read.
| Flag | Description |
|---|---|
--json | JSON output |
ww dashboard (alias: dash, d)
Live-refreshing TUI showing all Claude Code sessions across all repos. Renders in an alternate screen buffer with no flicker. Includes a timeline sparkline showing BUSY/WAIT/DONE transitions over the last 60 minutes.
ww dashboard # default 2s refresh
ww dash -i 5 # 5s refresh interval
ww dash --no-timeline # hide the timeline column
| Flag | Description |
|---|---|
-i, --interval | Refresh interval in seconds (default: 2) |
--no-timeline | Hide the timeline sparkline column |
| Key | Action |
|---|---|
j/k | Navigate rows |
Enter | Switch to tmux session |
t | Toggle timeline column |
r | Refresh |
q / Ctrl-C | Quit |
The timeline requires ww cc-setup to be re-run to install the updated hook that records status transitions.
ww log
Show activity log of worktree events. Events are recorded automatically when you create, remove, or sync worktrees. Stored as monthly JSONL files under <willow-base>/log/.
ww log # last 20 events
ww log --branch auth-refactor # filter by branch
ww log --repo myrepo # filter by repo
ww log --since 7d # events from last 7 days
ww log -n 50 # last 50 events
ww log --json # raw JSON output| Flag | Description |
|---|---|
--branch | Filter by branch name |
-r, --repo | Filter by repo name |
--since | Show events after duration (e.g. 7d, 24h, 30m) |
-n, --limit | Max events to show (default 20) |
--json | JSON output |
Event types:
| Action | Trigger |
|---|---|
create | Worktree created via ww new or ww checkout |
remove | Worktree removed via ww rm |
sync | Branch rebased via ww sync |
ww gc
Clean up leftover trash from removed worktrees and, optionally, prune worktrees whose branches have been merged into the base branch.
ww gc # empty <willow-base>/trash/ and list merged worktrees
ww gc --prune # also interactively remove merged worktrees
ww gc --dry-run # preview without touching anything
ww gc --prune --dry-run # list what --prune would remove| Flag | Description | Default |
|---|---|---|
--prune | Interactively remove merged worktrees | false |
--dry-run | Show what would be cleaned up without removing anything | false |
Without --prune, willow only empties the trash directory and prints the commands you'd run to remove each merged worktree. With --prune, it reads branches.json across all repos, cross-references git branch --merged, and prompts before removing each one.
Agents
Desktop notifications
Desktop notifications fire directly from Claude Code's hook system after you run ww cc-setup — there's no daemon, no polling, and no ww notify subcommand. Whenever an agent transitions from BUSY to DONE or WAIT, the hook fires a macOS Notification Center alert within ~200ms.
Enable desktop notifications: Set "notify": {"desktop": true} in config. The tmux status bar is a separate channel and is unaffected.
Custom notification command: Set notify.command in config to run your own script instead of the built-in dispatch. The command receives WILLOW_NOTIFY_TITLE and WILLOW_NOTIFY_BODY as environment variables.
Concurrency: When multiple sessions run in the same worktree, an advisory flock on <willow-base>/notify-states.lock prevents duplicate notifications.
ww dispatch <prompt>
Create a worktree and launch Claude Code with a prompt. From the terminal, Claude runs interactively in the foreground. From the tmux picker (Ctrl-G), it launches in a background session.
ww dispatch "Fix the login validation bug" # auto-name branch
ww dispatch "Add retry logic" --name add-retries # explicit branch name
ww dispatch "Write tests for auth" --base feature/auth # stacked on a branch
ww dispatch "Refactor payments" --repo myrepo # target specific repo| Flag | Description | Default |
|---|---|---|
--name | Worktree/branch name | Auto-slugified from prompt (e.g. dispatch--fix-the-login-validation) |
-r, --repo | Target repo by name | Auto-detected from cwd |
-b, --base | Base branch to fork from | Config default |
--no-fetch | Skip fetching from remote | false |
--yolo | Run Claude with --dangerously-skip-permissions | false |
How it works:
- Creates a new worktree (reuses
ww newinternally) - Launches
claude "<prompt>"(interactive session with prompt pre-loaded) - Logs a
dispatchevent (visible inww log)
The agent appears in ww status, ww dashboard, and the tmux picker. Switch to it anytime with ww sw.
ww cc-setup
Hook installation for Claude Code status tracking. Safe to re-run: each invocation overwrites willow's entries in ~/.claude/settings.json with the current binary path, so upgrades and relocations stay in sync. Third-party hook rules are left untouched.
- Creates the status directory:
<willow-base>/status/ - Registers the hidden
willow hooksubcommand for every event willow tracks. The absolute binary path is resolved viaos.Executable()at install time.
The hook fires on 6 events (UserPromptSubmit, PreToolUse, PostToolUse, Stop, Notification, SessionEnd). Each invocation writes per-session status to <willow-base>/status/<repo>/<worktree>/<session_id>.json, appends to the session timeline, and dispatches desktop notifications on status transitions. Multiple Claude sessions in the same worktree are tracked independently. The SessionEnd event immediately removes the session file for instant cleanup.
The hook also tracks enriched session data:
tool_count— number of tool invocations in the sessionstart_time— when the session first became activefiles_touched— files written/edited by the agent (stored in a.filessidecar)
Agent status
After running ww cc-setup, Claude Code automatically reports its state:
| Icon | Status | Meaning |
|---|---|---|
| 🤖 | BUSY | Agent is actively working |
| ✅ | DONE | Agent finished its turn |
| ⏳ | WAIT | Agent is waiting for user input |
| 🟡 | IDLE | Agent session ended |
-- | No activity detected |
Stale BUSY/DONE status (>5 min) automatically degrades to IDLE. Completed sessions show a ● unread indicator until you switch to that worktree via ww sw.
Configuration
ww config
View, edit, and initialize willow configuration. Merges global (~/.config/willow/config.json) and local (per-repo willow.json) configs, showing the effective value and source for each field. baseDir is global-only and resolved before repo-local config is loaded.
ww config show # show merged config with source annotations
ww config show --json # raw JSON output
ww config show -r myrepo # show config for a specific repo
ww config edit # open global config in $EDITOR
ww config edit --local # open local (per-repo) config
ww config edit --local -r myrepo # open specific repo's local config
ww config init # create default global config
ww config init --local # create default local config
ww config init --force # overwrite existing configww config show
| Flag | Description | Default |
|---|---|---|
--json | Output raw JSON | false |
-r, --repo | Target repo by name | Auto-detected from cwd |
ww config edit
| Flag | Description | Default |
|---|---|---|
--global | Edit global config (default) | true |
--local | Edit local (per-repo) config | false |
-r, --repo | Target repo by name | Auto-detected from cwd |
Editor resolution: $VISUAL > $EDITOR > vi.
ww config init
| Flag | Description | Default |
|---|---|---|
--local | Create local (per-repo) config | false |
-r, --repo | Target repo by name | Auto-detected from cwd |
--force | Overwrite existing config | false |
ww migrate-base <path>
Move willow's base directory to a new path, repair Git worktree metadata, and persist the new global baseDir.
ww migrate-base ~/code/evergreen/worktrees/willow
ww migrate-base ~/code/evergreen/worktrees/willow --dry-run
ww migrate-base ~/code/evergreen/worktrees/willow --yes| Flag | Description | Default |
|---|---|---|
--dry-run | Show the planned move without changing anything | false |
-y, --yes | Skip confirmation | false |
The command rejects overlapping paths, non-empty destinations, active Claude session files, and running from inside the current willow base.
ww doctor
Check your willow setup for common issues. Runs a series of checks and prints a checklist:
ww doctor # report only
ww doctor --fix # prompt to remove unmarked legacy willow hooks from settings.json✔ git 2.45.0
✔ gh CLI installed
✔ tmux installed
✔ Claude Code hooks installed
✔ <willow-base> exists
✔ <willow-base>/repos exists
✔ <willow-base>/worktrees exists
✔ no stale session files
✔ config valid
| Check | Details |
|---|---|
| git version | Warns if < 2.30 |
| gh CLI | Optional — warns if missing |
| tmux | Optional — warns if missing |
| Claude Code hooks | Checks ~/.claude/settings.json for willow hooks; flags unmarked legacy entries from older releases |
| Willow directories | <willow-base>, repos/, worktrees/ |
| Stale sessions | Session files older than 30 minutes |
| Config validity | Validates merged config for common issues |
--fix flag: When legacy willow hooks are detected, ww doctor --fix prompts for confirmation and removes them from ~/.claude/settings.json. Marker-tagged (source: "willow") and third-party rules are never touched.
Reference
ww tmux
Tmux integration for worktree management. See the Tmux Integration page for full documentation.
ww tmux pick # interactive worktree picker (for tmux popup)
ww tmux list # formatted picker lines (for fzf reload)
ww tmux status-bar # tmux status-right widget
ww tmux install # print tmux.conf lines to addSetup: ww tmux install prints the config to add to ~/.tmux.conf, including a prefix + w keybinding for the picker popup.
Aliases
| Alias | Command |
|---|---|
ww n | ww new |
ww co | ww checkout |
ww l | ww ls |
ww s | ww status |
ww dash / ww d | ww dashboard |
ww stack s | ww stack status |
Global flags
-C <path> Run as if willow was started in <path> (env: WILLOW_DIR)
--verbose Show git commands being executed
--trace Print timing trace to stderr for performance debugging (env: WILLOW_TRACE)