Skip to content

wt config

Manage user & project configs. Includes shell integration, hooks, and saved state.

Install shell integration (required for directory switching):

wt config shell install

Create user config file with documented examples:

wt config create

Create project config file (.config/wt.toml) for hooks:

wt config create --project

Show current configuration and file locations:

wt config show
FileLocationContainsCommitted & shared
User config~/.config/worktrunk/config.tomlWorktree path template, LLM commit configs, etc
Project config.config/wt.tomlProject hooks, dev server URL

Organizations can deploy a system-wide config file for shared defaults — run wt config show for the platform-specific location.

User config — personal preferences:

~/.config/worktrunk/config.toml
worktree-path = ".worktrees/{{ branch | sanitize }}"
[commit.generation]
command = "MAX_THINKING_TOKENS=0 claude -p --no-session-persistence --model=haiku --tools='' --safe-mode --setting-sources='user' --system-prompt=''"

Project config — shared team settings:

.config/wt.toml
[pre-start]
deps = "npm ci"
[pre-merge]
test = "npm test"

Create with wt config create. Values shown are defaults unless noted otherwise.

Location:

  • macOS/Linux: ~/.config/worktrunk/config.toml (or $XDG_CONFIG_HOME if set)
  • Windows: %APPDATA%\worktrunk\config.toml

Controls where new worktrees are created.

Available template variables:

  • {{ repo_path }} — absolute path to the repository root (e.g., /Users/me/code/myproject. Or for bare repos, the bare directory itself)
  • {{ repo }} — repository directory name (e.g., myproject)
  • {{ owner }} — primary remote owner path (may include subgroups like group/subgroup)
  • {{ remote_repo }} — repository name in the primary remote URL, without .git (e.g., myproject); differs from {{ repo }}, the directory on disk, when a clone was renamed
  • {{ branch }} — raw branch name (e.g., feature/auth)
  • {{ branch | sanitize }} — filesystem-safe: / and \ become - (e.g., feature-auth)
  • {{ branch | sanitize_db }} — database-safe: lowercase, underscores, hash suffix (e.g., feature_auth_x7k)
  • {{ branch | codename(2) }} — deterministic friendly name from a ~1.26M-combo pool (e.g., malleable-opah)

This is a smaller set than the variables hooks and aliases get.

Examples for repo at ~/code/myproject, branch feature/auth:

Default — sibling directory (~/code/myproject.feature-auth):

worktree-path = "{{ repo_path }}/../{{ repo }}.{{ branch | sanitize }}"

Inside the repository (~/code/myproject/.worktrees/feature-auth):

worktree-path = "{{ repo_path }}/.worktrees/{{ branch | sanitize }}"

Friendly branch-derived names (~/code/myproject.malleable-opah):

worktree-path = "{{ repo_path }}/../{{ repo }}.{{ branch | codename(2) }}"

Friendly names with branch identity in a parent directory (~/code/worktrees/feature-auth/malleable-opah):

worktree-path = "{{ repo_path }}/../worktrees/{{ branch | sanitize }}/{{ branch | codename(2) }}"

Centralized worktrees directory (~/worktrees/myproject/feature-auth):

worktree-path = "~/worktrees/{{ repo }}/{{ branch | sanitize }}"

By remote owner path (~/development/max-sixty/myproject/feature/auth):

worktree-path = "~/development/{{ owner }}/{{ repo }}/{{ branch }}"

Bare repository (~/code/myproject/feature-auth):

worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"

~ expands to the home directory. Relative paths resolve from repo_path.

Generate commit messages automatically during merge. Requires an external CLI tool.

[commit.generation]
command = "MAX_THINKING_TOKENS=0 claude -p --no-session-persistence --model=haiku --tools='' --safe-mode --setting-sources='user' --system-prompt=''"
[commit.generation]
command = "codex exec -m gpt-5.6-luna -c model_reasoning_effort='low' -c system_prompt='' --sandbox=read-only --json - | jq -sr '[.[] | select(.item.type? == \"agent_message\")] | last.item.text'"
[commit.generation]
command = "opencode run -m anthropic/claude-haiku-4.5 --variant fast"
[commit.generation]
command = "llm -m claude-haiku-4.5"
[commit.generation]
command = "aichat -m claude:claude-haiku-4.5"

See LLM commits docs for setup and Custom prompt templates for template customization.

Persistent flag values for wt list. Override on command line as needed.

[list]
summary = false # Enable LLM branch summaries (requires [commit.generation])
full = false # Show CI status and LLM summaries (--full)
branches = false # Include branches without worktrees (--branches)
remotes = false # Include remote-only branches (--remotes)
json-schema = 2 # JSON output schema: 2 (envelope) or 1 (bare array, the current default); unset emits 1 with a warning
columns = ["branch", "status", "ci", "path"] # Columns to show, in order — built-ins or custom headers (omit for the default set)
timeout-ms = 0 # Wall-clock budget for the entire collect phase; 0 disables

columns selects and orders the columns the wt list table and the wt switch picker render; --format json ignores it and always emits every field. Omit it for the default set. It is meant to drive a per-invocation alias (wt --config-set 'list.columns=[…]' list), giving a named view without disturbing the default wt list. A static setting works but pins one layout over a table that otherwise adapts to --full and terminal width.

Valid built-in names:

  • branch — The branch name
  • status — Git status symbols, plus any user-defined status
  • working-diff — Uncommitted line changes against HEAD, including untracked files (header HEAD±)
  • ahead-behind — Commits ahead of and behind the default branch (header main↕)
  • branch-diff — Line changes against the default branch (header main…±)
  • summary — An LLM-generated summary of the branch
  • upstream — Commits ahead of and behind the upstream tracking branch (header Remote⇅)
  • ci — CI status of the head commit
  • path — The worktree’s path
  • url — Dev-server URL from the [list] url template
  • commit — The head commit’s short hash
  • age — Time since the last commit
  • message — The head commit’s subject

A selection mixes built-ins with custom columns, each named by its [list.custom-columns] header (columns = ["branch", "Ticket", "ci"]), and is exhaustive: only the listed columns render. Omit columns to keep the default set, where custom columns append automatically. A built-in name wins a header collision; the gutter type indicator always shows.

Listing a column forces it on, space permitting: ci shows without --full, since --full only bundles columns into the default table rather than gating a named one. A column whose data source is missing still stays hidden — summary needs an LLM command ([commit.generation]), url needs a [list] url template — since listing can’t supply the data.

Custom columns add per-branch context to the wt list table. Each [list.custom-columns] entry is a column: the key is the header, the template renders each row’s cell.

[list.custom-columns.Ticket]
template = "{{ vars.ticket }}" # Required; the result is the cell text
width = 20 # Optional max display width (default: 40)
priority = 9 # Optional drop order when the terminal narrows;
# lower = kept longer (default: 9, the URL band)

Templates may reference {{ branch }}, {{ worktree_path }}, {{ worktree_name }} (empty for branch-only rows), and two per-branch namespaces:

  • {{ vars.* }} — values stored with wt config state vars set.
  • {{ git.branch.* }} — the branch’s own git config under branch.<name>.*, read straight from git config (e.g. {{ git.branch.jira }} for a key you set yourself, or the git-native description). Git lowercases config variable names, so branch.<name>.nvciShelf reads as {{ git.branch.nvcishelf }}.

All standard filters work (sanitize, hash_port, codename, …). A row where the template renders empty (e.g. a branch without the key) shows an empty cell; a column that is empty for every row is dropped from the table. wt list --format json includes the rendered values under columns.

A Jira column reading a key kept in git config, and a Summary column showing just the first line of the git-native branch description:

[list.custom-columns.Jira]
template = "{{ git.branch.jira }}"
[list.custom-columns.Summary]
template = "{{ git.branch.description | lines | first }}"

Shared by wt step commit, wt step squash, and wt merge.

[commit]
stage = "all" # What to stage before commit: "all", "tracked", or "none"

Most flags are on by default. Set to false to change default behavior.

[merge]
squash = true # Squash commits into one (--no-squash to preserve history)
commit = true # Commit uncommitted changes first (--no-commit to skip)
rebase = true # Rebase onto target before merge (--no-rebase to skip)
remove = true # Remove worktree after merge (--no-remove to keep)
verify = true # Run project hooks (--no-hooks to skip)
ff = true # Fast-forward merge (--no-ff to create a merge commit instead)

Persistent flag values for wt remove. Override on command line as needed.

[remove]
delete-branch = true # Delete branch after removal (--no-delete-branch to keep)
[switch]
cd = true # Change directory after switching (--no-cd to skip)
[switch.picker]
pager = "delta --paging=never" # Example: override git's core.pager for diff preview
[step.copy-ignored]
exclude = [] # Additional excludes (e.g., [".cache/", ".turbo/"])

Built-in excludes (VCS metadata and tool-state directories) always apply; the wt step copy-ignored docs list them. User config and project config exclusions are combined.

Command templates that run as wt <name>. See the Extending Worktrunk guide for usage and flags.

[aliases]
greet = "echo Hello from {{ branch }}"
url = "echo http://localhost:{{ branch | hash_port }}"

Aliases defined here apply to all projects. For project-specific aliases, use the project config [aliases] section instead.

User config can include a [projects] table for project-specific settings — worktree layout, setting overrides, anything else — separate from the project config shared with teammates.

Entries are keyed by project identifier — <host>/<owner>/<repo> derived from the primary remote URL (no .git suffix), or the canonical repo path when there is no remote. Run wt config show inside the repo to see the identifier for the current project; it appears in the PROJECT CONFIG section as Identifier: ….

Scalar values (like worktree-path) replace the global value; everything else (hooks, aliases, etc.) appends, global first. See how the layers rank.

[projects."github.com/user/repo"]
worktree-path = ".worktrees/{{ branch | sanitize }}"
list.full = true
merge.squash = false
remove.delete-branch = false
pre-start.env = "cp .env.example .env"
step.copy-ignored.exclude = [".repo-local-cache/"]
aliases.deploy = "make deploy BRANCH={{ branch }}"

Matching several repositories with one entry

Section titled “Matching several repositories with one entry”

A key containing * matches any run of characters, / included, so one entry covers a whole host or namespace — including nested groups. * is the only wildcard; every other character, . among them, is literal.

# Every repository on a self-hosted forge whose hostname carries no brand
[projects."git.company.example/*"]
forge.platform = "gitlab"
# Everything under one namespace shares a layout
[projects."git.company.example/platform/*"]
worktree-path = ".worktrees/{{ branch | sanitize }}"

Every matching entry applies, least- to most-specific, following the rule above: a more specific entry — git.company.example/platform/* over git.company.example/* — wins where both set the same setting, while hooks and aliases from every matching entry all run, least-specific first. A literal key is the most specific of all; specificity is the count of non-* characters in the key. End a host-wide key with /* — a bare git.company.example* also covers hosts whose names merely start with that string.

approved-commands matches the same way, so a pattern entry approves its commands for every repository it covers. Only a key written by hand is ever a pattern: wt config approvals add and the interactive prompt record under the exact identifier, and wt config approvals clear removes only that exact entry, leaving a pattern other repositories share intact.

forge names the forge for the matched repositories — the user-level counterpart of the project config’s forge platform block, for a self-hosted host whose name carries no github, gitlab, or gitea for detection to read.

[projects."git.company.example/*"]
forge.platform = "gitlab" # or "github", "gitea" (experimental), "azure-devops" (experimental)
forge.hostname = "api.git.company.example" # API host, when the remote's own host isn't it

Both fields describe the host rather than the repository, which is why a pattern keyed to a hostname suits them, and why an SSH alias resolved through ~/.ssh/config — where the name in the remote URL is local to one machine — belongs here rather than in a repository’s committed config. A repository’s own [forge] block still wins over any entry here, field by field: a repository that sets only platform still takes a matching entry’s hostname.

Hooks support all three hook forms. A table runs multiple commands concurrently; an array-of-tables pipeline runs steps in sequence. The dotted-key examples below are equivalent to the table forms — TOML treats projects."github.com/user/repo".post-start.server = "..." and a [projects."github.com/user/repo".post-start] table the same way:

# Single command
[projects."github.com/user/repo"]
post-start = "mise trust"
# Multiple commands, running concurrently
[projects."github.com/user/repo".post-start]
mise = "mise trust"
server = "npm run dev"
# Pipeline: steps run in sequence
[[projects."github.com/user/repo".post-start]]
install = "npm ci"
[[projects."github.com/user/repo".post-start]]
build = "npm run build"
server = "npm run dev"

Templates use minijinja syntax.

Available variables:

  • {{ git_diff }}, {{ git_diff_stat }} — diff content
  • {{ branch }}, {{ repo }} — context
  • {{ recent_commits }} — recent commit messages
  • {{ user_guidance }}, {{ project_guidance }} — rendered append fragments (see Appending to the prompt)

Default template:

[commit.generation]
template = """
<task>Write a commit message for the staged changes below.</task>
<format>
- Subject line under 50 chars
- For material changes, add a blank line then a body paragraph explaining the change
- Output only the commit message, no quotes or code blocks
</format>
<style>
- Imperative mood: "Add feature" not "Added feature"
- Match recent commit style (conventional commits if used)
- Describe the change, not the intent or benefit
</style>
{% if user_guidance %}
<user-guidance>
{{ user_guidance }}
</user-guidance>
{% endif %}{% if project_guidance %}
<project-guidance>
{{ project_guidance }}
</project-guidance>
{% endif %}
<diffstat>
{{ git_diff_stat }}
</diffstat>
<diff>
{{ git_diff }}
</diff>
<context>
Branch: {{ branch }}
{% if recent_commits %}<recent_commits>
{% for commit in recent_commits %}- {{ commit }}
{% endfor %}</recent_commits>{% endif %}
</context>
"""

Available variables (in addition to commit template variables):

  • {{ commit_details }} — list of commits being squashed; each renders as its subject and exposes .subject / .body
  • {{ target_branch }} — merge target branch

Default template:

[commit.generation]
squash-template = """
<task>Write a commit message for the combined effect of these commits.</task>
<format>
- Subject line under 50 chars
- For material changes, add a blank line then a body paragraph explaining the change
- Output only the commit message, no quotes or code blocks
</format>
<style>
- Imperative mood: "Add feature" not "Added feature"
- Match the style of commits being squashed (conventional commits if used)
- Describe the change, not the intent or benefit
</style>
{% if user_guidance %}
<user-guidance>
{{ user_guidance }}
</user-guidance>
{% endif %}{% if project_guidance %}
<project-guidance>
{{ project_guidance }}
</project-guidance>
{% endif %}
<commits branch="{{ branch }}" target="{{ target_branch }}">
{% for detail in commit_details %}- {{ detail.subject }}
{% endfor %}</commits>
<diffstat>
{{ git_diff_stat }}
</diffstat>
<diff>
{{ git_diff }}
</diff>
"""

template-append adds personal conventions to the commit and squash prompts without restating the whole template:

[commit.generation]
template-append = """
- Explain the rationale in the body, not just the change
"""

How the fragment renders, and the project-config counterpart: the LLM commits guide.

See wt hook for hook types, execution order, template variables, and examples. User hooks apply to all projects; project hooks apply only to that repository.

Project configuration lets teams share repository-specific settings — hooks, dev server URLs, and other defaults. The file lives in .config/wt.toml and is typically checked into version control.

To create a starter file with commented-out examples, run wt config create --project.

Project hooks apply to this repository only. See wt hook for hook types, execution order, and examples.

pre-start = "npm ci"
post-start = "npm run dev"
pre-merge = "npm test"

URL column in wt list (dimmed when port not listening):

[list]
url = "http://localhost:{{ branch | hash_port }}"

The forge is read from the remote’s hostname: any host carrying github, gitlab, or gitea anywhere in it, plus the Azure DevOps service domains. Name the forge explicitly for a host carrying none of those, such as a Forgejo instance at forge.example.com:

[forge]
platform = "github" # or "gitlab", "gitea" (experimental), "azure-devops" (experimental)
hostname = "github.example.com" # Example: API host (GHE / self-hosted GitLab)

When many repositories share one self-hosted host, name it once in user config with a pattern-keyed [projects] entry instead of repeating this block in each repo. A repository’s own [forge] still wins, field by field.

template-append adds project-wide conventions to the LLM commit and squash prompts, shared so every teammate’s LLM sees the same style guide:

[commit.generation]
template-append = """
- Use conventional commits (feat:, fix:, docs:, …)
- Reference the relevant issue ID in the body
"""

The first time the fragment is used (and whenever it changes), wt prompts the user to approve it — the same one-shot gate as project-defined hooks. Only template-append is honored from the project file; the LLM command and the main prompt template stay in user config, since they describe per-developer environment (which CLI is installed, which agent the developer prefers). How the fragment renders: the LLM commits guide.

Additional excludes for wt step copy-ignored:

[step.copy-ignored]
exclude = [".cache/", ".turbo/"]

Built-in excludes (VCS metadata and tool-state directories) always apply; the wt step copy-ignored docs list them. User config and project config exclusions are combined.

Command templates that run as wt <name>. See the Extending Worktrunk guide for usage and flags.

[aliases]
deploy = "make deploy BRANCH={{ branch }}"
url = "echo http://localhost:{{ branch | hash_port }}"

Aliases defined here are shared with teammates. For personal aliases, use the user config [aliases] section instead.

Worktrunk needs shell integration to change directories when switching worktrees. Install with:

wt config shell install

For manual setup, see wt config shell init --help.

Without shell integration, wt switch prints the target directory but cannot cd into it.

On first run without shell integration, Worktrunk offers to install it. On first commit without LLM configuration, it offers to configure a detected tool (claude, codex). Declining sets skip-shell-integration-prompt or skip-commit-generation-prompt automatically.

All user config options can be overridden with environment variables using the WORKTRUNK_ prefix.

Config keys use kebab-case (worktree-path), while env vars use SCREAMING_SNAKE_CASE (WORKTRUNK_WORKTREE_PATH). The conversion happens automatically.

For nested config sections, use double underscores to separate levels:

ConfigEnvironment Variable
worktree-pathWORKTRUNK_WORKTREE_PATH
commit.generation.commandWORKTRUNK_COMMIT__GENERATION__COMMAND
commit.stageWORKTRUNK_COMMIT__STAGE

Override the LLM command in CI to use a mock:

WORKTRUNK_COMMIT__GENERATION__COMMAND="echo 'test: automated commit'" wt merge
VariablePurpose
WORKTRUNK_BINOverride binary path for shell wrappers; useful for testing dev builds
WORKTRUNK_CONFIG_PATHOverride user config file location
WORKTRUNK_SYSTEM_CONFIG_PATHOverride system config file location
WORKTRUNK_PROJECT_CONFIG_PATHOverride project config file location (defaults to .config/wt.toml); relative paths resolve from the worktree root
XDG_CONFIG_DIRSColon-separated system config directories (default: /etc/xdg)
WORKTRUNK_DIRECTIVE_CD_FILEInternal: set by shell wrappers. wt writes a raw path; the wrapper cds to it
WORKTRUNK_SHELL_CWDInternal: set by wt on alias and hook bodies, so a nested wt preserves the user’s subdirectory
WORKTRUNK_COMPLETE_NAMEInternal: set by shell wrappers to the command name completions register under (defaults to the binary name)
WORKTRUNK_MAX_CONCURRENT_COMMANDSMax parallel git commands (default: 32). Lower if hitting file descriptor limits.
WORKTRUNK_VERBOSEVerbosity level (0/1/2), like -v/-vv but applied everywhere — including shell completion, which no flag can reach
RUST_LOGLogging directive (e.g. worktrunk=debug); overrides the verbosity baseline for what reaches stderr
NO_COLORDisable colored output (standard)
CLICOLOR_FORCEForce colored output even when not a TTY

--config-set <toml> overrides any user config key for a single invocation. The value is a TOML fragment, so arrays and tables work directly; the flag is global (works before or after the subcommand), repeatable, and a later --config-set replaces an earlier one for the same key.

wt --config-set list.full=true list
wt step copy-ignored --config-set 'step.copy-ignored.exclude=["target", "dist"]'

This composes with aliases — an alias body can invoke wt --config-set … <command> to render a named view without changing the saved config.

Sources closer to the invocation rank higher (user config above system config), and within a config file a project entry outranks the global key of the same name. So worktree-path comes from the first of these that sets it:

  1. --config-set 'worktree-path = …'
  2. WORKTRUNK_WORKTREE_PATH
  3. [projects."github.com/owner/repo"] in the config file
  4. global worktree-path in the config file

A --config-set that names a project entry is both the highest layer and the most specific key, so it beats the same flag’s global key:

wt --config-set 'projects."github.com/owner/repo".worktree-path = "/tmp/scratch"' switch --create feature

Hooks, aliases and step.copy-ignored.exclude accumulate rather than replace, so an env-set hook and a project’s hook both run.

wt config - Manage user & project configs
Includes shell integration, hooks, and saved state.
Usage: wt config [OPTIONS] <COMMAND>
Commands:
shell Shell integration setup
create Create configuration file
show Show configuration files & locations
update Update deprecated config settings
approvals Manage command approvals
alias Inspect and preview aliases
plugins Plugin management
state Manage internal data and cache
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
--config-set <toml>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
-v, --verbose...
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
-y, --yes
Skip approval prompts

Show configuration files & locations.

Shows location and contents of user config (~/.config/worktrunk/config.toml) and project config (.config/wt.toml). Also shows system config if present.

If a config file doesn’t exist, shows defaults that would be used.

Use --full to run diagnostic checks:

wt config show --full

This tests:

  • CI tool status — Whether gh (GitHub) or glab (GitLab) is installed and authenticated
  • Commit generation — Whether the LLM command can generate commit messages
  • Version check — Whether a newer version is available on GitHub

Command reference

Section titled “Command reference”
wt config show - Show configuration files & locations
Usage: wt config show [OPTIONS]
Options:
--full
Run diagnostic checks (CI tools, commit generation, version)
-h, --help
Print help (see a summary with '-h')
Output:
--format <FORMAT>
Output format
[default: text]
[possible values: text, json]
Global Options:
-C <path>
Working directory for this command
--config <path>
User config file path
--config-set <toml>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
-v, --verbose...
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
-y, --yes
Skip approval prompts

Manage command approvals.

Project hooks and project aliases prompt for approval on first run to prevent untrusted projects from running arbitrary commands. Approvals from both flows are stored together.

List commands and their approval status for current project:

wt config approvals list

Pre-approve all hook and alias commands for current project:

wt config approvals add

Pre-approve without prompting, for a container or CI job:

wt config approvals add --yes

Clear approvals for current project:

wt config approvals clear

Clear only approvals for commands no longer in the project config:

wt config approvals clear --stale

Clear global approvals:

wt config approvals clear --global

Check whether an unattended run would stop for approval:

wt config approvals list --format=json | jq -r .state

Approved commands are saved to ~/.config/worktrunk/approvals.toml. Re-approval is required when the command template changes or the project moves.

--yes bypasses the prompt, and what it leaves behind depends on the command it is passed to. On a command that runs project commands it grants consent for that run alone and records nothing, so the next run asks again. On wt config approvals add the record is the whole point, so the approvals are written — which is how an unattended environment pre-approves a project it has just cloned.

wt config approvals list reads the state without prompting or writing it, so an orchestrator can find out whether a non-interactive run will stop for approval before scheduling one. --format=json emits:

{
"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 no_commands (the project declares none), approval_required (at least one is unapproved), or approved. name is absent for an unnamed command and for the commit-template fragment.

stale is separate rather than a fourth state, because it co-occurs with all three: these are approvals recorded earlier whose command has since been edited or removed from the project config. They are what --yes would silently re-approve, so an orchestrator preserving the approval model reads them before choosing that flag.

Command reference

Section titled “Command reference”
wt config approvals - Manage command approvals
Usage: wt config approvals [OPTIONS] <COMMAND>
Commands:
list List project commands and their approval status
add Store approvals in approvals.toml
clear Clear approved commands from approvals.toml
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
--config-set <toml>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
-v, --verbose...
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
-y, --yes
Skip approval prompts

Inspect and preview aliases.

Aliases are command templates configured in user (~/.config/worktrunk/config.toml) or project (.config/wt.toml) config and run as wt <name>. See the Extending Worktrunk guide for the configuration format.

Show every configured alias’s template:

wt config alias show

Show the template for deploy:

wt config alias show deploy

Preview an invocation without running it:

wt config alias dry-run deploy
wt config alias dry-run deploy -- --env=staging

Command reference

Section titled “Command reference”
wt config alias - Inspect and preview aliases
Usage: wt config alias [OPTIONS] <COMMAND>
Commands:
show Show an alias's template, or all aliases' templates
dry-run Preview an alias invocation with template expansion
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
--config-set <toml>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
-v, --verbose...
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
-y, --yes
Skip approval prompts

Manage internal data and cache.

State is stored in .git/ (config entries and log files), separate from configuration files.

Get the default branch:

wt config state default-branch

Set the default branch manually:

wt config state default-branch set main

Set a marker for current branch:

wt config state marker set 🚧

Store arbitrary data:

wt config state vars set env=staging

Drop the regenerable caches:

wt config state cache clear

Show all stored state:

wt config state get

Clear all stored state:

wt config state clear

Command reference

Section titled “Command reference”
wt config state - Manage internal data and cache
Usage: wt config state [OPTIONS] <COMMAND>
Commands:
get Get all stored state
clear Clear all stored state
cache Regenerable caches
default-branch Default branch detection and override
logs Operation and debug logs
marker Branch markers
vars Custom variables per branch
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
--config-set <toml>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
-v, --verbose...
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
-y, --yes
Skip approval prompts

Regenerable caches.

View or drop worktrunk’s regenerable caches in one place. Everything here is rebuilt on demand — clearing only forces recomputation, never data loss.

  • CI status — GitHub/GitLab CI per branch (30–60s TTL), shown in wt list, plus the largest PR/MR number seen (sizes the CI column)
  • Summaries — LLM-generated branch summaries (wt list --full, wt switch preview)
  • Git commands — cached merge-tree, ancestry, diff-stat, and wt switch preview results
  • Hints — one-time hints already shown in this repo
  • Previous branch — the wt switch - target, re-recorded on the next switch

cache clear drops all of the above with no prompt. It re-shows one-time hints and forgets the wt switch - target until the next switch — both repopulate on their own.

Without a subcommand, runs get.

Show cache contents:

wt config state cache

Drop all caches:

wt config state cache clear

Command reference

Section titled “Command reference”
wt config state cache - Regenerable caches
Usage: wt config state cache [OPTIONS] [COMMAND]
Commands:
get Show cache contents
clear Drop all caches
Options:
-h, --help
Print help (see a summary with '-h')
Output:
--format <FORMAT>
Output format (text, json) [default: text]
Global Options:
-C <path>
Working directory for this command
--config <path>
User config file path
--config-set <toml>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
-v, --verbose...
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
-y, --yes
Skip approval prompts

Default branch detection and override.

Useful in scripts to avoid hardcoding main or master:

git rebase $(wt config state default-branch)

In a hook or alias template, prefer the {{ default_branch }} template variable; $(wt config state default-branch) is for plain shell scripts.

Without a subcommand, runs get. set stores the override in the repository’s local git config. The override adds no project file and applies to every linked worktree in the clone. clear then get re-detects. The branch must exist locally for wt list comparisons.

default-branch get resolves the value and caches it on a miss; the aggregate wt config state get only reports the cache (read-only), so it can show (none) until something populates it.

Worktrunk detects the default branch automatically:

  1. Worktrunk cache — Checks git config worktrunk.default-branch
  2. Git cache — Detects primary remote and checks its HEAD (e.g., origin/HEAD)
  3. Remote query — If not cached, queries git ls-remote — typically 100ms–2s, abandoned after 10s
  4. Local inference — If no remote, or the query was abandoned, infers from local branches

Once detected, the result is cached in worktrunk.default-branch for fast access. The cache isn’t re-validated on every command, so a later change to origin/HEAD — a renamed default branch followed by git remote set-head origin -a — isn’t picked up automatically. wt config state flags the drift when the cached value differs from the remote’s local HEAD — expected for a deliberate override; set adopts the new branch and clear re-detects.

An abandoned remote query is the one case that isn’t cached: the branch it inferred locally answers that command, but a value guessed while the remote was unreachable would otherwise become permanent, so the next command queries again.

The local inference fallback uses these heuristics in order:

  • If only one local branch exists, uses it
  • For bare repos or empty repos, checks symbolic-ref HEAD
  • Checks git config init.defaultBranch
  • Looks for common names: main, master, develop, trunk

If none of these match, detection fails; set it explicitly with wt config state default-branch set BRANCH.

Command reference

Section titled “Command reference”
wt config state default-branch - Default branch detection and override
Usage: wt config state default-branch [OPTIONS] [COMMAND]
Commands:
get Get the default branch
set Set the default branch
clear Clear the default branch cache
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
--config-set <toml>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
-v, --verbose...
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
-y, --yes
Skip approval prompts

Operation and debug logs.

View and manage log files — hook output, command audit trail, and debug diagnostics.

Three kinds of logs live in .git/wt/logs/:

All hook executions and LLM commands are recorded automatically — one JSON object per line. Rotates to commands.jsonl.old at 1MB (~2MB total). Fields:

FieldDescription
tsISO 8601 timestamp
wtThe wt command that triggered this (e.g., wt hook pre-merge --yes)
labelWhat ran (e.g., pre-merge user:lint, commit.generation)
cmdShell command executed
exitExit code (null for background commands)
dur_msDuration in milliseconds (null for background commands)

The command log appends entries and is not branch-specific — it records all activity across all worktrees.

Hook output lives in per-branch subtrees under .git/wt/logs/{branch}/:

OperationLog path
Background hooks{branch}/{source}/{hook-type}/{name}.log
Background removal{branch}/internal/remove.log

All post-* hooks (post-start, post-switch, post-commit, post-merge) run in the background and produce log files. Source is user or project. Branch and hook names are sanitized for filesystem safety (invalid characters → -; short collision-avoidance hash appended). Same operation on same branch overwrites the previous log. Removing a branch clears its subtree; orphans from deleted branches can be swept with wt config state logs clear.

FileCreated when
trace.logRunning with -vv
trace.jsonlRunning with -vv
subprocess.logRunning with -vv
diagnostic.mdRunning with -vv

trace.log is the human-readable trace at -vv — each command’s start ($ …) and completion (/✗ … 12.3ms), in-process spans, milestones, and bounded subprocess previews. trace.jsonl is the same event stream as one JSON object per line, for machines (jq, chrome://tracing); wt config state logs profile reads it to summarize a performance report (where time went, parallelism, redundant commands). subprocess.log holds the raw uncapped subprocess stdout/stderr bodies. diagnostic.md is a markdown bug-report bundle that leads with that same performance profile and inlines trace.log; wt prints a gh gist create command pointing at it. All four are overwritten on each -vv run.

All logs are stored in .git/wt/logs/ (in the main worktree’s git directory). All worktrees write to the same directory. Top-level files are shared logs (command audit + diagnostics); top-level directories are per-branch log trees.

wt config state logs --format=json emits three arrays — command_log, hook_output, diagnostic. Each entry carries a file (relative), path (absolute), size, and modified_at (unix seconds). Hook-output entries additionally expose branch, source (user / project / internal), hook_type (the post-* kind, or null for internal ops), and name. Filter with jq to pick out a specific entry.

List all log files:

wt config state logs

Query the command log:

tail -5 .git/wt/logs/commands.jsonl | jq .

Path to one hook log (e.g. the post-start server hook for the current branch):

wt config state logs --format=json | jq -r '.hook_output[] | select(.source == "user" and .hook_type == "post-start" and (.name | startswith("server"))) | .path'

Logs for a specific branch:

wt config state logs --format=json | jq '.hook_output[] | select(.branch | startswith("feature"))'

Clear all logs:

wt config state logs clear

Command reference

Section titled “Command reference”
wt config state logs - Operation and debug logs
Usage: wt config state logs [OPTIONS] [COMMAND]
Commands:
get List all log file paths
profile Performance profile from a trace
clear Clear all log files
Options:
-h, --help
Print help (see a summary with '-h')
Output:
--format <FORMAT>
Output format (text, json) [default: text]
Global Options:
-C <path>
Working directory for this command
--config <path>
User config file path
--config-set <toml>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
-v, --verbose...
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
-y, --yes
Skip approval prompts

CI status cache.

Deprecated — the CI status cache is now part of wt config state cache. This subcommand still works but prints a deprecation notice.

Status values, display symbols, and fetch behavior: wt list CI status.

Without a subcommand, runs get for the current branch. Use clear to reset cache for a branch or clear --all to reset all.

Command reference

Section titled “Command reference”
wt config state ci-status - CI status cache
Usage: wt config state ci-status [OPTIONS] [COMMAND]
Commands:
get Get CI status for a branch
clear Clear CI status cache
Options:
-h, --help
Print help (see a summary with '-h')
Output:
--format <FORMAT>
Output format (text, json) [default: text]
Global Options:
-C <path>
Working directory for this command
--config <path>
User config file path
--config-set <toml>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
-v, --verbose...
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
-y, --yes
Skip approval prompts

Branch markers.

Custom status text or emoji shown in the wt list Status column.

Markers appear at the end of the Status column, after git symbols:

wt list
Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message
@ main ^⇡ ⇡1 33323bc 1d Initial commit
+ feature-api 🤖 ↑1 +1 70343f0 1d Add REST API endp…
+ review-ui ? 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com…
+ wip-docs ? +1 33323bc 1d Initial commit
Showing 4 worktrees, 2 with changes, 2 ahead, 1 column hidden
  • Work status🚧 WIP, ready for review, 🔥 urgent
  • Agent tracking — The Claude Code plugin sets markers automatically
  • Notes — Any short text: "blocked", "needs tests"

Stored in git config as worktrunk.state.<branch>.marker. Set directly with:

git config worktrunk.state.feature.marker '{"marker":"🚧","set_at":0}'

Without a subcommand, runs get for the current branch. For --branch, use get --branch=NAME.

Command reference

Section titled “Command reference”
wt config state marker - Branch markers
Usage: wt config state marker [OPTIONS] [COMMAND]
Commands:
get Get marker for a branch
set Set marker for a branch
clear Clear marker for a branch
Options:
-h, --help
Print help (see a summary with '-h')
Output:
--format <FORMAT>
Output format (text, json) [default: text]
Global Options:
-C <path>
Working directory for this command
--config <path>
User config file path
--config-set <toml>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
-v, --verbose...
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
-y, --yes
Skip approval prompts

Custom variables per branch.

Store custom variables per branch. Values are stored as-is — plain strings or JSON.

Set and get values:

wt config state vars set env=staging
wt config state vars get env

Store JSON:

wt config state vars set config='{"port": 3000, "debug": true}'

List all keys:

wt config state vars list

Operate on a different branch:

wt config state vars set env=production --branch=main

Variables are available in hook templates as {{ vars.<key> }}. Use the default filter for keys that may not be set:

[post-start]
dev = "ENV={{ vars.env | default('development') }} npm start -- --port {{ vars.port | default('3000') }}"

JSON object and array values support dot access:

wt config state vars set config='{"port": 3000, "debug": true}'
[post-start]
dev = "npm start -- --port {{ vars.config.port }}"

Stored in git config as worktrunk.state.<branch>.vars.<key>. Keys must contain only letters, digits and hyphens — dots conflict with git config’s section separator, underscores with its variable name format.

Command reference

Section titled “Command reference”
wt config state vars - Custom variables per branch
Usage: wt config state vars [OPTIONS] <COMMAND>
Commands:
get Get a value
list List all keys
set Set a value
clear Clear a key or all keys
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
--config-set <toml>
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
-v, --verbose...
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
-y, --yes
Skip approval prompts