wt step

Run individual operations. The building blocks of wt merge — commit, squash, rebase, push — plus standalone utilities.

Examples

Commit with LLM-generated message:

wt step commit

Manual merge workflow with review between steps:

wt step commit
wt step squash
wt step rebase
wt step push

Operations

See also

Command reference

wt step - Run individual operations

The building blocks of wt merge — commit, squash, rebase, push — plus standalone
utilities.

Usage: wt step [OPTIONS] <COMMAND>

Commands:
  commit        Stage and commit with LLM-generated message
  squash        Squash commits since branching
  push          Fast-forward target to current branch
  rebase        Rebase onto target
  diff          Show all changes since branching
  copy-ignored  Copy gitignored files to another worktree
  eval          [experimental] Evaluate a template expression
  for-each      [experimental] Run command in each worktree
  promote       [experimental] Swap a branch into the main worktree
  prune         [experimental] Remove worktrees merged into the default branch
  relocate      [experimental] Move worktrees to expected paths

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...
          Verbose output (-v: hooks, templates; -vv: debug report)

Subcommands

wt step commit

Stage and commit with LLM-generated message.

Stages all changes (including untracked files) and commits with an LLM-generated message.

Options

--stage

Controls what to stage before committing:

ValueBehavior
allStage all changes including untracked files (default)
trackedStage only modified tracked files
noneDon't stage anything, commit only what's already staged
wt step commit --stage=tracked

Configure the default in user config:

[commit]
stage = "tracked"

--show-prompt

Output the rendered LLM prompt to stdout without running the command. Useful for inspecting prompt templates or piping to other tools:

# Inspect the rendered prompt
wt step commit --show-prompt | less

# Pipe to a different LLM
wt step commit --show-prompt | llm -m gpt-5-nano

Command reference

wt step commit - Stage and commit with LLM-generated message

Usage: wt step commit [OPTIONS]

Options:
      --stage <STAGE>
          What to stage before committing [default: all]

          Possible values:
          - all:     Stage everything: untracked files + unstaged tracked
            changes
          - tracked: Stage tracked changes only (like git add -u)
          - none:    Stage nothing, commit only what's already in the index

      --show-prompt
          Show prompt without running LLM

          Outputs the rendered prompt to stdout for debugging or manual piping.

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

Automation:
  -y, --yes
          Skip approval prompts

      --no-verify
          Skip hooks

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

      --config <path>
          User config file path

  -v, --verbose...
          Verbose output (-v: hooks, templates; -vv: debug report)

wt step squash

Squash commits since branching. Stages changes and generates message with LLM.

Stages all changes (including untracked files), then squashes all commits since diverging from the target branch into a single commit with an LLM-generated message.

Options

--stage

Controls what to stage before squashing:

ValueBehavior
allStage all changes including untracked files (default)
trackedStage only modified tracked files
noneDon't stage anything, squash only committed changes
wt step squash --stage=none

Configure the default in user config:

[commit]
stage = "tracked"

--show-prompt

Output the rendered LLM prompt to stdout without running the command. Useful for inspecting prompt templates or piping to other tools:

wt step squash --show-prompt | less

Command reference

wt step squash - Squash commits since branching

Stages changes and generates message with LLM.

Usage: wt step squash [OPTIONS] [TARGET]

Arguments:
  [TARGET]
          Target branch

          Defaults to default branch.

Options:
      --stage <STAGE>
          What to stage before committing [default: all]

          Possible values:
          - all:     Stage everything: untracked files + unstaged tracked
            changes
          - tracked: Stage tracked changes only (like git add -u)
          - none:    Stage nothing, commit only what's already in the index

      --show-prompt
          Show prompt without running LLM

          Outputs the rendered prompt to stdout for debugging or manual piping.

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

Automation:
  -y, --yes
          Skip approval prompts

      --no-verify
          Skip hooks

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

      --config <path>
          User config file path

  -v, --verbose...
          Verbose output (-v: hooks, templates; -vv: debug report)

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.

Extra git diff arguments

Arguments after -- are forwarded to git diff:

wt step diff -- --stat
wt step diff -- --name-only
wt step diff -- -- '*.rs'

The diff is pipeable to tools like delta:

wt step diff | delta

How it works

Equivalent to:

cp "$(git rev-parse --git-dir)/index" /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

wt step diff - Show all changes since branching

Includes committed, staged, unstaged, and untracked files.

Usage: wt step diff [OPTIONS] [TARGET] [-- <EXTRA_ARGS>...]

Arguments:
  [TARGET]
          Target branch

          Defaults to default branch.

  [EXTRA_ARGS]...
          Extra arguments forwarded to git diff

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...
          Verbose output (-v: hooks, templates; -vv: debug report)

wt step copy-ignored

Copy gitignored files to another worktree. Eliminates cold starts by copying build caches and dependencies.

Git worktrees share the repository but not untracked files. This command copies gitignored files to another worktree, eliminating cold starts.

Setup

Add to the project config:

# .config/wt.toml
[post-start]
copy = "wt step copy-ignored"

What gets copied

All gitignored files are copied by default. Tracked files are never touched.

To limit what gets copied, create .worktreeinclude with gitignore-style patterns. Files must be both gitignored and in .worktreeinclude:

# .worktreeinclude
.env
node_modules/
target/

Common patterns

TypePatterns
Dependenciesnode_modules/, .venv/, target/, vendor/, Pods/
Build caches.cache/, .next/, .parcel-cache/, .turbo/
Generated assetsImages, ML models, binaries too large for git
Environment files.env (if not generated per-worktree)

Features

Performance

Reflink copies share disk blocks until modified — no data is actually copied. For a 14GB target/ directory:

CommandTime
cp -R (full copy)2m
cp -Rc / wt step copy-ignored20s

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.

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:

[post-create]
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, which copies matching files when creating worktrees. Differences:

Command reference

wt step copy-ignored - Copy gitignored files to another worktree

Eliminates cold starts by copying build caches and dependencies.

Usage: wt step copy-ignored [OPTIONS]

Options:
      --from <FROM>
          Source worktree branch

          Defaults to main worktree.

      --to <TO>
          Destination worktree branch

          Defaults to current worktree.

      --dry-run
          Show what would be copied

      --force
          Overwrite existing files in destination

  -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...
          Verbose output (-v: hooks, templates; -vv: debug report)

wt step eval

Evaluate a template expression. Prints the result to stdout for use in scripts and shell substitutions.

Evaluates a template expression in the current worktree context and prints the result to stdout. All hook template variables and filters are available.

Output goes to stdout with no decoration, making it suitable for shell substitution and piping.

Examples

Get the port for the current branch:

$ wt step eval '{{ branch | hash_port }}'
16066

Use in shell substitution:

$ curl http://localhost:$(wt step eval '{{ branch | hash_port }}')/health

Combine multiple values:

$ wt step eval '{{ branch | hash_port }},{{ ("supabase-api-" ~ branch) | hash_port }}'
16066,16739

Use conditionals and filters:

$ wt step eval '{{ branch | sanitize_db }}'
feature_auth_oauth2_a1b

Show available template variables:

$ wt step eval --dry-run '{{ branch }}'
branch=feature/auth-oauth2
worktree_path=/home/user/projects/myapp-feature-auth-oauth2
...
Result: feature/auth-oauth2

Note: This command is experimental and may change in future versions.

Command reference

wt step eval - [experimental] Evaluate a template expression

Prints the result to stdout for use in scripts and shell substitutions.

Usage: wt step eval [OPTIONS] <TEMPLATE>

Arguments:
  <TEMPLATE>
          Template expression to evaluate

Options:
      --dry-run
          Show template variables and expanded result

  -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...
          Verbose output (-v: hooks, templates; -vv: debug report)

wt step for-each

Run command in each worktree. Executes sequentially with real-time output; continues on failure.

Executes a command sequentially in every worktree with real-time output. Continues on failure and shows a summary at the end.

Context JSON is piped to stdin for scripts that need structured data.

Template variables

All variables are shell-escaped. See wt hook template variables for the complete list and filters.

Examples

Check status across all worktrees:

wt step for-each -- git status --short

Run npm install in all worktrees:

wt step for-each -- npm install

Use branch name in command:

wt step for-each -- "echo Branch: {{ branch }}"

Pull updates in worktrees with upstreams (skips others):

git fetch --prune && wt step for-each -- '[ "$(git rev-parse @{u} 2>/dev/null)" ] || exit 0; git pull --autostash'

Note: This command is experimental and may change in future versions.

Command reference

wt step for-each - [experimental] Run command in each worktree

Executes sequentially with real-time output; continues on failure.

Usage: wt step for-each [OPTIONS] -- <ARGS>...

Arguments:
  <ARGS>...
          Command template (see --help for all variables)

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...
          Verbose output (-v: hooks, templates; -vv: debug report)

wt step promote

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

# 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

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 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

wt step promote - [experimental] Swap a branch into the main worktree

Exchanges branches and gitignored files between two worktrees.

Usage: wt step promote [OPTIONS] [BRANCH]

Arguments:
  [BRANCH]
          Branch to promote to main worktree

          Defaults to current branch, or default branch from main worktree.

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...
          Verbose output (-v: hooks, templates; -vv: debug report)

wt step prune

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.

Min-age guard

Worktrees younger than --min-age (default: 1 hour) are skipped. This prevents removing a worktree just created from the default branch — it looks "merged" because its branch points at the same commit.

wt step prune --min-age=0s     # no age guard
wt step prune --min-age=2d     # skip worktrees younger than 2 days

Examples

Preview what would be removed:

wt step prune --dry-run

Remove all merged worktrees:

wt step prune

Command reference

wt step prune - [experimental] Remove worktrees merged into the default branch

Usage: wt step prune [OPTIONS]

Options:
      --dry-run
          Show what would be removed

      --min-age <MIN_AGE>
          Skip worktrees younger than this

          [default: 1h]

      --foreground
          Run removal in foreground (block until complete)

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

Automation:
  -y, --yes
          Skip approval prompts

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

      --config <path>
          User config file path

  -v, --verbose...
          Verbose output (-v: hooks, templates; -vv: debug report)

wt step relocate

Move worktrees to expected paths. Relocates worktrees whose path doesn't match the worktree-path template.

Moves worktrees to match the configured worktree-path template.

Examples

Preview what would be moved:

wt step relocate --dry-run

Move all mismatched worktrees:

wt step relocate

Auto-commit and clobber blockers (never fails):

wt step relocate --commit --clobber

Move specific worktrees:

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.

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.

Skipped worktrees

Note: This command is experimental and may change in future versions.

Command reference

wt step relocate - [experimental] Move worktrees to expected paths

Relocates worktrees whose path doesn't match the worktree-path template.

Usage: wt step relocate [OPTIONS] [BRANCHES]...

Arguments:
  [BRANCHES]...
          Worktrees to relocate (defaults to all mismatched)

Options:
      --dry-run
          Show what would be moved

      --commit
          Commit uncommitted changes before relocating

      --clobber
          Backup non-worktree paths at target locations

          Moves blocking paths to <path>.bak-<timestamp>.

  -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...
          Verbose output (-v: hooks, templates; -vv: debug report)

Aliases

[experimental] Custom command templates configured in user config (~/.config/worktrunk/config.toml) or project config (.config/wt.toml). Aliases support the same template variables as hooks.

# .config/wt.toml
[aliases]
deploy = "make deploy BRANCH={{ branch }}"
port = "echo http://localhost:{{ branch | hash_port }}"
wt step deploy                            # run the alias
wt step deploy --dry-run                  # show expanded command
wt step deploy --var env=staging          # pass extra template variables
wt step deploy --yes                      # skip approval prompt

When defined in both user and project config, user aliases take precedence. Project-config aliases require command approval on first run (same as project hooks). User-config aliases are trusted.

Alias names that match a built-in step command (commit, squash, etc.) are shadowed by the built-in and will never run.