wt hook

Run hooks independently of normal worktree operations.

Hooks normally run automatically during wt switch --create, wt merge, and wt remove. This command runs them on demand — useful for testing hooks during development, running in CI pipelines, or re-running after a failure.

Both user hooks (from ~/.config/worktrunk/config.toml) and project hooks (from .config/wt.toml) are supported.

wt hook pre-merge           # Run pre-merge hooks
wt hook pre-merge --force   # Skip approval prompts (for CI)

Hook types

HookWhenBlockingFail-fast
post-createAfter worktree createdYesNo
post-startAfter worktree createdNo (background)No
pre-commitBefore commit during mergeYesYes
pre-mergeBefore merging to targetYesYes
post-mergeAfter successful mergeYesNo
pre-removeBefore worktree removedYesYes

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

post-create

Runs after worktree creation, blocks until complete. The worktree switch doesn't finish until these commands succeed.

Use cases: Installing dependencies, database migrations, copying environment files.

[post-create]
install = "npm ci"
migrate = "npm run db:migrate"
env = "cp .env.example .env"

post-start

Runs after worktree creation, in background. The worktree switch completes immediately; these run in parallel.

Use cases: Long builds, dev servers, file watchers, downloading large assets.

[post-start]
build = "npm run build"
server = "npm run dev"

Output logged to .git/wt-logs/{branch}-{source}-post-start-{name}.log (source is user or project).

pre-commit

Runs before committing during wt merge, fail-fast. All commands must exit 0 for the commit to proceed.

Use cases: Formatters, linters, type checking.

[pre-commit]
format = "cargo fmt -- --check"
lint = "cargo clippy -- -D warnings"

pre-merge

Runs before merging to target branch, fail-fast. All commands must exit 0 for the merge to proceed.

Use cases: Tests, security scans, build verification.

[pre-merge]
test = "cargo test"
build = "cargo build --release"

post-merge

Runs after successful merge in the main worktree, best-effort. Failures are logged but don't abort.

Use cases: Deployment, notifications, installing updated binaries.

post-merge = "cargo install --path ."

pre-remove

Runs before worktree removal during wt remove, fail-fast. All commands must exit 0 for removal to proceed.

Use cases: Cleanup tasks, saving state, notifying external systems.

[pre-remove]
cleanup = "rm -rf /tmp/cache/{{ branch }}"

Timing during merge

See wt merge for the complete pipeline.

Configuration

Hooks are defined in .config/wt.toml. They can be a single command or multiple named commands:

# Single command (string)
post-create = "npm install"

# Multiple commands (table) — run sequentially in declaration order
[pre-merge]
test = "cargo test"
build = "cargo build --release"

Template variables

Hooks can use template variables that expand at runtime:

VariableExampleDescription
{{ repo }}my-projectRepository name
{{ branch }}feature-fooBranch name
{{ worktree }}/path/to/worktreeAbsolute worktree path
{{ worktree_name }}my-project.feature-fooWorktree directory name
{{ repo_root }}/path/to/mainRepository root path
{{ default_branch }}mainDefault branch name
{{ commit }}a1b2c3d4e5f6...Full HEAD commit SHA
{{ short_commit }}a1b2c3dShort HEAD commit SHA
{{ remote }}originPrimary remote name
{{ remote_url }}git@github.com:user/repo.gitRemote URL
{{ upstream }}origin/featureUpstream tracking branch
{{ target }}mainTarget branch (merge hooks only)

JSON context

Hooks also receive context as JSON on stdin, enabling hooks in any language:

import json, sys
ctx = json.load(sys.stdin)
print(f"Setting up {ctx['repo']} on branch {ctx['branch']}")

The JSON includes all template variables plus hook_type and hook_name.

Security

Project commands require approval on first run:

🟡 repo needs approval to execute 3 commands:

⚪ post-create install:
   echo 'Installing dependencies...'

❓ Allow and remember? [y/N]

Manage approvals with wt hook approvals add and wt hook approvals clear.

User hooks

Define hooks in ~/.config/worktrunk/config.toml to run for all repositories. User hooks run before project hooks and don't require approval.

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

[pre-merge]
notify = "notify-send 'Merging {{ branch }}'"

User hooks support the same hook types and template variables as project hooks.

Key differences from project hooks:

AspectProject hooksUser hooks
Location.config/wt.toml~/.config/worktrunk/config.toml
ScopeSingle repositoryAll repositories
ApprovalRequiredNot required
Execution orderAfter user hooksBefore project hooks

Skip hooks with --no-verify.

Use cases:

Filtering by repository:

User hooks receive JSON context on stdin, enabling repository-specific behavior:

# ~/.config/worktrunk/config.toml
[post-create]
gitlab-setup = """
python3 -c '
import json, sys, subprocess
ctx = json.load(sys.stdin)
if "gitlab" in ctx.get("remote", ""):
    subprocess.run(["glab", "mr", "create", "--fill"])
'
"""

Examples

Node.js / TypeScript

[post-create]
install = "npm ci"

[post-start]
dev = "npm run dev"

[pre-commit]
lint = "npm run lint"
typecheck = "npm run typecheck"

[pre-merge]
test = "npm test"
build = "npm run build"

Rust

[post-create]
build = "cargo build"

[pre-commit]
format = "cargo fmt -- --check"
clippy = "cargo clippy -- -D warnings"

[pre-merge]
test = "cargo test"
build = "cargo build --release"

[post-merge]
install = "cargo install --path ."

Python (uv)

[post-create]
install = "uv sync"

[pre-commit]
format = "uv run ruff format --check ."
lint = "uv run ruff check ."

[pre-merge]
test = "uv run pytest"
typecheck = "uv run mypy ."

Monorepo

[post-create]
frontend = "cd frontend && npm ci"
backend = "cd backend && cargo build"

[post-start]
database = "docker-compose up -d postgres"

[pre-merge]
frontend-tests = "cd frontend && npm test"
backend-tests = "cd backend && cargo test"

Common patterns

Fast dependencies + slow build — Install blocking, build in background:

post-create = "npm install"
post-start = "npm run build"

Progressive validation — Quick checks before commit, thorough validation before merge:

[pre-commit]
lint = "npm run lint"
typecheck = "npm run typecheck"

[pre-merge]
test = "npm test"
build = "npm run build"

Target-specific behavior:

post-merge = """
if [ "{{ target }}" = "main" ]; then
    npm run deploy:production
elif [ "{{ target }}" = "staging" ]; then
    npm run deploy:staging
fi
"""

Symlinks and caches — The {{ repo_root }} variable points to the main worktree:

[post-create]
cache = "ln -sf {{ repo_root }}/node_modules node_modules"
env = "cp {{ repo_root }}/.env.local .env"

See also

Command reference

wt hook - Run hooks independently

Usage: wt hook [OPTIONS] <COMMAND>

Commands:
  show         Show configured hooks
  post-create  Run post-create hooks
  post-start   Run post-start hooks
  pre-commit   Run pre-commit hooks
  pre-merge    Run pre-merge hooks
  post-merge   Run post-merge hooks
  pre-remove   Run pre-remove hooks
  approvals    Manage command approvals

Options:
  -h, --help
          Print help (see a summary with '-h')

Global Options:
  -C <path>
          Working directory for this command

      --config <path>
          User config file path

  -v, --verbose
          Show commands and debug info

wt hook approvals

How Approvals Work

Commands from project hooks (.config/wt.toml) and LLM configuration require approval on first run. This prevents untrusted projects from running arbitrary commands.

Approval flow:

  1. Command is shown with expanded template variables
  2. User approves or denies
  3. Approved commands are saved to user config under [projects."project-id"]

When re-approval is required:

Bypassing prompts:

Examples

Pre-approve all commands for current project:

wt hook approvals add

Clear approvals for current project:

wt hook approvals clear

Clear global approvals:

wt hook approvals clear --global

Command reference

wt hook approvals - Manage command approvals

Usage: wt hook approvals [OPTIONS] <COMMAND>

Commands:
  add    Store approvals in config
  clear  Clear approved commands from config

Options:
  -h, --help
          Print help (see a summary with '-h')

Global Options:
  -C <path>
          Working directory for this command

      --config <path>
          User config file path

  -v, --verbose
          Show commands and debug info