Files
max-sixty__worktrunk/docs/content/step.md
Maximilian Roos 5c42c5b7d5 feat: machine-readable approval state and branch-removal outcomes (#3710)
Two of the five machine-readable-output requests NathanaelRea opened
(#3696–#3700), reviewed as a set and implemented where the gap was real.

## `wt config approvals list --format=json` (#3698)

The command already computed the four distinctions an orchestrator needs
— no commands, approved, approval-required, and stale — read-only,
without prompting or writing. It had no `--format` flag, so the only way
to learn that a non-interactive run would stop for approval was to run
the operation and catch `NotInteractive`, or to pass `--yes` and approve
whatever was there.

```json
{
  "state": "approval_required",
  "commands": [
    {"phase": "post-start", "name": "dev", "template": "npm run dev", "approved": false},
    {"phase": "pre-merge", "template": "cargo test", "approved": true}
  ],
  "stale": ["some removed command"]
}
```

`state` is what a caller branches on. `stale` stays a separate list
rather than a fourth `state`, because it co-occurs with all three — and
those are the approvals `--yes` would silently re-approve after their
command template changed, which is exactly what an orchestrator
preserving the approval model needs to see.

A flag on the existing read command rather than a new `status` verb,
matching `wt config show`, `wt config state get`, `wt config state
logs`, and `wt list`.

Closes #3698.

## `branch_outcome` on removal (#3700, partly)

`wt remove --format=json` reported the branch as one boolean, collapsing
five internal outcomes into two values:

| Internal outcome | `branch_deleted` was |
|---|---|
| `Deleted` | `true` |
| `Deferred` — handed to a detached process, result never observed |
`true` |
| `NotAttempted` — no branch, or `--no-delete-branch` | `false` |
| `Retained` — a sibling worktree has it checked out | `false` |
| `Retained` — **the CAS refused; the ref moved under us** | `false` |

The last row is the exact race #3700 asks for protection against.
Worktrunk already deletes with `git update-ref -d <ref> <oid>` and
already fails closed when the ref has moved — then reported it as the
same `false` that means "you asked me not to". And `Deferred` reported
`true` on intent.

`branch_outcome` names it instead: `deleted`, `deferred`,
`not_attempted`, `retained_unmerged`, `retained_checked_out`,
`retained_raced`, `retained_failed`. A caller that sees `retained_raced`
knows to re-read the ref and retry, which is what the guard detects it
for.

**This does not close #3700.** That issue asks for an *input* — a
caller-supplied expected OID that makes `wt` fail closed against the
orchestrator's own observation. This is an *output*. They land in the
same place on the default path, because the integration check already
refuses to delete unintegrated content, so the caller was never going to
lose commits — they just couldn't classify the refusal. Where the gap is
real is `--force-delete` / `-D`, which takes the early return in
`delete_branch_if_safe` and runs `git branch -D` with no integration
check and no CAS. If an `--expected-oid` flag lands, it has to gate that
path.

## Notes

- **Output-format break.** `branch_deleted` is replaced, not
supplemented, on `wt remove --format=json` and on `wt step prune
--format=json`'s live path. Per CLAUDE.md, output formatting is on the
flexible side of the interface line; flagging it here so the release
changelog picks it up.
- **`wt step prune --dry-run` keeps `branch_deleted`.** A dry run
predicts; it runs nothing to have an outcome. Different thing, different
name, documented as such.
- **`retained_raced` and `retained_checked_out` have no deterministic
CLI trigger.** Both come from windows between `wt`'s own fresh read and
the ref mutation, which no hook can be scheduled inside. They're covered
at the unit level (`branch_fate_from_result_mapping`,
`branch_fate_json_outcome_is_distinct_per_fate`, and
`cas_rejects_delete_when_branch_advances` in `src/git/remove.rs`, which
drives the race with a stale snapshot). The integration tests cover the
two reachable contrasts: `retained_unmerged` via a `pre-remove` hook
that commits, and `not_attempted` via `--no-delete-branch`.
- **`print_json` lives under `src/commands/list/`** and now has a third
caller from outside that module. Worth a more central home; not moved
here.

## The other three

Reviewed but not implemented:

- **#3696** — already possible. `wt --config-set 'list.json-schema = 2'
list --format=json` pins the schema per invocation above every config
layer, as does `WORKTRUNK_LIST__JSON_SCHEMA`. Answered on the issue;
what's left is a docs gap and making an out-of-range value fail rather
than degrade in JSON mode.
- **#3697** — the machine-readable error channel. A real gap and the one
policy call in the set; not started.
- **#3699** — aimed at `wt config state logs --format=json`, which is a
directory listing reconstructed from paths, under a model that
overwrites. The append-only run record it wants is `commands.jsonl`.

## Testing

`cargo run -- hook pre-merge --yes` green: 4533 tests, clippy, fmt,
doctests, rustdoc, docs sync.

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 11:17:27 -07:00

1186 lines
48 KiB
Markdown

+++
title = "wt step"
description = "Run individual operations. The building blocks of wt merge — commit, squash, rebase, push — plus standalone utilities."
weight = 16
[extra]
group = "Commands"
+++
<!-- ⚠️ AUTO-GENERATED from `wt step --help-page` — edit src/cli/mod.rs to update -->
Run individual operations. The building blocks of wt merge — commit, squash, rebase, push — plus standalone utilities.
## Examples
Commit with LLM-generated message:
{% terminal(cmd="wt step commit") %}
<span class=c>◎</span> <span class=c>Generating commit message and committing changes... <span style='color:var(--bright-black,#555)'>(2 files, <span class=g>+26</span></span></span><span style='color:var(--bright-black,#555)'>)</span>
<span style='background:var(--bright-white,#fff)'> </span> <b>feat(validation): add input validation utilities</b>
<span class=g>✓</span> <span class=g>Committed changes @ <span class=d>a1b2c3d</span></span>
{% end %}
Manual merge workflow with review between steps:
{{ terminal(cmd="wt step commit|||wt step squash|||wt step rebase|||wt step push") }}
## Operations
- [`commit`](#wt-step-commit) — Stage and commit with [LLM-generated message](@/llm-commits.md)
- [`squash`](#wt-step-squash) — Squash all branch commits into one with [LLM-generated message](@/llm-commits.md)
- [`rebase`](#wt-step-rebase) — Rebase onto target branch
- [`push`](#wt-step-push) — Fast-forward target to current branch
- [`diff`](#wt-step-diff) — Show all changes since branching (committed, staged, unstaged, untracked)
- [`copy-ignored`](#wt-step-copy-ignored) — Copy gitignored files between worktrees
- [`eval`](#wt-step-eval) — <span class="badge-experimental"></span> Evaluate a template expression
- [`for-each`](#wt-step-for-each) — <span class="badge-experimental"></span> Run a command in every worktree
- [`promote`](#wt-step-promote) — <span class="badge-experimental"></span> Swap a branch into the main worktree
- [`prune`](#wt-step-prune) — Remove worktrees and branches merged into the default branch
- [`relocate`](#wt-step-relocate) — <span class="badge-experimental"></span> Move worktrees to expected paths
- [`tether`](#wt-step-tether) — <span class="badge-experimental"></span> Run a command; kill its whole process tree when its worktree is removed
- [`<alias>`](@/extending.md#aliases) — Run a configured command alias
## See also
- [`wt merge`](@/merge.md) — Runs commit → squash → rebase → hooks → push → cleanup automatically
- [`wt hook`](@/hook.md) — Run configured hooks
- [Aliases](@/extending.md#aliases) — Custom command templates run as `wt <name>`
## Command reference
{% terminal() %}
wt step - Run individual operations
The building blocks of <b>wt merge</b> — commit, squash, rebase, push — plus standalone utilities.
Usage: <b><span class=c>wt step</span></b> <span class=c>[OPTIONS]</span> <span class=c>&lt;COMMAND&gt;</span>
<b><span class=g>Commands:</span></b>
<b><span class=c>commit</span></b> Stage and commit with LLM-generated message
<b><span class=c>squash</span></b> Squash commits since branching
<b><span class=c>rebase</span></b> Rebase onto target
<b><span class=c>push</span></b> Fast-forward target to current branch
<b><span class=c>diff</span></b> Show all changes since branching
<b><span class=c>copy-ignored</span></b> Copy gitignored files to another worktree
<b><span class=c>eval</span></b> [experimental] Evaluate a template expression
<b><span class=c>for-each</span></b> [experimental] Run command in each worktree
<b><span class=c>promote</span></b> [experimental] Swap a branch into the main worktree
<b><span class=c>prune</span></b> [experimental] Remove worktrees merged into the default branch
<b><span class=c>relocate</span></b> [experimental] Move worktrees to expected paths
<b><span class=c>tether</span></b> [experimental] Run a command; kill its whole process tree when its worktree is
removed
<b><span class=g>Options:</span></b>
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
# Subcommands
## wt step commit
Stage and commit with LLM-generated message.
See [LLM-generated commit messages](@/llm-commits.md) for configuration and prompt customization.
### Options
#### Staging
Controls what to stage before committing:
| Value | Behavior |
|-------|----------|
| `all` | Stage all changes including untracked files (default) |
| `tracked` | Stage only modified tracked files |
| `none` | Don't stage anything, commit only what's already staged |
{{ terminal(cmd="wt step commit --stage=tracked") }}
Configure the default in user config:
```toml
[commit]
stage = "tracked"
```
#### Dry run
Render the prompt, print the LLM command, generate the message, and exit without staging, running hooks, or committing:
{{ terminal(cmd="wt step commit --dry-run") }}
Three sections are printed: the rendered prompt, the shell command that would invoke the LLM, and the message returned. The LLM call still happens — only the commit is skipped.
### Command reference
{% terminal() %}
wt step commit - Stage and commit with LLM-generated message
Usage: <b><span class=c>wt step commit</span></b> <span class=c>[OPTIONS]</span>
<b><span class=g>Options:</span></b>
<b><span class=c>-b</span></b>, <b><span class=c>--branch</span></b><span class=c> &lt;BRANCH&gt;</span>
Branch to operate on (defaults to current worktree)
<b><span class=c>--stage</span></b><span class=c> &lt;STAGE&gt;</span>
What to stage before committing [default: all]
Possible values:
- <b><span class=c>all</span></b>: Stage everything: untracked files + unstaged tracked changes
- <b><span class=c>tracked</span></b>: Stage tracked changes only (like <b>git add -u</b>)
- <b><span class=c>none</span></b>: Stage nothing, commit only what&#39;s already in the index
<b><span class=c>--dry-run</span></b>
Preview prompt, command, and generated message without committing
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Automation:</span></b>
<b><span class=c>--no-hooks</span></b>
Skip hooks
<b><span class=c>--format</span></b><span class=c> &lt;FORMAT&gt;</span>
Output format
JSON prints structured result to stdout after the commit completes.
[default: text]
[possible values: text, json]
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
## wt step squash
Squash commits since branching. Stages changes and generates message with LLM.
See [LLM-generated commit messages](@/llm-commits.md) for configuration and prompt customization.
### Options
#### Staging
Controls what to stage before squashing:
| Value | Behavior |
|-------|----------|
| `all` | Stage all changes including untracked files (default) |
| `tracked` | Stage only modified tracked files |
| `none` | Don't stage anything, squash only committed changes |
{{ terminal(cmd="wt step squash --stage=none") }}
Configure the default in user config:
```toml
[commit]
stage = "tracked"
```
#### Dry run
Render the prompt, print the LLM command, generate the squash message, and exit without resetting, running hooks, or committing:
{{ terminal(cmd="wt step squash --dry-run") }}
Three sections are printed: the rendered prompt, the shell command that would invoke the LLM, and the message returned. The LLM call still happens — only the squash and commit are skipped.
### Command reference
{% terminal() %}
wt step squash - Squash commits since branching
Stages changes and generates message with LLM.
Usage: <b><span class=c>wt step squash</span></b> <span class=c>[OPTIONS]</span> <span class=c>[TARGET]</span>
<b><span class=g>Arguments:</span></b>
<span class=c>[TARGET]</span>
Target branch
Defaults to default branch.
<b><span class=g>Options:</span></b>
<b><span class=c>--stage</span></b><span class=c> &lt;STAGE&gt;</span>
What to stage before committing [default: all]
Possible values:
- <b><span class=c>all</span></b>: Stage everything: untracked files + unstaged tracked changes
- <b><span class=c>tracked</span></b>: Stage tracked changes only (like <b>git add -u</b>)
- <b><span class=c>none</span></b>: Stage nothing, commit only what&#39;s already in the index
<b><span class=c>--dry-run</span></b>
Preview prompt, command, and generated message without squashing
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Automation:</span></b>
<b><span class=c>--no-hooks</span></b>
Skip hooks
<b><span class=c>--format</span></b><span class=c> &lt;FORMAT&gt;</span>
Output format
JSON prints structured result to stdout after the squash completes.
[default: text]
[possible values: text, json]
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
## wt step rebase
Rebase onto target.
A rebase puts the branch's commits on top of the target, which is what [`wt step push`](#wt-step-push) needs — a push fast-forwards only if the target is an ancestor of the branch. `wt merge` runs this step as part of its pipeline; on its own it brings a branch up to date with a target that has moved, without merging into it.
The target is any commit: a branch, a tag, a SHA.
### Examples
{{ terminal(cmd="wt step rebase # Rebase onto default branch|||wt step rebase develop # Rebase onto develop|||wt step rebase v1.2.0 # Rebase onto a tag") }}
### Outcomes
The first matching row wins:
| Branch and target | Result |
|-------------------|--------|
| The target is already an ancestor of the branch, with no merge commit in between | Nothing runs — `Already up to date` |
| The branch is an ancestor of the target, so it has no commits of its own | `Fast-forwarded to <target>` |
| Otherwise | The branch's commits replay onto the target's tip — refused outright if the two share no history |
A branch that merged the target into itself still rebases: the target is its ancestor, but the merge commit in between keeps the first row from applying.
When the target's local ref lags its upstream, the rows are measured against that upstream, which the result then names in place of the argument. [`wt merge`](@/merge.md) covers why.
### Conflicts
A conflicting commit leaves the rebase open rather than undoing it. The worktree keeps git's conflict markers, and the ways out are `git rebase --continue` once the conflict is resolved, `git rebase --skip`, or `git rebase --abort`. Until the rebase is settled, `wt step rebase`, `wt step squash`, `wt step push`, and `wt merge` refuse to run — as they do while any other git operation is open, a conflicted `git merge` included.
### Command reference
{% terminal() %}
wt step rebase - Rebase onto target
Usage: <b><span class=c>wt step rebase</span></b> <span class=c>[OPTIONS]</span> <span class=c>[TARGET]</span>
<b><span class=g>Arguments:</span></b>
<span class=c>[TARGET]</span>
Target branch, tag, or commit
Defaults to default branch.
<b><span class=g>Options:</span></b>
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Automation:</span></b>
<b><span class=c>--format</span></b><span class=c> &lt;FORMAT&gt;</span>
Output format
JSON prints structured result to stdout after the rebase completes.
[default: text]
[possible values: text, json]
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
## wt step push
Fast-forward target to current branch.
Despite the name, no commits leave the repository. The target branch's ref moves forward locally, and a worktree holding that branch is updated along with it. Publishing is a separate `git push` to the remote afterward.
The target is a branch, and must already be an ancestor of the current branch. One that has moved ahead is refused, and there is no force variant — [`wt step rebase`](#wt-step-rebase) puts the branch back on top of it first.
### Examples
{{ terminal(cmd="wt step push # Fast-forward main to current branch|||wt step push develop # Fast-forward develop instead|||wt step push --no-ff # Merge commit instead of a fast-forward") }}
### Target worktree
When the target branch has a worktree of its own, that worktree's files move to the new commits too. Uncommitted changes there never move: the update carries any file the push doesn't touch — staged or not — exactly where it is, and a change touching a file the push does change is refused upfront, naming the file. If the sync can't be applied for any reason — a conflicting file appearing in the race window after the check, or a busy index — the update is rolled back whole, leaving branch and worktree as they were.
A worktree that is still registered but whose directory is gone is refused as well, since nothing can be synced into it — `git worktree prune` clears the registration.
### Command reference
{% terminal() %}
wt step push - Fast-forward target to current branch
Usage: <b><span class=c>wt step push</span></b> <span class=c>[OPTIONS]</span> <span class=c>[TARGET]</span>
<b><span class=g>Arguments:</span></b>
<span class=c>[TARGET]</span>
Target branch
Defaults to default branch.
<b><span class=g>Options:</span></b>
<b><span class=c>--no-ff</span></b>
Create a merge commit (no fast-forward)
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Automation:</span></b>
<b><span class=c>--format</span></b><span class=c> &lt;FORMAT&gt;</span>
Output format
JSON prints structured result to stdout after the push completes.
[default: text]
[possible values: text, json]
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
## wt step diff
Show all changes since branching. Includes committed, staged, unstaged, and untracked files.
This is what `wt merge` would include — a single diff against the merge base.
### Operating on another worktree
`--branch` diffs another worktree's branch without leaving the current one:
{{ terminal(cmd="wt step diff --branch feature") }}
The branch must have a checked-out worktree.
### Extra git diff arguments
Arguments after `--` are forwarded to `git diff`:
{{ terminal(cmd="wt step diff -- --stat|||wt step diff -- --name-only|||wt step diff -- -- '*.rs'") }}
The diff is pipeable to tools like `delta`:
{{ terminal(cmd="wt step diff | delta") }}
### How it works
Equivalent to:
{{ terminal(cmd="cp __WT_QUOT__$(git rev-parse --git-dir)/index__WT_QUOT__ /tmp/idx|||GIT_INDEX_FILE=/tmp/idx git add --intent-to-add .|||GIT_INDEX_FILE=/tmp/idx git diff $(git merge-base HEAD $(wt config state default-branch))") }}
`git diff` ignores untracked files. `git add --intent-to-add .` registers them in the index without staging their content, making them visible to `git diff`. This runs against a copy of the real index so the original is never modified.
### Command reference
{% terminal() %}
wt step diff - Show all changes since branching
Includes committed, staged, unstaged, and untracked files.
Usage: <b><span class=c>wt step diff</span></b> <span class=c>[OPTIONS]</span> <span class=c>[TARGET]</span> <b><span class=c>[--</span></b> <span class=c>&lt;EXTRA_ARGS&gt;...</span><b><span class=c>]</span></b>
<b><span class=g>Arguments:</span></b>
<span class=c>[TARGET]</span>
Target branch
Defaults to default branch.
<span class=c>[EXTRA_ARGS]...</span>
Extra arguments forwarded to <b>git diff</b>
<b><span class=g>Options:</span></b>
<b><span class=c>-b</span></b>, <b><span class=c>--branch</span></b><span class=c> &lt;BRANCH&gt;</span>
Branch to operate on (defaults to current worktree)
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
## wt step copy-ignored
Copy gitignored files to another worktree. Eliminates cold starts by copying build caches and dependencies.
### Setup
Add to the project config:
```toml
# .config/wt.toml
[post-start]
copy = "wt step copy-ignored"
```
### What gets copied
All gitignored files are copied by default, except for built-in excluded directories: VCS metadata (`.bzr/`, `.hg/`, `.jj/`, `.pijul/`, `.sl/`, `.svn/`), tool-state (`.conductor/`, `.entire/`, `.worktrees/`), and nested worktrees. Tracked files are never touched. Discovery handles nested `.gitignore` files, global excludes, and `.git/info/exclude`. Existing files in the destination are skipped, so re-running is safe; `--force` overwrites them.
To limit what gets copied further, create `.worktreeinclude` with gitignore-style patterns. Files must be **both** gitignored **and** in `.worktreeinclude`:
```text
# .worktreeinclude
.env
node_modules/
target/
```
After `.worktreeinclude` selects entries, you can add more gitignore-style excludes in user config, per-project user overrides, or project config:
```toml
[step.copy-ignored]
exclude = [".cache/", ".turbo/"]
```
To copy nothing unless `.worktreeinclude` exists — matching Claude Code desktop, where the file is required — pass `--require-include`:
{{ terminal(cmd="wt step copy-ignored --require-include") }}
Without `.worktreeinclude`, the command is a no-op (it reports that nothing was copied and why). With the file present, only matching files copy as above. To apply this across every repository, put the flag in a user-config hook: `post-start = "wt step copy-ignored --require-include"`.
### Common patterns
| Type | Patterns |
|------|----------|
| Dependencies | `node_modules/`, `.venv/`, `target/`, `vendor/`, `Pods/` |
| Build caches | `.cache/`, `.next/`, `.parcel-cache/`, `.turbo/` |
| Generated assets | Images, ML models, binaries too large for git |
| Environment files | `.env` (if not generated per-worktree) |
### Performance
Reflink copies share disk blocks until modified — no data is actually copied. For a 14GB `target/` directory:
| Command | Time |
|---------|------|
| `cp -R` (full copy) | 2m |
| `cp -Rc` / `wt step copy-ignored` | 20s |
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 `pre-start` instead if subsequent hooks or `--execute` command need the copied files immediately.
### Background-hook priority (experimental)
When invoked from a background hook pipeline (`post-*` hooks), `wt step copy-ignored` self-lowers its CPU and I/O priority — `taskpolicy -b` on macOS, `nice -n 19` plus `ionice -c 3` on Linux — so it yields to interactive work. Foreground callers (`pre-*` hooks, direct interactive use) run at normal priority so the user isn't waiting on a throttled copy.
wt signals background-hook context by exporting `WORKTRUNK_FOREGROUND=-1` into every detached hook pipeline; `copy-ignored` inspects that variable on entry. The variable name is experimental and may change.
### Language-specific notes
#### Rust
The `target/` directory is huge (often 1-10GB). Copying with reflink cuts first build from ~68s to ~3s by reusing compiled dependencies.
#### Node.js
`node_modules/` is large but mostly static. If the project has no native dependencies, symlinks are even faster:
```toml
[pre-start]
deps = "ln -sf {{ primary_worktree_path }}/node_modules ."
```
#### Python
Virtual environments contain absolute paths and can't be copied. Use `uv sync` instead — it's fast enough that copying isn't worth it.
### Behavior vs Claude Code on desktop
The `.worktreeinclude` pattern is shared with [Claude Code on desktop](https://code.claude.com/docs/en/desktop), which copies matching files when creating worktrees. Differences:
- worktrunk copies all gitignored files by default; Claude Code requires `.worktreeinclude`. Pass `--require-include` to match Claude Code (copy nothing without `.worktreeinclude`)
- worktrunk uses copy-on-write for large directories like `target/` (see Performance above)
- worktrunk runs as a configurable hook in the worktree lifecycle
### Command reference
{% terminal() %}
wt step copy-ignored - Copy gitignored files to another worktree
Eliminates cold starts by copying build caches and dependencies.
Usage: <b><span class=c>wt step copy-ignored</span></b> <span class=c>[OPTIONS]</span>
<b><span class=g>Options:</span></b>
<b><span class=c>--from</span></b><span class=c> &lt;FROM&gt;</span>
Source worktree branch
Defaults to main worktree.
<b><span class=c>--to</span></b><span class=c> &lt;TO&gt;</span>
Destination worktree branch
Defaults to current worktree.
<b><span class=c>--dry-run</span></b>
Show what would be copied
<b><span class=c>--force</span></b>
Overwrite existing files in destination
<b><span class=c>--require-include</span></b>
Require .worktreeinclude to copy anything
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Automation:</span></b>
<b><span class=c>--format</span></b><span class=c> &lt;FORMAT&gt;</span>
Output format
JSON prints structured result to stdout after the copy completes.
[default: text]
[possible values: text, json]
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
## wt step eval
<span class="badge-experimental"></span>
Evaluate a template expression. Prints the result to stdout for use in scripts and shell substitutions.
All [hook template variables and filters](@/hook.md#template-variables) are available.
### Examples
Get the port for the current branch:
{% terminal(cmd="wt step eval '__WT_OPEN__ branch | hash_port __WT_CLOSE__'") %}
16066
{% end %}
Use in shell substitution:
{{ terminal(cmd="curl http://localhost:$(wt step eval '__WT_OPEN__ branch | hash_port __WT_CLOSE__')/health") }}
Combine multiple values:
{% terminal(cmd="wt step eval '__WT_OPEN__ branch | hash_port __WT_CLOSE__,__WT_OPEN__ (__WT_QUOT__supabase-api-__WT_QUOT__ ~ branch) | hash_port __WT_CLOSE__'") %}
16066,16739
{% end %}
Use conditionals and filters:
{% terminal(cmd="wt step eval '__WT_OPEN__ branch | sanitize_db __WT_CLOSE__'") %}
feature_auth_oauth2_a1b
{% end %}
List the available template variables with `-v` (alongside the expansion, on stderr):
{% terminal(cmd="wt step eval -v '__WT_OPEN__ branch __WT_CLOSE__'") %}
○ eval template variables:
branch = feature/auth-oauth2
worktree_path = /home/user/projects/myapp-feature-auth-oauth2
○ eval source
{{ branch }}
○ eval result
feature/auth-oauth2
feature/auth-oauth2
{% end %}
### Command reference
{% terminal() %}
wt step eval - [experimental] Evaluate a template expression
Prints the result to stdout for use in scripts and shell substitutions.
Usage: <b><span class=c>wt step eval</span></b> <span class=c>[OPTIONS]</span> <span class=c>&lt;TEMPLATE&gt;</span>
<b><span class=g>Arguments:</span></b>
<span class=c>&lt;TEMPLATE&gt;</span>
Template expression to evaluate
<b><span class=g>Options:</span></b>
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Automation:</span></b>
<b><span class=c>--format</span></b><span class=c> &lt;FORMAT&gt;</span>
Output format
JSON prints <b>{name, template, result}</b> to stdout instead of the bare result.
[default: text]
[possible values: text, json]
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
## wt step for-each
<span class="badge-experimental"></span>
Run command in each worktree. Executes sequentially with real-time output; continues past command failures.
A summary of successes and failures is shown at the end. A template-expansion error (a malformed `{{ … }}` argument) aborts the whole run; only command failures are tolerated and reported. Context JSON — a flat object of every template variable — is piped to stdin for scripts that need structured data.
### Arguments
Arguments after `--` are the program and its arguments — run directly, no shell.
{{ terminal(cmd="wt step for-each -- git status --short|||wt step for-each -- npm install") }}
For pipes, redirects, variables, or globs, wrap in `sh -c`:
{{ terminal(cmd="wt step for-each -- sh -c 'git status | wc -l'|||wt step for-each -- sh -c 'echo $HOME && git pull'") }}
### Template variables
Variables substitute into each argv element before exec. See [`wt hook` template variables](@/hook.md#template-variables) for the complete list and filters.
{{ terminal(cmd="wt step for-each -- echo 'Branch: __WT_OPEN__ branch __WT_CLOSE__'") }}
Each element is expanded fresh in every worktree, so `{{ branch }}` is that worktree's branch. An alias wrapping for-each renders templates earlier, in the invoking worktree; [deferring expansion in an alias](@/extending.md#deferring-expansion-to-a-nested-wt-command) shows how to keep a variable per-worktree.
### Examples
Pull updates in worktrees with upstreams (skips others):
{{ terminal(cmd="git fetch --prune && wt step for-each -- sh -c '[ __WT_QUOT__$(git rev-parse @{u} 2>/dev/null)__WT_QUOT__ ] || exit 0; git pull --autostash'") }}
### Command reference
{% terminal() %}
wt step for-each - [experimental] Run command in each worktree
Executes sequentially with real-time output; continues past command failures.
Usage: <b><span class=c>wt step for-each</span></b> <span class=c>[OPTIONS]</span> <b><span class=c>--</span></b> <span class=c>&lt;ARGS&gt;...</span>
<b><span class=g>Arguments:</span></b>
<span class=c>&lt;ARGS&gt;...</span>
Command template (see --help for all variables)
<b><span class=g>Options:</span></b>
<b><span class=c>--format</span></b><span class=c> &lt;FORMAT&gt;</span>
Output format
[default: text]
[possible values: text, json]
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
## wt step promote
<span class="badge-experimental"></span>
Swap a branch into the main worktree. Exchanges branches and gitignored files between two worktrees.
**Experimental.** Use promote for temporary testing when the main worktree has special significance (Docker Compose, IDE configs, heavy build artifacts anchored to project root), and hooks & tools aren't yet set up to run on arbitrary worktrees. The idiomatic Worktrunk workflow does not use `promote`; instead each worktree has a full environment. `promote` is the only Worktrunk command which changes a branch in an existing worktree.
### Example
{{ terminal(cmd="# from ~/project (main worktree)|||wt step promote feature") }}
Before:
```
Branch Path
@ main ~/project
+ feature ~/project.feature
```
After:
```
Branch Path
@ feature ~/project
+ main ~/project.feature
```
To restore: `wt step promote main` from anywhere, or just `wt step promote` from the main worktree.
Without an argument, promotes the current branch — or restores the default branch if run from the main worktree.
### Requirements
- Both worktrees must be clean
- The branch must have an existing worktree
### Gitignored files
Gitignored files (build artifacts, `node_modules/`, `.env`) are swapped along with the branches so each worktree keeps the artifacts that belong to its branch. Files are discovered using the same mechanism as [`copy-ignored`](#wt-step-copy-ignored) and can be filtered with `.worktreeinclude`.
The swap uses `rename()` for each entry — fast regardless of entry size, since only filesystem metadata changes. If the worktree is on a different filesystem from `.git/`, it falls back to reflink copy.
### Command reference
{% terminal() %}
wt step promote - [experimental] Swap a branch into the main worktree
Exchanges branches and gitignored files between two worktrees.
Usage: <b><span class=c>wt step promote</span></b> <span class=c>[OPTIONS]</span> <span class=c>[BRANCH]</span>
<b><span class=g>Arguments:</span></b>
<span class=c>[BRANCH]</span>
Branch to promote to main worktree
Defaults to current branch, or default branch from main worktree.
<b><span class=g>Options:</span></b>
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Automation:</span></b>
<b><span class=c>--format</span></b><span class=c> &lt;FORMAT&gt;</span>
Output format
JSON prints structured result to stdout after the promote completes. The mismatch warning
still appears on stderr in JSON mode (safety signal).
[default: text]
[possible values: text, json]
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
## wt step prune
<span class="badge-experimental"></span>
Remove worktrees merged into the default branch.
Bulk-removes worktrees and branches that are integrated into the default branch, using the same criteria as `wt remove`'s branch cleanup. Stale worktree entries are cleaned up too.
In `wt list`, candidates show `_` (same commit) or `⊂` (content integrated). Run `--dry-run` to preview. See `wt remove --help` for the full integration criteria.
Locked worktrees and the main worktree are always skipped. The current worktree is removed last, triggering cd to the primary worktree. Pre-remove and post-remove hooks run for each removal; a candidate whose hooks include an unapproved project command is skipped with `(approval required)` (pre-approve with `wt config approvals add`, or pass `--yes`).
### Min-age guard
Worktrees younger than `--min-age` (default: 1 day) are skipped. This prevents removing a worktree just created from the default branch — it looks "merged" because its branch points at the same commit.
{{ terminal(cmd="wt step prune --min-age=0s # no age guard|||wt step prune --min-age=2d # skip worktrees younger than 2 days") }}
### JSON output
`--format=json` prints one object per candidate to stdout. The two modes report different things, and name their fields accordingly: a live run reports `branch_outcome`, the executed outcome, using the vocabulary [`wt remove`](@/remove.md#json-output) documents; `--dry-run` reports `branch_deleted`, its prediction of whether the removal would take the branch, since it runs nothing to have an outcome. A dry run also carries `reason` and `target` (why the candidate qualifies, and what it was measured against).
### Examples
Preview what would be removed:
{{ terminal(cmd="wt step prune --dry-run") }}
Remove all merged worktrees:
{{ terminal(cmd="wt step prune") }}
### Command reference
{% terminal() %}
wt step prune - [experimental] Remove worktrees merged into the default branch
Usage: <b><span class=c>wt step prune</span></b> <span class=c>[OPTIONS]</span>
<b><span class=g>Options:</span></b>
<b><span class=c>--dry-run</span></b>
Show what would be removed
<b><span class=c>--min-age</span></b><span class=c> &lt;MIN_AGE&gt;</span>
Skip worktrees younger than this
[default: 1d]
<b><span class=c>--foreground</span></b>
Run removal in foreground (block until complete)
<b><span class=c>--format</span></b><span class=c> &lt;FORMAT&gt;</span>
Output format
[default: text]
[possible values: text, json]
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
## wt step relocate
<span class="badge-experimental"></span>
Move worktrees to expected paths. Relocates worktrees whose path doesn't match the worktree-path template.
### Examples
Preview what would be moved:
{{ terminal(cmd="wt step relocate --dry-run") }}
Move all mismatched worktrees:
{{ terminal(cmd="wt step relocate") }}
Auto-commit and clobber blockers (never fails):
{{ terminal(cmd="wt step relocate --commit --clobber") }}
Move specific worktrees:
{{ terminal(cmd="wt step relocate feature bugfix") }}
### Swap handling
When worktrees are at each other's expected locations (e.g., `alpha` at
`repo.beta` and `beta` at `repo.alpha`), relocate automatically resolves
this by using a temporary location.
### Clobbering
With `--clobber`, non-worktree paths at target locations are moved to
`<path>.bak.<timestamp>` before relocating. If that name is already taken,
the move counts up (`…-2`, `…-3`, …) until it finds a free name, so an
existing backup is never overwritten.
### Main worktree behavior
The main worktree can't be moved with `git worktree move`. Instead, relocate
switches it to the default branch and creates a new linked worktree at the
expected path. Untracked and gitignored files remain at the original location.
### Dirty worktrees
Linked worktrees relocate as-is — `git worktree move` carries uncommitted
changes along. Only the main worktree skips when dirty (its `git checkout`
refuses), unless `--commit` is passed.
### Skipped worktrees
- **Dirty main worktree** (without `--commit`) — use `--commit` to auto-commit first
- **Locked** — unlock with `git worktree unlock`
- **Target blocked** (without `--clobber`) — use `--clobber` to backup blocker
- **Detached HEAD** — no branch to compute expected path
### Command reference
{% terminal() %}
wt step relocate - [experimental] Move worktrees to expected paths
Relocates worktrees whose path doesn&#39;t match the <b>worktree-path</b> template.
Usage: <b><span class=c>wt step relocate</span></b> <span class=c>[OPTIONS]</span> <span class=c>[BRANCHES]...</span>
<b><span class=g>Arguments:</span></b>
<span class=c>[BRANCHES]...</span>
Worktrees to relocate (defaults to all mismatched)
<b><span class=g>Options:</span></b>
<b><span class=c>--dry-run</span></b>
Show what would be moved
<b><span class=c>--commit</span></b>
Commit uncommitted changes before relocating
<b><span class=c>--clobber</span></b>
Backup non-worktree paths at target locations
Moves blocking paths to <b>&lt;path&gt;.bak.&lt;timestamp&gt;</b>. If that name is taken, counts up (<b>…-2</b>, <b>…-3</b>
, …) to a free name.
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Automation:</span></b>
<b><span class=c>--format</span></b><span class=c> &lt;FORMAT&gt;</span>
Output format
JSON prints structured result to stdout after the relocate completes.
[default: text]
[possible values: text, json]
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
## wt step tether
<span class="badge-experimental"></span>
Run a command; kill its whole process tree when its worktree is removed. Teardown is automatic and needs no pre-remove hook; the group gets SIGTERM then SIGKILL.
### Why
A `post-start` hook to start a long-lived process and a `pre-remove` hook to
stop it is usually enough. But `pre-remove` only runs when worktrunk removes
the worktree, so a `git worktree remove`, an `rm -rf`, or a crashed hook skips
it. Across enough worktree churn some process is bound to outlive its worktree,
and with no cleanup these leaks accumulate (on macOS they eventually saturate
`fseventsd`). `tether` removes the need for a `pre-remove`: it ties the
command's lifetime to the worktree and kills the whole process group once the
worktree is gone.
### Arguments
Arguments after `--` are the program and its arguments, run directly, no shell.
{{ terminal(cmd="wt step tether -- npm run dev") }}
For pipes, redirects, variables, or globs, wrap in `sh -c`:
{{ terminal(cmd="wt step tether -- sh -c 'PORT=$P npm run dev | tee dev.log'") }}
To run the command from a subdirectory, pass the global `-C` flag (teardown
still watches the worktree root, so a server launched with a relative `-C` is
torn down with the worktree):
{{ terminal(cmd="wt step tether -C frontend -- npm run dev") }}
### Examples
Run a dev server, torn down automatically when the worktree goes away:
```toml
# .config/wt.toml
[post-start]
server = "wt step tether -- npm run dev -- --port {{ branch | hash_port }}"
```
### Command reference
{% terminal() %}
wt step tether - [experimental] Run a command; kill its whole process tree when its worktree is removed
Teardown is automatic and needs no <b>pre-remove</b> hook; the group gets <b>SIGTERM</b> then <b>SIGKILL</b>.
Usage: <b><span class=c>wt step tether</span></b> <span class=c>[OPTIONS]</span> <b><span class=c>--</span></b> <span class=c>&lt;COMMAND&gt;...</span>
<b><span class=g>Arguments:</span></b>
<span class=c>&lt;COMMAND&gt;...</span>
Command to run (after <b>--</b>, run directly, no shell)
<b><span class=g>Options:</span></b>
<b><span class=c>-h</span></b>, <b><span class=c>--help</span></b>
Print help (see a summary with &#39;-h&#39;)
<b><span class=g>Global Options:</span></b>
<b><span class=c>-C</span></b><span class=c> &lt;path&gt;</span>
Working directory for this command
<b><span class=c>--config</span></b><span class=c> &lt;path&gt;</span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</span>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
<b><span class=c>-v</span></b>, <b><span class=c>--verbose</span></b><span class=c>...</span>
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
apply the same level everywhere — including shell completion, which no flag can reach
<b><span class=c>-y</span></b>, <b><span class=c>--yes</span></b>
Skip approval prompts
{% end %}
<!-- END AUTO-GENERATED -->