Configuration

Config file locations

Willow resolves its base directory with this precedence:

  1. WILLOW_BASE_DIR
  2. global config baseDir
  3. default ~/.willow

Other config merges two tiers (local wins):

PriorityPathScope
1 (highest)<willow-base>/repos/<repo>.git/willow.jsonPer-repo, local only
2~/.config/willow/config.jsonGlobal defaults

The local config lives inside the bare repo directory, so it's private to your machine. Global config provides machine-wide defaults for all repos. baseDir is global-only so willow can resolve <willow-base> before it tries to load repo-local config.

<willow-base> defaults to ~/.willow. Move an existing setup with ww migrate-base &lt;path&gt;.

Config schema

{
  "baseDir": "~/code/willow",
  "baseBranch": "main",
  "branchPrefix": "alice",
  "postCheckoutHook": ".husky/post-checkout",
  "setup": ["npm install", "cp .env.example .env"],
  "teardown": [],
  "defaults": {
    "fetch": true,
    "autoSetupRemote": true
  },
  "notify": {
    "desktop": true,
    "command": ""
  },
  "agent": {
    "default": "cursor",
    "harnesses": {
      "cursor": {
        "command": "cursor-agent",
        "args": []
      }
    }
  },
  "tmux": {
    "notification": true,
    "notifyCommand": "afplay /System/Library/Sounds/Glass.aiff",
    "switcherPreview": true,
    "layout": ["split-window -h"],
    "panes": [
      {},
      { "command": "dev sync --only install_system_deps" }
    ]
  },
  "telemetry": true
}

Fields

FieldTypeDescription
baseDirstringGlobal-only willow base directory. Also overridable with WILLOW_BASE_DIR
baseBranchstringDefault branch to fork new worktrees from
branchPrefixstringPrefix for new branch names (e.g. alicealice/feature-auth)
postCheckoutHookstringScript to run after creating a worktree
setupstring[]Commands to run after creating a worktree (e.g. install deps)
teardownstring[]Commands to run before removing a worktree
defaults.fetchbooleanWhether to fetch before creating a worktree
defaults.autoSetupRemotebooleanAuto-configure remote tracking for new branches
notify.desktopbooleanSend desktop notifications from agent hooks when an agent finishes or needs input (default: true)
notify.commandstringCustom command for non-tmux notifications. Receives WILLOW_NOTIFY_TITLE and WILLOW_NOTIFY_BODY
agent.defaultstringDefault harness for ww dispatch and tmux Ctrl-G (claude, codex, or cursor; default: claude)
agent.harnesses.<id>.commandstringOverride the executable used for a harness
agent.harnesses.<id>.argsstring[]Extra args passed before the prompt when launching a harness
agent.harnesses.<id>.yoloArgsstring[]Override the args used by --yolo for that harness
tmux.notificationbooleanPlay sound on BUSY→DONE transitions (default: true)
tmux.notifyCommandstringCommand to run for notifications (default: afplay Glass.aiff)
tmux.switcherPreviewbooleanShow the right-side live preview in the tmux picker. Set to false for an fzf-only picker and compact ww tmux install popup binding (default: true)
tmux.layoutstring[]Raw tmux subcommands to run after session creation (e.g. ["split-window -h", "select-layout even-horizontal"])
tmux.panesPaneConfig[]Per-pane commands, indexed by pane order. Pane 0 is the initial pane, pane 1 is the first split, etc. Each entry: { "command": "..." }
telemetrybooleanEnable/disable anonymous error telemetry. Willow is opt-in by default. Also controllable via WILLOW_TELEMETRY=off env var

When telemetry is enabled, Willow only reports system errors and panics, along with basic context like the failing command and elapsed time.

Directory structure

<willow-base>/
├── repos/                       # Bare clones
│   └── myrepo.git/
│       └── willow.json          # Per-repo config
├── worktrees/                   # All worktrees, grouped by repo
│   └── myrepo/
│       ├── main/
│       ├── auth-refactor/
│       └── payments/
└── status/                      # Agent status
    └── myrepo/
        └── auth-refactor/
            ├── claude/
            │   └── <session_id>.json
            ├── codex/
            │   └── <session_id>.json
            └── cursor/
                └── <session_id>.json

<willow-base>/repos/

Contains bare git clones. Each repo is cloned once with ww clone and shared across all worktrees. Per-repo config (willow.json) lives here.

<willow-base>/worktrees/

Each worktree is a fully isolated directory with its own working copy. Grouped by repo name — <willow-base>/worktrees/<repo>/<branch>/.

<willow-base>/status/

Created by ww cc-setup, ww codex-setup, ww cursor-setup, or ww agent setup. Contains JSON files with agent status for each worktree and harness.

// <willow-base>/status/myrepo/auth-refactor/codex/<session_id>.json
{
  "harness": "codex",
  "session_id": "<session_id>",
  "status": "BUSY",
  "timestamp": "2024-01-15T10:30:00Z",
  "worktree": "auth-refactor",
  "model": "gpt-5.5",
  "turn_id": "turn_123"
}

Agent hooks

ww cc-setup registers the hidden willow hook --harness claude subcommand in ~/.claude/settings.json. ww codex-setup registers willow hook --harness codex in ~/.codex/hooks.json. ww cursor-setup registers willow hook --harness cursor in ~/.cursor/hooks.json. When an agent fires a hook event, it invokes the willow binary directly — there is no intermediate shell script or background daemon.

Codex treats user-level command hooks as reviewable hooks. If Codex warns about hooks that need review, open /hooks inside the Codex CLI and trust the Willow hook.

Cursor hooks are installed as direct hook entries with a short timeout and fail open. Willow preserves unrelated Cursor hook rules and replaces only earlier Willow Cursor entries.