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
FlagDescriptionDefault
--forceRemove existing repo and re-clone from scratchfalse

What happens under the hood:

  1. git clone --bare <url> <willow-base>/repos/<name>.git
  2. Configure remote fetch refs
  3. git fetch origin
  4. 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
FlagDescription
--tab-titleInclude 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)
FlagDescriptionDefault
-b, --baseBase branch to fork fromConfig default / auto-detected
-r, --repoTarget repo by nameAuto-detected from cwd
-e, --existingUse an existing branch (or pick from fzf if no branch given)false
--prGitHub PR number or URL
--no-fetchSkip fetching from remotefalse
--cdPrint 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 URL

This 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:

  1. Worktree exists for that branch → switch to it (like ww sw)
  2. Branch exists on remote but no worktree → create a worktree for it (like ww new -e)
  3. Branch doesn't exist → create a new branch + worktree (like ww new)
  4. 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)
FlagDescriptionDefault
-r, --repoTarget repo by nameAuto-detected from cwd
-b, --baseBase branch (only when creating a new branch)Config default / auto-detected
--prGitHub PR number or URL
--no-fetchSkip fetching from remotefalse
--cdPrint 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.

FlagDescriptionDefault
-r, --repoTarget 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
FlagDescriptionDefault
-f, --forceSkip safety checksfalse
--keep-branchKeep the local branchfalse
--pruneRun git worktree prune afterfalse

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.

FlagDescription
--jsonJSON output
--path-onlyPaths 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 layer

When --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)
  • MergeableMERGEABLE, CONFLICTING, or UNKNOWN
  • Diff stats — additions and deletions
FlagDescriptionDefault
-r, --repoTarget repo by nameAuto-detected from cwd
--jsonOutput as JSONfalse

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
FlagDescriptionDefault
-r, --repoTarget repo by nameAuto-detected from cwd
--no-fetchSkip git fetch originfalse
--abortAbort in-progress rebases across all stacked worktreesfalse

How it works:

  1. Fetches origin once
  2. Processes branches in topological order (parents before children)
  3. For root branches (parent is main): rebases onto origin/main
  4. For stacked branches: rebases onto the local parent (which was just synced)
  5. 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.

FlagDescription
--jsonJSON 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

ww dashboard

FlagDescription
-i, --intervalRefresh interval in seconds (default: 2)
--no-timelineHide the timeline sparkline column
KeyAction
j/kNavigate rows
EnterSwitch to tmux session
tToggle timeline column
rRefresh
q / Ctrl-CQuit

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
FlagDescription
--branchFilter by branch name
-r, --repoFilter by repo name
--sinceShow events after duration (e.g. 7d, 24h, 30m)
-n, --limitMax events to show (default 20)
--jsonJSON output

Event types:

ActionTrigger
createWorktree created via ww new or ww checkout
removeWorktree removed via ww rm
syncBranch 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
FlagDescriptionDefault
--pruneInteractively remove merged worktreesfalse
--dry-runShow what would be cleaned up without removing anythingfalse

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
FlagDescriptionDefault
--nameWorktree/branch nameAuto-slugified from prompt (e.g. dispatch--fix-the-login-validation)
-r, --repoTarget repo by nameAuto-detected from cwd
-b, --baseBase branch to fork fromConfig default
--no-fetchSkip fetching from remotefalse
--yoloRun Claude with --dangerously-skip-permissionsfalse

How it works:

  1. Creates a new worktree (reuses ww new internally)
  2. Launches claude "<prompt>" (interactive session with prompt pre-loaded)
  3. Logs a dispatch event (visible in ww 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.

  1. Creates the status directory: <willow-base>/status/
  2. Registers the hidden willow hook subcommand for every event willow tracks. The absolute binary path is resolved via os.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 session
  • start_time — when the session first became active
  • files_touched — files written/edited by the agent (stored in a .files sidecar)

Agent status

After running ww cc-setup, Claude Code automatically reports its state:

IconStatusMeaning
🤖BUSYAgent is actively working
DONEAgent finished its turn
WAITAgent is waiting for user input
🟡IDLEAgent 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 config

ww config show

FlagDescriptionDefault
--jsonOutput raw JSONfalse
-r, --repoTarget repo by nameAuto-detected from cwd

ww config edit

FlagDescriptionDefault
--globalEdit global config (default)true
--localEdit local (per-repo) configfalse
-r, --repoTarget repo by nameAuto-detected from cwd

Editor resolution: $VISUAL > $EDITOR > vi.

ww config init

FlagDescriptionDefault
--localCreate local (per-repo) configfalse
-r, --repoTarget repo by nameAuto-detected from cwd
--forceOverwrite existing configfalse

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
FlagDescriptionDefault
--dry-runShow the planned move without changing anythingfalse
-y, --yesSkip confirmationfalse

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
CheckDetails
git versionWarns if < 2.30
gh CLIOptional — warns if missing
tmuxOptional — warns if missing
Claude Code hooksChecks ~/.claude/settings.json for willow hooks; flags unmarked legacy entries from older releases
Willow directories<willow-base>, repos/, worktrees/
Stale sessionsSession files older than 30 minutes
Config validityValidates 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 add

Setup: ww tmux install prints the config to add to ~/.tmux.conf, including a prefix + w keybinding for the picker popup.

Aliases

AliasCommand
ww nww new
ww coww checkout
ww lww ls
ww sww status
ww dash / ww dww dashboard
ww stack sww 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)