wt config
Manage user & project configs. Includes shell integration, hooks, and saved state.
Examples
Section titled “Examples”Install shell integration (required for directory switching):
wt config shell installCreate user config file with documented examples:
wt config createCreate project config file (.config/wt.toml) for hooks:
wt config create --projectShow current configuration and file locations:
wt config showConfiguration files
Section titled “Configuration files”| File | Location | Contains | Committed & shared |
|---|---|---|---|
| User config | ~/.config/worktrunk/config.toml | Worktree path template, LLM commit configs, etc | ✗ |
| Project config | .config/wt.toml | Project 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:
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:
[pre-start]deps = "npm ci"
[pre-merge]test = "npm test"User Configuration
Section titled “User Configuration”Create with wt config create. Values shown are defaults unless noted otherwise.
Location:
- macOS/Linux:
~/.config/worktrunk/config.toml(or$XDG_CONFIG_HOMEif set) - Windows:
%APPDATA%\worktrunk\config.toml
Worktree path template
Section titled “Worktree path template”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 likegroup/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.
LLM commit messages
Section titled “LLM commit messages”Generate commit messages automatically during merge. Requires an external CLI tool.
Claude Code
Section titled “Claude Code”[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'"OpenCode
Section titled “OpenCode”[commit.generation]command = "opencode run -m anthropic/claude-haiku-4.5 --variant fast"[commit.generation]command = "llm -m claude-haiku-4.5"aichat
Section titled “aichat”[commit.generation]command = "aichat -m claude:claude-haiku-4.5"See LLM commits docs for setup and Custom prompt templates for template customization.
Command config
Section titled “Command config”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 disablescolumns 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 namestatus— Git status symbols, plus any user-defined statusworking-diff— Uncommitted line changes againstHEAD, including untracked files (headerHEAD±)ahead-behind— Commits ahead of and behind the default branch (headermain↕)branch-diff— Line changes against the default branch (headermain…±)summary— An LLM-generated summary of the branchupstream— Commits ahead of and behind the upstream tracking branch (headerRemote⇅)ci— CI status of the head commitpath— The worktree’s pathurl— Dev-server URL from the[list] urltemplatecommit— The head commit’s short hashage— Time since the last commitmessage— 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
Section titled “Custom columns”
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 textwidth = 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 withwt config state vars set.{{ git.branch.* }}— the branch’s own git config underbranch.<name>.*, read straight fromgit config(e.g.{{ git.branch.jira }}for a key you set yourself, or the git-nativedescription). Git lowercases config variable names, sobranch.<name>.nvciShelfreads 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 }}"Commit
Section titled “Commit”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)Remove
Section titled “Remove”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
Section titled “Switch”[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.
Aliases
Section titled “Aliases”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 project-specific settings
Section titled “User project-specific settings”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 = truemerge.squash = falseremove.delete-branch = falsepre-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 platform and hostname
Section titled “Forge platform and hostname”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 itBoth 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"Custom prompt templates
Section titled “Custom prompt templates”Templates use minijinja syntax.
Commit template
Section titled “Commit template”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>
"""Squash template
Section titled “Squash template”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>
"""Appending to the prompt
Section titled “Appending to the prompt”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
Section titled “Project Configuration”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"Dev server URL
Section titled “Dev server URL”URL column in wt list (dimmed when port not listening):
[list]url = "http://localhost:{{ branch | hash_port }}"Forge platform
Section titled “Forge platform”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.
Commit-message append
Section titled “Commit-message append”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.
Copy-ignored excludes
Section titled “Copy-ignored excludes”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.
Aliases
Section titled “Aliases”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.
Shell Integration
Section titled “Shell Integration”Worktrunk needs shell integration to change directories when switching worktrees. Install with:
wt config shell installFor manual setup, see wt config shell init --help.
Without shell integration, wt switch prints the target directory but cannot cd into it.
First-run prompts
Section titled “First-run prompts”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.
Environment variables
Section titled “Environment variables”All user config options can be overridden with environment variables using the WORKTRUNK_ prefix.
Naming convention
Section titled “Naming convention”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:
| Config | Environment Variable |
|---|---|
worktree-path | WORKTRUNK_WORKTREE_PATH |
commit.generation.command | WORKTRUNK_COMMIT__GENERATION__COMMAND |
commit.stage | WORKTRUNK_COMMIT__STAGE |
Example: CI/testing override
Section titled “Example: CI/testing override”Override the LLM command in CI to use a mock:
WORKTRUNK_COMMIT__GENERATION__COMMAND="echo 'test: automated commit'" wt mergeOther environment variables
Section titled “Other environment variables”| Variable | Purpose |
|---|---|
WORKTRUNK_BIN | Override binary path for shell wrappers; useful for testing dev builds |
WORKTRUNK_CONFIG_PATH | Override user config file location |
WORKTRUNK_SYSTEM_CONFIG_PATH | Override system config file location |
WORKTRUNK_PROJECT_CONFIG_PATH | Override project config file location (defaults to .config/wt.toml); relative paths resolve from the worktree root |
XDG_CONFIG_DIRS | Colon-separated system config directories (default: /etc/xdg) |
WORKTRUNK_DIRECTIVE_CD_FILE | Internal: set by shell wrappers. wt writes a raw path; the wrapper cds to it |
WORKTRUNK_SHELL_CWD | Internal: set by wt on alias and hook bodies, so a nested wt preserves the user’s subdirectory |
WORKTRUNK_COMPLETE_NAME | Internal: set by shell wrappers to the command name completions register under (defaults to the binary name) |
WORKTRUNK_MAX_CONCURRENT_COMMANDS | Max parallel git commands (default: 32). Lower if hitting file descriptor limits. |
WORKTRUNK_VERBOSE | Verbosity level (0/1/2), like -v/-vv but applied everywhere — including shell completion, which no flag can reach |
RUST_LOG | Logging directive (e.g. worktrunk=debug); overrides the verbosity baseline for what reaches stderr |
NO_COLOR | Disable colored output (standard) |
CLICOLOR_FORCE | Force colored output even when not a TTY |
Inline config overrides (--config-set)
Section titled “Inline config overrides (--config-set)”--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 listwt 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.
Precedence
Section titled “Precedence”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:
--config-set 'worktree-path = …'WORKTRUNK_WORKTREE_PATH[projects."github.com/owner/repo"]in the config file- global
worktree-pathin 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 featureHooks, aliases and step.copy-ignored.exclude accumulate rather than replace, so an env-set hook and a project’s hook both run.
Command reference
Section titled “Command reference”wt config - Manage user & project configsIncludes shell integration, hooks, and saved state.Usage: wt config [OPTIONS] <COMMAND>Commands:shell Shell integration setupcreate Create configuration fileshow Show configuration files & locationsupdate Update deprecated config settingsapprovals Manage command approvalsalias Inspect and preview aliasesplugins Plugin managementstate Manage internal data and cacheOptions:-h, --helpPrint 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 debuglogs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 toapply the same level everywhere — including shell completion, which no flag can reach-y, --yesSkip approval promptsSubcommands
Section titled “Subcommands”wt config show
Section titled “wt config show”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.
Full diagnostics
Section titled “Full diagnostics”Use --full to run diagnostic checks:
wt config show --fullThis tests:
- CI tool status — Whether
gh(GitHub) orglab(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 & locationsUsage: wt config show [OPTIONS]Options:--fullRun diagnostic checks (CI tools, commit generation, version)-h, --helpPrint help (see a summary with '-h')Output:--format <FORMAT>Output formatGlobal 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 debuglogs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 toapply the same level everywhere — including shell completion, which no flag can reach-y, --yesSkip approval promptswt config approvals
Section titled “wt config approvals”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.
Examples
Section titled “Examples”List commands and their approval status for current project:
wt config approvals listPre-approve all hook and alias commands for current project:
wt config approvals addPre-approve without prompting, for a container or CI job:
wt config approvals add --yesClear approvals for current project:
wt config approvals clearClear only approvals for commands no longer in the project config:
wt config approvals clear --staleClear global approvals:
wt config approvals clear --globalCheck whether an unattended run would stop for approval:
wt config approvals list --format=json | jq -r .stateHow approvals work
Section titled “How approvals work”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.
Reading approval state
Section titled “Reading approval state”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 approvalsUsage: wt config approvals [OPTIONS] <COMMAND>Commands:list List project commands and their approval statusadd Store approvals in approvals.tomlclear Clear approved commands from approvals.tomlOptions:-h, --helpPrint 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 debuglogs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 toapply the same level everywhere — including shell completion, which no flag can reach-y, --yesSkip approval promptswt config alias
Section titled “wt config alias”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.
Examples
Section titled “Examples”Show every configured alias’s template:
wt config alias showShow the template for deploy:
wt config alias show deployPreview an invocation without running it:
wt config alias dry-run deploywt config alias dry-run deploy -- --env=stagingCommand reference
Section titled “Command reference”wt config alias - Inspect and preview aliasesUsage: wt config alias [OPTIONS] <COMMAND>Commands:show Show an alias's template, or all aliases' templatesdry-run Preview an alias invocation with template expansionOptions:-h, --helpPrint 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 debuglogs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 toapply the same level everywhere — including shell completion, which no flag can reach-y, --yesSkip approval promptswt config state
Section titled “wt config state”Manage internal data and cache.
State is stored in .git/ (config entries and log files), separate from configuration files.
- cache: Regenerable caches — CI status, summaries, git commands, hints, and the
wt switch -target - default-branch: The repository’s default branch (
main,master, etc.) - marker: Custom status marker for a branch (shown in
wt list) - vars: Custom variables per branch
- logs: Operation and debug logs
Examples
Section titled “Examples”Get the default branch:
wt config state default-branchSet the default branch manually:
wt config state default-branch set mainSet a marker for current branch:
wt config state marker set 🚧Store arbitrary data:
wt config state vars set env=stagingDrop the regenerable caches:
wt config state cache clearShow all stored state:
wt config state getClear all stored state:
wt config state clearCommand reference
Section titled “Command reference”wt config state - Manage internal data and cacheUsage: wt config state [OPTIONS] <COMMAND>Commands:get Get all stored stateclear Clear all stored statecache Regenerable cachesdefault-branch Default branch detection and overridelogs Operation and debug logsmarker Branch markersvars Custom variables per branchOptions:-h, --helpPrint 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 debuglogs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 toapply the same level everywhere — including shell completion, which no flag can reach-y, --yesSkip approval promptswt config state cache
Section titled “wt config state cache”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.
What’s cached
Section titled “What’s cached”- 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 switchpreview) - Git commands — cached merge-tree, ancestry, diff-stat, and
wt switchpreview 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.
Examples
Section titled “Examples”Show cache contents:
wt config state cacheDrop all caches:
wt config state cache clearCommand reference
Section titled “Command reference”wt config state cache - Regenerable cachesUsage: wt config state cache [OPTIONS] [COMMAND]Commands:get Show cache contentsclear Drop all cachesOptions:-h, --helpPrint help (see a summary with '-h')Output:--format <FORMAT>Output format (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 debuglogs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 toapply the same level everywhere — including shell completion, which no flag can reach-y, --yesSkip approval promptswt config state default-branch
Section titled “wt config state default-branch”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.
Detection
Section titled “Detection”Worktrunk detects the default branch automatically:
- Worktrunk cache — Checks
git config worktrunk.default-branch - Git cache — Detects primary remote and checks its HEAD (e.g.,
origin/HEAD) - Remote query — If not cached, queries
git ls-remote— typically 100ms–2s, abandoned after 10s - 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 overrideUsage: wt config state default-branch [OPTIONS] [COMMAND]Commands:get Get the default branchset Set the default branchclear Clear the default branch cacheOptions:-h, --helpPrint 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 debuglogs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 toapply the same level everywhere — including shell completion, which no flag can reach-y, --yesSkip approval promptswt config state logs
Section titled “wt config state logs”Operation and debug logs.
View and manage log files — hook output, command audit trail, and debug diagnostics.
What’s logged
Section titled “What’s logged”Three kinds of logs live in .git/wt/logs/:
Command log (commands.jsonl)
Section titled “Command log (commands.jsonl)”All hook executions and LLM commands are recorded automatically — one JSON object per line. Rotates to commands.jsonl.old at 1MB (~2MB total). Fields:
| Field | Description |
|---|---|
ts | ISO 8601 timestamp |
wt | The wt command that triggered this (e.g., wt hook pre-merge --yes) |
label | What ran (e.g., pre-merge user:lint, commit.generation) |
cmd | Shell command executed |
exit | Exit code (null for background commands) |
dur_ms | Duration 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 logs
Section titled “Hook output logs”Hook output lives in per-branch subtrees under .git/wt/logs/{branch}/:
| Operation | Log 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.
Diagnostic files
Section titled “Diagnostic files”| File | Created when |
|---|---|
trace.log | Running with -vv |
trace.jsonl | Running with -vv |
subprocess.log | Running with -vv |
diagnostic.md | Running 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.
Location
Section titled “Location”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.
Structured output
Section titled “Structured output”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.
Examples
Section titled “Examples”List all log files:
wt config state logsQuery 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 clearCommand reference
Section titled “Command reference”wt config state logs - Operation and debug logsUsage: wt config state logs [OPTIONS] [COMMAND]Commands:get List all log file pathsprofile Performance profile from a traceclear Clear all log filesOptions:-h, --helpPrint help (see a summary with '-h')Output:--format <FORMAT>Output format (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 debuglogs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 toapply the same level everywhere — including shell completion, which no flag can reach-y, --yesSkip approval promptswt config state ci-status
Section titled “wt config state ci-status”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 cacheUsage: wt config state ci-status [OPTIONS] [COMMAND]Commands:get Get CI status for a branchclear Clear CI status cacheOptions:-h, --helpPrint help (see a summary with '-h')Output:--format <FORMAT>Output format (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 debuglogs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 toapply the same level everywhere — including shell completion, which no flag can reach-y, --yesSkip approval promptswt config state marker
Section titled “wt config state marker”Branch markers.
Custom status text or emoji shown in the wt list Status column.
Display
Section titled “Display”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 hiddenUse cases
Section titled “Use cases”- Work status —
🚧WIP,✅ready for review,🔥urgent - Agent tracking — The Claude Code plugin sets markers automatically
- Notes — Any short text:
"blocked","needs tests"
Storage
Section titled “Storage”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 markersUsage: wt config state marker [OPTIONS] [COMMAND]Commands:get Get marker for a branchset Set marker for a branchclear Clear marker for a branchOptions:-h, --helpPrint help (see a summary with '-h')Output:--format <FORMAT>Output format (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 debuglogs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 toapply the same level everywhere — including shell completion, which no flag can reach-y, --yesSkip approval promptswt config state vars
Section titled “wt config state vars”Custom variables per branch.
Store custom variables per branch. Values are stored as-is — plain strings or JSON.
Examples
Section titled “Examples”Set and get values:
wt config state vars set env=stagingwt config state vars get envStore JSON:
wt config state vars set config='{"port": 3000, "debug": true}'List all keys:
wt config state vars listOperate on a different branch:
wt config state vars set env=production --branch=mainTemplate access
Section titled “Template access”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 }}"Storage format
Section titled “Storage format”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 branchUsage: wt config state vars [OPTIONS] <COMMAND>Commands:get Get a valuelist List all keysset Set a valueclear Clear a key or all keysOptions:-h, --helpPrint 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 debuglogs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 toapply the same level everywhere — including shell completion, which no flag can reach-y, --yesSkip approval prompts