Configuration
wtm init # the wizard: worktree location, base branch, .env provisioning, hookswtm config show # the resolved project configwtm config edit # open config.toml in your editorwtm init --only hooks --yes # regenerate one section (env, hooks, worktrees)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├── exec/ # the output of the last wtm exec, one file per worktree└── pending-removals.toml # namespace drops a clean still owesEach 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.
Project config: config.toml
Section titled “Project config: config.toml”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_createhooks run after a worktree is created;on_cleanhooks run in the worktree just beforeclean/pruneremoves it (to tear down external resources). A non-zero hook aborts the operation unless the entry setscontinue_on_error.- A hook is a
/bin/shline. It interpolates{{worktree}},{{branch}},{{root}}and (foron_create){{from_branch}}, each quoted for the spot it lands in, so a path holding a'or a$arrives as spelled on disk:cd {{worktree}},cd "{{worktree}}"andcd '{{worktree}}'all work. - The raw output of the last run of each phase is kept in
hooks/<phase>-<branch>.log.
A hook also gets the worktree's run variables (COMPOSE_PROJECT_NAME, WTM_* and the declared ports, see How wtm run works) 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.
The environment of wtm exec
Section titled “The environment of wtm exec”wtm exec --all -- 'echo "$WTM_BRANCH on port offset $WTM_PORT_OFFSET"'wtm exec runs its command with the run variables a hook gets, under the same conditions. One difference: a hook keeps the environment it inherited when no run variable applies, while wtm exec always removes the variables that describe a worktree (WTM_WORKTREE, WTM_BRANCH, WTM_ORDINAL, WTM_PORT_OFFSET, WTM_ISOLATION, COMPOSE_PROJECT_NAME) before adding the target's. A hook runs on the worktree just created from where you stand; an exec command runs in worktrees you are not in, and the values your shell carries would point it at your stack instead of theirs. The recipe is in Run a command across worktrees.
Env strategies
Section titled “Env strategies”| 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; wtm create --env-from <strategy> overrides it for one 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. --from overrides the source for one run, and the report always shows which source was used.
Global config
Section titled “Global config”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); 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 onlyenabled = true # false sends every URL back to http://localhost:<port>ui.animationsdefaults to on when absent;falseturns off everywtm uianimation at once, useful over a slow 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. 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 opts in withurl = { port = "PORT" }inrun.toml, whichwtm run initwrites for the services it detects;url.hostreplaces the job's name as the first label (url = { port = "PORT", host = "api" }). See Named URLs.- A job started by another job is served too. A root
turbo run devholding several apps, declared asruns = ["web", "api"], registers the name of every published job it runs, sohttp://web.<worktree>.<repo>.localhost:11080answers even though the daemon only holds the runner. Those addresses are reported on the runner (under its line in a run, on its row inwtm ui), and the apps get no row of their own while it is up.
IDE autocomplete + validation
Section titled “IDE autocomplete + validation”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:
wtm schema dump # <git-common-dir>/wtm/schemas/{run,project}.schema.jsonwtm schema dump --global # global.schema.json, beside the global configThe schemas of the latest release are also published at https://wtm.sh/schemas/<name>.json (project.schema.json, run.schema.json, global.schema.json, events.v1.json), for a file written outside a repository or a tool that should not depend on a local copy.