Skip to content
Merged
2 changes: 1 addition & 1 deletion dev/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@
# worktree-path = ".worktrees/{{ branch | sanitize }}"
# list.full = true
# merge.squash = false
# post-create.env = "cp .env.example .env"
# pre-start.env = "cp .env.example .env"
# aliases.deploy = "make deploy BRANCH={{ branch }}"
#
# ### Custom prompt templates
Expand Down
4 changes: 2 additions & 2 deletions docs/content/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ command = "CLAUDECODE= MAX_THINKING_TOKENS=0 claude -p --no-session-persistence

```toml
# .config/wt.toml
[post-create]
[pre-start]
deps = "npm ci"

[pre-merge]
Expand Down Expand Up @@ -239,7 +239,7 @@ Override global user config for a specific project. Scalar values (like `worktre
worktree-path = ".worktrees/{{ branch | sanitize }}"
list.full = true
merge.squash = false
post-create.env = "cp .env.example .env"
pre-start.env = "cp .env.example .env"
aliases.deploy = "make deploy BRANCH={{ branch }}"
```

Expand Down
77 changes: 40 additions & 37 deletions docs/content/hook.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,15 @@ Hooks are shell commands that run at key points in the worktree lifecycle — au

# Hook Types

| Hook | When | Blocking | Fail-fast |
|------|------|----------|-----------|
| `pre-switch` | Before every switch | Yes | Yes |
| `post-create` | After worktree created | Yes | No |
| `post-start` | After worktree created | No | No |
| `post-switch` | After every switch | No | No |
| `pre-commit` | Before commit during merge | Yes | Yes |
| `pre-merge` | Before merging to target | Yes | Yes |
| `post-merge` | After successful merge | Yes | No |
| `pre-remove` | Before worktree removed | Yes | Yes |
| `post-remove` | After worktree removed | No | No |

**Blocking**: Command waits for hook to complete before continuing.
**Fail-fast**: First failure aborts the operation.

Background hooks show a single-line summary by default. Use `-v` to see expanded command details.
| Event | `pre-` (blocking) | `post-` (background) |
|-------|-------------------|---------------------|
| **start** | `pre-start` | `post-start` |
| **switch** | `pre-switch` | `post-switch` |
| **commit** | `pre-commit` | `post-commit` |
| **merge** | `pre-merge` | `post-merge` |
| **remove** | `pre-remove` | `post-remove` |

`pre-*` hooks block — failure aborts the operation (except `pre-start`, which warns and continues). `post-*` hooks run in the background. Use `-v` to see expanded command details for background hooks.

The most common starting point is `post-start` — it runs background tasks (dev servers, file copying, builds) when creating a worktree.

Expand All @@ -49,12 +42,12 @@ fi
"""
```

## post-create
## pre-start

Tasks that must complete before `post-start` hooks or `--execute` run: dependency installation, environment file generation.

```toml
[post-create]
[pre-start]
install = "npm ci"
env = "echo 'PORT={{ branch | hash_port }}' > .env.local"
```
Expand Down Expand Up @@ -88,6 +81,15 @@ format = "cargo fmt -- --check"
lint = "cargo clippy -- -D warnings"
```

## post-commit

Background tasks after a successful commit: CI triggers, notifications, background linting. Output logged to `.git/wt/logs/{branch}-{source}-post-commit-{name}.log`. Respects `--no-verify`.

```toml
[post-commit]
notify = "curl -s https://ci.example.com/trigger?branch={{ branch }}"
```

## pre-merge

Tests, security scans, build verification — runs after rebase, before merge to target.
Expand All @@ -100,7 +102,7 @@ build = "cargo build --release"

## post-merge

Deployment, notifications, installing updated binaries. Runs in the target branch worktree if it exists, otherwise the main worktree.
Deployment, notifications, installing updated binaries. Runs in background in the target branch worktree if it exists, otherwise the main worktree. Output logged to `.git/wt/logs/{branch}-{source}-post-merge-{name}.log`.

```toml
post-merge = "cargo install --path ."
Expand All @@ -125,7 +127,7 @@ kill-server = "lsof -ti :{{ branch | hash_port }} -sTCP:LISTEN | xargs kill 2>/d
remove-db = "docker stop {{ repo }}-{{ branch | sanitize }}-postgres 2>/dev/null || true"
```

During `wt merge`, hooks run in this order: pre-commit → pre-merge → pre-remove → post-remove → post-merge. See [`wt merge`](@/merge.md#pipeline) for the complete pipeline.
During `wt merge`, hooks run in this order: pre-commit → post-commit (background) → pre-merge → pre-remove → post-remove + post-merge (background). See [`wt merge`](@/merge.md#pipeline) for the complete pipeline.

# Security

Expand All @@ -134,7 +136,7 @@ Project commands require approval on first run:
```
▲ repo needs approval to execute 3 commands:

○ post-create install:
○ pre-start install:
echo 'Installing dependencies...'

❯ Allow and remember? [y/N]
Expand All @@ -157,7 +159,7 @@ Project hooks are defined in `.config/wt.toml`. They can be a single command or

```toml
# Single command (string)
post-create = "npm install"
pre-start = "npm install"

# Multiple commands (table) — run sequentially in declaration order
[pre-merge]
Expand All @@ -171,7 +173,7 @@ Define hooks in `~/.config/worktrunk/config.toml` to run for all repositories. U

```toml
# ~/.config/worktrunk/config.toml
[post-create]
[pre-start]
setup = "echo 'Setting up worktree...'"

[pre-merge]
Expand Down Expand Up @@ -219,7 +221,7 @@ Hooks can use template variables that expand at runtime:
| `{{ default_branch }}` | Default branch name |
| `{{ remote }}` | Primary remote name |
| `{{ remote_url }}` | Remote URL |
| `{{ hook_type }}` | Hook type being run (e.g. `post-create`, `pre-merge`) |
| `{{ hook_type }}` | Hook type being run (e.g. `pre-start`, `pre-merge`) |
| `{{ hook_name }}` | Hook command name (if named) |

Bare variables (`branch`, `worktree_path`, `commit`) refer to the branch the operation acts on: the destination for switch/create, the source for merge/remove. `base` and `target` give the other side:
Expand All @@ -235,7 +237,7 @@ Pre and post hooks share the same perspective — `{{ branch | hash_port }}` pro
Some variables are conditional: `upstream` requires remote tracking; `base`/`target` are only in two-worktree hooks. Undefined variables error — use conditionals:

```toml
[post-create]
[pre-start]
# Rebase onto upstream if tracking a remote branch (e.g., wt switch --create feature origin/feature)
sync = "{% if upstream %}git fetch && git rebase {{ upstream }}{% endif %}"
```
Expand Down Expand Up @@ -286,7 +288,7 @@ Templates also support functions for dynamic lookups:
The `worktree_path_of_branch` function returns the filesystem path of a worktree given a branch name, or an empty string if no worktree exists for that branch. This is useful for referencing files in other worktrees:

```toml
[post-create]
[pre-start]
# Copy config from main worktree
setup = "cp {{ worktree_path_of_branch('main') }}/config.local {{ worktree_path }}"
```
Expand All @@ -296,8 +298,8 @@ setup = "cp {{ worktree_path_of_branch('main') }}/config.local {{ worktree_path
Hooks receive all template variables as JSON on stdin, enabling complex logic that templates can't express:

```toml
[post-create]
setup = "python3 scripts/post-create-setup.py"
[pre-start]
setup = "python3 scripts/pre-start-setup.py"
```

```python
Expand All @@ -319,7 +321,7 @@ wt hook pre-merge project: # Run all project hooks
wt hook pre-merge user:test # Run only user's "test" hook
wt hook pre-merge project:test # Run only project's "test" hook
wt hook pre-merge --yes # Skip approval prompts (for CI)
wt hook post-create --var branch=feature/test # Override template variable
wt hook pre-start --var branch=feature/test # Override template variable
```

The `user:` and `project:` prefixes filter by source. Use `user:` or `project:` alone to run all hooks from that source, or `user:name` / `project:name` to run a specific hook.
Expand All @@ -328,14 +330,14 @@ The `--var KEY=VALUE` flag overrides built-in template variables — useful for

# Designing Effective Hooks

## post-start vs post-create
## post-start vs pre-start

Both run when creating a worktree. The difference:

| Hook | Execution | Best for |
|------|-----------|----------|
| `post-start` | Background, parallel | Long-running tasks that don't block worktree creation |
| `post-create` | Blocks until complete | Tasks the developer needs before working (dependency install) |
| `pre-start` | Blocks until complete | Tasks the developer needs before working (dependency install) |

Many tasks work well in `post-start` — they'll likely be ready by the time they're needed, especially when the fallback is recompiling. If unsure, prefer `post-start` for faster worktree creation.

Expand All @@ -350,10 +352,10 @@ Git worktrees share the repository but not untracked files. [`wt step copy-ignor
copy = "wt step copy-ignored"
```

Use `post-create` instead if subsequent hooks need the copied files — for example, copying `node_modules/` before `pnpm install` so the install reuses cached packages:
Use `pre-start` instead if subsequent hooks need the copied files — for example, copying `node_modules/` before `pnpm install` so the install reuses cached packages:

```toml
[post-create]
[pre-start]
copy = "wt step copy-ignored"
install = "pnpm install"
```
Expand Down Expand Up @@ -409,7 +411,7 @@ Jinja2's operator precedence has pipe `|` with higher precedence than concatenat
Generate `.env.local` with the connection string:

```toml
[post-create]
[pre-start]
env = """
cat > .env.local << EOF
DATABASE_URL=postgres://postgres:dev@localhost:{{ ('db-' ~ branch) | hash_port }}/{{ branch | sanitize_db }}
Expand Down Expand Up @@ -451,7 +453,7 @@ fi
Use `uv sync` to recreate virtual environments (or `python -m venv .venv && .venv/bin/pip install -r requirements.txt` for pip-based projects):

```toml
[post-create]
[pre-start]
install = "uv sync"
```

Expand All @@ -460,7 +462,7 @@ For copying dependencies and caches between worktrees, see [`wt step copy-ignore
## See also

- [`wt merge`](@/merge.md) — Runs hooks automatically during merge
- [`wt switch`](@/switch.md) — Runs post-create/post-start hooks on `--create`
- [`wt switch`](@/switch.md) — Runs pre-start/post-start hooks on `--create`
- [`wt config`](@/config.md) — Manage hook approvals
- [`wt config state logs`](@/config.md#wt-config-state-logs) — Access background hook logs

Expand All @@ -474,10 +476,11 @@ Usage: <b><span class=c>wt hook</span></b> <span class=c>[OPTIONS]</span> <span
<b><span class=g>Commands:</span></b>
<b><span class=c>show</span></b> Show configured hooks
<b><span class=c>pre-switch</span></b> Run pre-switch hooks
<b><span class=c>post-create</span></b> Run post-create hooks
<b><span class=c>pre-start</span></b> Run pre-start hooks
<b><span class=c>post-start</span></b> Run post-start hooks
<b><span class=c>post-switch</span></b> Run post-switch hooks
<b><span class=c>pre-commit</span></b> Run pre-commit hooks
<b><span class=c>post-commit</span></b> Run post-commit hooks
<b><span class=c>pre-merge</span></b> Run pre-merge hooks
<b><span class=c>post-merge</span></b> Run post-merge hooks
<b><span class=c>pre-remove</span></b> Run pre-remove hooks
Expand Down
4 changes: 2 additions & 2 deletions docs/content/step.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ Reflink copies share disk blocks until modified — no data is actually copied.

Uses per-file reflink (like `cp -Rc`) — copy time scales with file count.

Use the `post-start` hook so the copy runs in the background. Use `post-create` instead if subsequent hooks or `--execute` command need the copied files immediately.
Use the `post-start` hook so the copy runs in the background. Use `pre-start` instead if subsequent hooks or `--execute` command need the copied files immediately.

### Language-specific notes

Expand All @@ -397,7 +397,7 @@ The `target/` directory is huge (often 1-10GB). Copying with reflink cuts first
`node_modules/` is large but mostly static. If the project has no native dependencies, symlinks are even faster:

```toml
[post-create]
[pre-start]
deps = "ln -sf {{ primary_worktree_path }}/node_modules ."
```

Expand Down
2 changes: 1 addition & 1 deletion docs/content/switch.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ When creating a worktree, worktrunk:
1. Runs [pre-switch hooks](@/hook.md#pre-switch) (blocking, fail-fast)
2. Creates worktree at configured path
3. Switches to new directory
4. Runs [post-create hooks](@/hook.md#post-create) (blocking)
4. Runs [pre-start hooks](@/hook.md#pre-start) (blocking)
5. Spawns [post-start hooks](@/hook.md#post-start) (background)
6. Spawns [post-switch hooks](@/hook.md#post-switch) (background)

Expand Down
4 changes: 2 additions & 2 deletions skills/worktrunk/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ command = "CLAUDECODE= MAX_THINKING_TOKENS=0 claude -p --no-session-persistence

```toml
# .config/wt.toml
[post-create]
[pre-start]
deps = "npm ci"

[pre-merge]
Expand Down Expand Up @@ -230,7 +230,7 @@ Override global user config for a specific project. Scalar values (like `worktre
worktree-path = ".worktrees/{{ branch | sanitize }}"
list.full = true
merge.squash = false
post-create.env = "cp .env.example .env"
pre-start.env = "cp .env.example .env"
aliases.deploy = "make deploy BRANCH={{ branch }}"
```

Expand Down
Loading
Loading