Configuration
Config file locations
Willow resolves its base directory with this precedence:
WILLOW_BASE_DIR- global config
baseDir - default
~/.willow
Other config merges two tiers (local wins):
| Priority | Path | Scope |
|---|---|---|
| 1 (highest) | <willow-base>/repos/<repo>.git/willow.json | Per-repo, local only |
| 2 | ~/.config/willow/config.json | Global 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 <path>.
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
| Field | Type | Description |
|---|---|---|
baseDir | string | Global-only willow base directory. Also overridable with WILLOW_BASE_DIR |
baseBranch | string | Default branch to fork new worktrees from |
branchPrefix | string | Prefix for new branch names (e.g. alice → alice/feature-auth) |
postCheckoutHook | string | Script to run after creating a worktree |
setup | string[] | Commands to run after creating a worktree (e.g. install deps) |
teardown | string[] | Commands to run before removing a worktree |
defaults.fetch | boolean | Whether to fetch before creating a worktree |
defaults.autoSetupRemote | boolean | Auto-configure remote tracking for new branches |
notify.desktop | boolean | Send desktop notifications from agent hooks when an agent finishes or needs input (default: true) |
notify.command | string | Custom command for non-tmux notifications. Receives WILLOW_NOTIFY_TITLE and WILLOW_NOTIFY_BODY |
agent.default | string | Default harness for ww dispatch and tmux Ctrl-G (claude, codex, or cursor; default: claude) |
agent.harnesses.<id>.command | string | Override the executable used for a harness |
agent.harnesses.<id>.args | string[] | Extra args passed before the prompt when launching a harness |
agent.harnesses.<id>.yoloArgs | string[] | Override the args used by --yolo for that harness |
tmux.notification | boolean | Play sound on BUSY→DONE transitions (default: true) |
tmux.notifyCommand | string | Command to run for notifications (default: afplay Glass.aiff) |
tmux.switcherPreview | boolean | Show 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.layout | string[] | Raw tmux subcommands to run after session creation (e.g. ["split-window -h", "select-layout even-horizontal"]) |
tmux.panes | PaneConfig[] | Per-pane commands, indexed by pane order. Pane 0 is the initial pane, pane 1 is the first split, etc. Each entry: { "command": "..." } |
telemetry | boolean | Enable/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.