Command reference
One entry per command: synopsis, flags, behavior, refusals, examples. The binary is worktree; wt is the shell function emitted by wt init that wraps it and evals its jump scripts.
CONVENTIONS
Synopsis notation. [x] optional, <x> placeholder, a|b alternatives, … repetition. Flags come before positional arguments; parsing stops at the first non-flag word.
Prefix matching. Wherever a worktree or project name is expected, any unique prefix works. An ambiguous prefix is an error.
Jump commands. A child process can't cd its parent — jumps print a two-line script on stdout (cd '<path>' + export WT_HOME=…) which the wt() wrapper evals. Every jump touches the destination's CHECKOUT stamp.
Output streams. stdout is machine-readable only: tables, status fields, jump scripts, passthrough git. Narration goes to stderr, prefixed wt:. Piped output and NO_COLOR disable coloring.
Exit codes. 0 success · 1 any error or refusal (message + often a hint: line on stderr) · 2 no arguments (usage).
Kind colors. cyan root repo · green wt worktrees · orange bases (red when drifted) · magenta external · red peeks.
Creating worktrees
wt create
wt create [-c|--checkout] [-n|--name <name>] [-w|--wait | --no-deps | --no-ignored] [<branch>]
Adds a worktree for <branch>, resolved in order:
1Local branch, free — plain worktree on that branch.
2Local branch, checked out elsewhere — git forbids a second checkout; a fresh auto-suffixed branch (main → main-2) is cut at its tip, with <branch> recorded as base.
3Remote-only branch — new local tracking branch (prefers origin), <remote>/<branch> recorded as base.
4Unknown — error with a hint to git fetch first. create never fetches on its own.
With no argument: a clean worktree at the current commit on a new auto-named branch (main-2, main-3, …; current branch recorded as base). Uncommitted changes stay behind — use fork to bring them. The worktree lands at <workspace-root>/<project>/<name> and is seeded with the current worktree's git-ignored files; declared deps copy in the background after the jump.
-c, --checkout
emit the jump script into the new worktree
-n, --name <name>
directory name override; a leading dash appends to the branch name (-n -fix on feature/auth → feature-auth-fix)
-w, --wait
copy deps synchronously (block until done)
--no-deps
skip deps this run; other ignored files still copy
--no-ignored
copy nothing ignored at all (implies no deps)
Refuses when: the repo is not linked; the branch is unknown locally and on every remote; no argument on a detached HEAD (hint: wt fork <name>).
$ wt create feature/auth
wt: branch 'feature/auth' found locally (origin/feature/auth: ahead 2, behind 1)
wt: created worktree 'feature-auth' at /home/dev/worktrees/myrepo/feature-auth
wt: switch with: wt ch feature-auth
See also: wt fork · wt deps · wt link
wt fork
wt fork [-c|--checkout] [-n|--name <name>] [-w|--wait | --no-deps | --no-ignored] [<new-branch>]
Forks your current state: a new branch at HEAD, a new worktree, and all your staged, unstaged and untracked changes carried over with the staged/unstaged split preserved exactly. The original worktree is untouched — copy, not move (implemented as git stash create + stash apply --index, plus a file copy for untracked files). Without an argument the branch is auto-named like create.
Passing an existing branch makes it the base instead: wt fork main from a dirty dev cuts an auto-named main-2 at main's tip and applies your changes onto it — "take my WIP onto a fresh main". If the apply isn't clean, the worktree stays with instructions to finish manually.
fork always records the worktree's base branch, so a later argument-less wt reset works. Flags: identical to wt create.
Refuses when: the repo is not linked.
$ wt fork
wt: created new branch 'main-2' from HEAD (4b9a96f)
wt: created worktree 'main-2' at /home/dev/worktrees/devbox/main-2
wt: carried over: 2 staged, 1 unstaged, 3 untracked file(s)
wt: switch with: wt ch main-2
See also: wt create · wt checkout -b · wt root fork
Navigation
wt checkout (alias: ch)
wt checkout [<name>]
wt checkout -b <new-branch>
Jumps to a worktree by directory name (unique prefix works). No git state changes hands — every worktree permanently has its branch checked out.
Bare wt ch opens an interactive picker (terminal only): the 10 most recently used worktrees, newest first. Arrows or j/k move, Enter jumps, Esc/q cancels.
-b <new-branch> works like git checkout -b: fork your current state into a new worktree on that branch and jump there — shorthand for wt fork -c <new-branch>.
$ wt ch feat # unique prefix of feature-auth
wt: → myrepo/feature-auth
See also: wt home · wt switch · wt finish
wt home
wt home
Jumps to the main checkout (the root repo) from wherever you are. Equivalent to wt root checkout.
wt switch
wt switch
Toggles between where you are and where your last wt jump left from — like cd -. Run it twice and you're back. The state is per shell: it lives in the wt() wrapper (handed to the binary as WT_PREV), so every terminal has its own toggle, and the command requires the wrapper.
Refuses when: no previous location exists in this shell (hint: jump somewhere first); the previous directory no longer exists.
wt finish
wt finish [-d|--delete] [-b|--branch] [-f|--force]
The "I'm done here" command. Jumps back to the last location this shell jumped from (the same per-shell state switch uses), or home when there is none — and, on request, cleans up on the way out. If the remembered location is inside the worktree being deleted, finish lands you home instead.
-d, --delete
also remove the worktree (branch kept)
-b, --branch
also delete its branch — safe delete, requires -d
-f, --force
discard local changes (needed for -d on a dirty worktree)
Refuses when: -b without -d; run in the main checkout; -d on a base worktree (hint: wt base rm); -d with local changes and no -f.
wt finish # just go back — the worktree stays
wt finish -d # …and delete the worktree
wt finish -d -b # …and its branch too (only if merged)
See also: wt remove · wt switch
wt peek (alias: unpeek)
wt peek [-w|--wait | --no-deps | --no-ignored] <rev>
wt peek # list open peeks
wt peek off [<name>] # jump back and delete (alias: wt unpeek)
Exports <rev>'s files (any revision: origin/main, a tag, a sha, HEAD~3) into a disposable fake worktree under ~/worktrees/<project>/peek-… — a plain directory with no branch, no checkout, no git registration — and jumps there. Your real worktrees are never written to, so peeking is safe from any state: dirty, mid-rebase, whatever.
The environment comes along so the peeked code runs: ignored files copy synchronously, declared deps in the background (-w, --no-deps, --no-ignored behave as on create). It is a viewer by contract: git commands inside a peek fail and edits are throwaway. Open peeks appear in bare wt peek and as red peek rows in wt list. If looking turns into working, use wt fork or wt create.
$ wt peek v2.1.0
wt: peeking 'v2.1.0' (163a116) — read-only snapshot, edits here are throwaway
wt: wt peek off returns and deletes it
Cross-context commands
wt root
wt root <status|checkout|create|fork> [args…]
Runs a command in the root repo's context from wherever you stand; all flags pass through unchanged.
root status [-g]
the root's status, as if run there
root checkout
jump to the root (same as wt home); with a name or -b, behaves like checkout run at the root
root create …
create based on the root's current branch
root fork [-c] …
fork the root's current state (its branch and changes), even while you're in another worktree
wt global
wt global list|ls [<project>]
wt global status [-g] <project>[/<worktree>]
wt global git-log <project>[/<worktree>] [git-log args…]
wt global checkout|ch <project>[/<worktree>]
Works outside any repo, over projects registered with wt link -r. list with no argument prints all registered projects (NAME · WORKTREES · PATH); with a project, that project's full worktree table. status, git-log and checkout behave like their in-repo counterparts, addressed as <project> (the root repo) or <project>/<worktree>.
Project names prefix-match like worktree names. The registry is plain global git config (wt.project.<name> → root repo path); entries whose repo vanished are pruned automatically with a note.
wt global ls tickets-app
wt global git-log tickets-app/dev --oneline -5
wt global ch tickets-app/dev
Base branches
wt base
wt base [list]
wt base add [--no-ignored] [--deps [-w|--wait]] <branch>
wt base rm [-f] <name>
wt base update [<name>]
wt base reset [--hard] [<name>]
A base branch is a long-lived branch (main, staging, dev, …) promoted to a permanent, view-only worktree named after the branch: jump in to inspect or run it with zero impact on your real work, and fork off it the moment looking becomes working. Bases sort right after the root in wt list and show orange names.
add <branch>
promote an existing local or remote branch. Seeds the base with the current worktree's ignored files but not declared deps (a base is a launch pad, not a build directory): --deps opts in (-w waits), --no-ignored copies nothing. Refuses branches checked out in any worktree.
list
(default) the worktree table filtered to bases
rm [-f] <name>
remove the base worktree; the branch is never touched. -f discards local changes. Plain wt rm refuses bases outright.
update [<name>]
fetch and fast-forward bases to their upstreams — all, or one. ff-only: it can never lose anything; dirty, drifted or diverged bases are skipped with a note.
reset [--hard] [<name>]
restore pristine: a drifted base gets its own branch re-checked out, then hard-synced to the upstream tip, backward moves included. Local changes block unless --hard (tracked discarded, untracked kept). No name needed inside the base.
Drift. wt can't stop git from switching a base's branch — it flags it: a drifted base gets a red BRANCH cell and a ! suffix in wt list, and (base: <name>, drifted) in wt status. wt reset refuses to run inside a base — other worktrees reset to it.
$ wt base add staging
wt: added base 'staging' at /home/dev/worktrees/myrepo/staging
wt: jump with: wt ch staging — fork real work off it with: wt fork staging
Inspection
wt list (alias: ls)
wt list
NAME BRANCH STATE COMMIT CREATED CHECKOUT
* devbox main dirty 4b9a96f - now
feature-auth feature/auth clean 163a116 2d 5h
main-2 main-2 clean 4b9a96f 3h -
• * marks the row you stand in.
• STATE is clean, dirty, or syncing (deps copy still running).
• CREATED is the worktree's age from git's own metadata (root shows -).
• CHECKOUT is the last time wt jumped there — recorded as a stamp in the worktree's git admin dir; starts as - and travels/dies with the worktree.
• Order: root first, then bases, then everything else by most recent checkout (most recently created as tiebreak). Open peeks appear as red peek rows.
wt status
wt status [-g|--git] [<name>]
Shows one worktree — current by default, or any by name — as project, worktree (with a (home) marker on the main checkout, base/drift notes on bases), branch, commit, and path fields on stdout. While deps are syncing, a deps line shows the state and log path. Inside a peek, the peek view with its exit hint is shown instead.
-g, --git appends real git status output as if run in that worktree — passthrough, so your git config applies.
$ wt status feature-auth
project myrepo
worktree feature-auth
branch feature/auth
commit 163a116 Kirill G, 2 days ago: auth: add session refresh
path /home/dev/worktrees/myrepo/feature-auth
wt git-log
wt git-log [<name>] [git-log arguments…]
Runs plain git log in a worktree without leaving where you stand. The first argument is taken as a worktree name only when it doesn't start with -; everything else passes to git log verbatim, so wt git-log dev --oneline -5 and wt git-log --oneline -5 (current worktree) both do what you'd expect. Passthrough like status -g: your pager, colors and log config all apply.
Files and dependencies
wt copy
wt copy [-f] [--from <worktree>] <file> [<dst>]
Copies one file into the current worktree — from the root repo by default, or from any worktree via --from (prefix matching). Made for the files git doesn't carry across worktrees: .env, local configs, credentials. Paths are relative to each worktree's root; <dst> defaults to the same relative path. Parent directories are created; symlinks and executable bits survive. An existing destination is refused unless -f overwrites it.
wt copy .env # root repo's .env → here
wt copy --from dev-2 .env.local # from another worktree
wt copy .env config/.env.dev # different destination path
wt deps
wt deps [list]
wt deps add <path>
wt deps rm <path>
wt deps sync [<name>]
Manages deps — heavy git-ignored paths (node_modules, build caches, …) declared once per repo (multi-valued git config wt.deps) and copied into new worktrees by a detached background worker: on create/fork/peek you're already in the new worktree while the dep streams in. Each dep is built in a .wt-partial sibling and renamed into place, so a half-copied directory never appears at its real path. Small ignored files still copy synchronously before the jump.
While a copy runs, wt list shows syncing and wt status a deps line with the log path. wt rm refuses a still-syncing worktree; wt rm -f stops the copier first (its pid rides in the state marker), so a forced removal never races the copy.
sync [<name>] re-copies deps into a worktree in the foreground — recovery after a failed background copy, or onboarding a worktree created before deps were declared (bases included). Related flags on create/fork/peek/base add: -w/--wait, --no-deps, --no-ignored.
Cleanup
wt remove (alias: rm)
wt remove [-f] [-b] <name>
wt remove [-f] [-b] '<glob>'
Removes a worktree by name (prefix matching).
-f
remove even with local changes; also stops a running deps copy first
-b
also delete the branch — safely (git branch -d), so an unmerged branch survives with a hint
Refuses when: the worktree is dirty and -f is missing; the target is the main checkout, the worktree you stand in, or a base (hint: wt base rm); deps are still syncing (without -f).
Glob bulk-removal. A pattern (quote it so the shell doesn't expand it) removes several at once: wt rm 'dev-*'. Wildcards only ever match wt-managed worktrees — never the root, bases, other tools' worktrees, or the one you stand in — and always print the matches and ask [y/N] first; only a plain y proceeds. -f/-b apply to every match.
wt reset
wt reset [--hard] [<base>]
Moves the worktree's branch back to the tip of its base branch, staying on the branch. The base is recorded automatically at creation for every fork and for auto-named or remote creates (main-2 remembers main; remote-created worktrees remember origin/<branch>); wt create <branch> from a free local branch records none — there, and for worktrees made by other tools, name the base explicitly: wt reset <branch> always works and overrides the recording.
Local changes block it; --hard proceeds, discarding tracked changes (untracked files are kept — standard git reset --hard semantics).
Refuses when: run in the main checkout; run inside a base worktree (hint: wt base reset snaps a base back to its upstream); no recorded base and no argument.
Configuration and meta
wt link
wt link [-r|--register] [<name>]
Links the repo to a project name, stored in git config wt.name (shared .git/config: visible from every worktree, never tracked, per-clone — a fresh clone needs linking again). The name decides where worktrees live (~/worktrees/<name>/…) and is deliberate, not derived from the directory, so two repos both cloned as app/ don't collide.
create and fork refuse to run in unlinked repos; everything else works regardless. Relinking is allowed — existing worktrees keep working. Unlink with git config --unset wt.name.
-r, --register also adds the project to the global registry that wt global works from. Bare wt link -r registers an already-linked repo — registration is always explicit. With no arguments, shows the current link and whether it is registered.
wt alias
wt alias [list]
wt alias add <name> <command…>
wt alias rm <name>
Git-style command aliases: name your favorite invocations and wt expands them, typed arguments appended. add/rm write your global git config (wt.alias.<name>) — aliases are a habit, not a project property — but any scope works: a per-repo alias is just git config wt.alias.stg "status -g".
Builtins always win over an alias of the same name. An alias must expand to a builtin. Aliases tab-complete like the command they expand to, and an alias of a jump command cd's exactly like the real thing.
wt alias add cr create -c # wt cr feature → wt create -c feature
wt alias add stg status -g
wt init
wt init <zsh|bash>
Prints the wt() shell function and tab completion for eval in your rc file: eval "$(worktree init zsh)". The function is what makes jumps actually cd (it evals the binary's jump script) and what keeps the per-shell state behind switch/finish.
Completion: subcommands complete on the first word, and everywhere a command accepts an existing worktree or branch name, TAB offers real candidates computed live by the binary (rm only offers what is removable; create also lists remote-only branches). If your .zshrc runs the eval before compinit, wt initializes the completion system itself. The wrapper carries a version marker; an outdated one triggers a stderr warning suggesting a re-eval.
wt prompt
worktree prompt zsh
Prints a ready-made <project> (<branch>) prompt segment in pure zsh prompt escapes: the project name instead of the checkout's folder name (unlinked repos fall back to the repo name, non-git directories to the plain path), with the branch colored in the wt list palette — and a red peek:<rev> inside a peek. Literal % in names is escaped, so directory names can't inject prompt codes. One binary call per prompt (~5 ms). zsh only.
setopt PROMPT_SUBST
_wt_prompt() { GIT_OPTIONAL_LOCKS=0 command worktree prompt zsh 2>/dev/null || print -rn -- '%B%F{cyan}%~%f%b'; }
PROMPT='%F{green}%n@%m%f: $(_wt_prompt) %B$%b '
wt help / wt --version
wt help # also: -h, --help — usage on stdout, exit 0
wt --version # "wt <tag> (<commit>)", or "wt dev" for unstamped builds
make install stamps the version via git describe; building outside a checkout takes make install VERSION=<tag> COMMIT=<sha>.
Internal commands. complete, __jump-alias and __deps-worker exist for tab completion, jump-alias detection and the background deps copier. Implementation details — not for direct use, no compatibility promise.
Configuration keys and environment
KEY / VARIABLESCOPEMEANING
wt.name
repo (shared .git/config)
project link name; decides ~/worktrees/<name>/
wt.deps
repo, multi-valued
ignored paths copied in the background
wt.copyignored
repo
false disables ignored-file copying on create/fork
wt.root
repo or global
worktree base directory (default ~/worktrees); ~ and relative values resolve against home
wt.alias.<name>
any scope (default global)
command alias
wt.project.<name>
global
registry entry for wt global → root repo path
WT_ROOT
environment
worktree base directory override (beats config)
WT_HOME
environment
exported by every jump: the repo's main checkout path
WT_PREV
environment
set by the wt() wrapper: previous location for switch/finish
NO_COLOR
environment
plain, uncolored output