Skip to content

Configuration

This content is for v0.28. Switch to the latest version for up-to-date documentation.

All wtm files live under <git-common-dir>/wtm/ (.git/wtm/ for a normal clone). Git never commits anything inside .git/, so wtm is invisible to teammates and to git status: worktree usage stays personal.

<git-common-dir>/wtm/
├── config.toml # project settings
├── run.toml # dev jobs + profiles
├── schemas/ # JSON schemas for editor autocomplete
├── worktrees/<encoded-branch>/
│ └── meta.json # source branch, timestamp, env strategy, ordinal, isolation, namespaces
├── logs/<encoded-branch>/ # each job's output, one file per job
├── hooks/ # the raw output of the last on_create / on_clean run, per branch
└── pending-removals.toml # namespace drops a clean still owes

Each file and each meta.json field is described in Where wtm keeps its state.

Everything is plain TOML, validated at load time (unknown keys are rejected, not silently ignored). Edit by hand, or use wtm config show / wtm config edit and the wtm run import flow.

Generated by wtm init, per-clone, never committed.

[worktrees]
base_path = "../.trees" # where worktrees are created (relative to repo root)
base_branch = "main" # default base for new worktrees
[env]
strategy = "example" # example | main | parent (see below)
# Each detected value file and its committed template (schema). wtm distinguishes
# templates (.env.example / .dist / .sample / .template / .tmpl, committed) from
# value files (.env, gitignored). .env.local is detected and flagged local but
# stays syncable.
[[env.file]]
target = ".env"
template = ".env.example"
[[env.file]]
target = ".env.local"
local = true
[hooks]
on_create = [
"pnpm install", # string: runs from worktree root
{ cmd = "pnpm install", cwd = "apps/api" }, # object: runs from a subdir
{ cmd = "pnpm install", cwd = "apps/web", continue_on_error = true }, # non-fatal
]
on_clean = [
"docker compose down", # runs right before a worktree is removed
]

on_create hooks run after a worktree is created; on_clean hooks run in the worktree just before it is removed by clean/prune (e.g. to tear down external resources). A non-zero hook aborts the operation unless the entry sets continue_on_error. Hooks interpolate {{worktree}}, {{branch}}, {{root}}, and (for on_create) {{from_branch}}. A hook is a /bin/sh line and each value is quoted for the spot it lands in, so a path holding a ' or a $ reaches the command as it is spelled on disk: cd {{worktree}}, cd "{{worktree}}" and cd '{{worktree}}' all work.

A hook also gets the worktree's run variables (COMPOSE_PROJECT_NAME, WTM_* and the declared ports, see Run config) only when run.toml declares a job running docker compose and the worktree recorded its isolation (isolation in its meta.json: every worktree created since the choice exists, or adopted with wtm env <branch> --isolation isolated). Otherwise a hook runs with the environment it always had, and resolving nothing allocates no ordinal. When the worktree's own .env (in the directory a compose job runs from) sets COMPOSE_PROJECT_NAME, that value is the one a hook gets.

Strategy Behavior
example Copies file.example from the main checkout, renamed to file. Warns if .example is missing.
main Copies the actual file from the main checkout.
parent Copies from the source worktree (--from), falling back to main.

The strategy is recorded per worktree. Later, wtm env reconciles a worktree's .env against its template (the committed schema) plus the same value source, adding missing keys and (with --mode refresh) settling values that drifted. Override the source for a single run with --from; the report always shows which source was used.

Created by wtm init, personal to each developer. It lives under the OS config directory (~/.config/wtm/config.toml on Linux, ~/Library/Application Support/wtm/config.toml on macOS), and wtm run proxy status prints the resolved path.

shell = "zsh" # zsh | bash | fish
[ui]
animations = true # false disables every wtm ui animation (tab rule, new-row flash)
[proxy]
port = 11080 # where named job URLs are served, on the loopback only
enabled = true # false sends every URL back to http://localhost:<port>

ui.animations defaults to on when absent; set it to false to turn off every wtm ui animation at once, useful over a slow or laggy connection.

[proxy] serves each worktree's HTTP jobs under their own hostname (http://<job>.<worktree>.<repo>.localhost:11080), so two worktrees stop sharing one cookie jar. A job opts in with url = { port = "PORT" } in run.toml, which wtm run init writes for the services it detects; url.host replaces the job's name as the first label (url = { port = "PORT", host = "api" }). Both keys default to the values above; the proxy lives in the background daemon and dies with it, and a port it cannot bind costs the names, never the jobs.

A job started by another job is served too. A root turbo run dev is one process holding several apps, declared as runs = ["web", "api"]: starting it registers the name of every published job it runs, so http://web.<worktree>.<repo>.localhost:11080 answers even though the only job the daemon holds is the runner. Those addresses are reported on the runner (under its line in a run, on its row in wtm ui), and the apps it holds get no row of their own while it is up: they are its subprocesses, not jobs beside it.

Every TOML file wtm init writes starts with a #:schema ./schemas/...json directive. Pair it with Even Better TOML (or the bundled JetBrains TOML plugin) for autocomplete, hover docs, and real-time validation. Schemas ship with the binary and are written beside each file every time wtm writes it (init, init --only, run init, run job / run profile, relocate…), no internet required. To refresh them after upgrading without touching a config:

Terminal window
wtm schema dump # <git-common-dir>/wtm/schemas/{run,project}.schema.json
wtm schema dump --global # global.schema.json, beside the global config