Requested by @max-sixty in [#3809](https://github.com/max-sixty/worktrunk/issues/3809#issuecomment-5273484502) — a couple of words clarifying that `Alt-x`'s removal is safe-only. The keybinding table read `Remove selected worktree/branch`, which doesn't say the removal never forces; that's what sent the reporter looking for a force-remove that isn't there. The picker hardcodes the safe path — [`prepare_removal`](https://github.com/max-sixty/worktrunk/blob/7a2a3e003e7eed138ff5f2dcd2296f6bbd8e86d4/src/commands/picker/mod.rs#L323-L330) passes `BranchDeletionMode::SafeDelete` and `force_worktree: false`. Deliberately scoped to the table cell, per the "(only)" in the request. The bigger question — whether `Alt-x` should ever pass `-D` — is still open on the issue and isn't touched here. Primary source is `after_long_help` in `src/cli/mod.rs`; the three mirrors and the `--help` snapshot are regenerated. Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
14 KiB
+++ title = "wt switch" description = "Switch to a worktree; create if needed." weight = 10
[extra] group = "Commands" +++
Switch to a worktree; create if needed.
Worktrees are addressed by branch name; paths are computed from a configurable template. Unlike git switch, this navigates between worktrees rather than changing branches in place.
Examples
{{ terminal(cmd="wt switch feature-auth # Switch to worktree|||wt switch - # Previous worktree (like cd -)|||wt switch --create new-feature # Create new branch and worktree|||wt switch --create hotfix --base production|||wt switch pr:123 # Switch to PR #123's branch|||wt switch https://github.com/owner/repo/pull/123 # ...or paste the PR's URL") }}
Creating a branch
The --create flag creates a new branch from --base — the default branch unless specified. Without --create, the branch must already exist. Switching to a remote branch (e.g., wt switch feature when only origin/feature exists) creates a local tracking branch.
Creating worktrees
If the branch already has a worktree, wt switch changes directories to it. Otherwise, it creates one:
- Runs pre-switch hooks, blocking until complete
- Creates worktree at configured path
- Switches to new directory
- Runs pre-start hooks, blocking until complete
- Spawns post-start and post-switch hooks in the background
{{ terminal(cmd="wt switch feature # Existing branch → creates worktree|||wt switch --create feature # New branch and worktree|||wt switch --create fix --base release # New branch from release|||wt switch --create temp --no-hooks # Skip hooks") }}
Naming a worktree
Worktrees are addressed by branch name, and every argument that takes one also accepts the path of the worktree itself — resolved after the branch, so a directory never shadows a branch sharing its name. A path names what a branch cannot: a detached worktree, or one of two checkouts of the same branch. Relative paths resolve against -C and a leading ~ against the home directory, so a path worktrunk printed can be pasted back.
Shortcuts
| Shortcut | Meaning |
|---|---|
^ |
Default branch (main/master) |
@ |
Current branch/worktree |
- |
Previous worktree (like cd -) |
pr:{N} |
GitHub PR #N's branch |
mr:{N} |
GitLab MR !N's branch |
{{ terminal(cmd="wt switch - # Back to previous|||wt switch ^ # Default branch worktree|||wt switch --create fix --base=@ # Branch from current HEAD|||wt switch --create fix --base=pr:123 # Branch from PR #123's head|||wt switch pr:123 # PR #123's branch|||wt switch mr:101 # MR !101's branch") }}
Shortcuts also apply to --base. For a fork PR/MR, the head commit is fetched and used as the base SHA without creating a tracking branch.
Interactive picker
When called without arguments, wt switch opens an interactive picker to browse and select worktrees with live preview. The candidate set widens with --branches (local branches without worktrees), --remotes (remote branches), and --prs (open PRs/MRs — see below).
The CI column shows each row's PR/MR CI and review status, the same as wt list --full.
Keybindings:
| Key | Action |
|---|---|
↑/↓ |
Navigate worktree list |
| (type) | Filter worktrees |
Enter |
Switch to selected worktree |
Alt-c |
Create new worktree named as entered text |
Alt-x |
Remove selected worktree/branch (never forces) |
Alt-y |
Copy selected branch name to the clipboard |
Alt-o |
Open the selected row's PR/MR URL in the browser |
Alt-r |
Refresh the list (pick up worktrees created elsewhere) |
Esc |
Cancel |
Alt-1–Alt-7 |
Jump to a preview tab |
Tab/Shift-Tab |
Cycle preview tabs forward/backward |
Alt-p |
Toggle preview panel |
Ctrl-u/Ctrl-d |
Scroll preview up/down |
Alt-o is a no-op on a row with no PR/MR (or whose status hasn't loaded yet).
Alt-x is a no-op on the current worktree (the @ row) — removing the worktree in use would have to switch elsewhere first, so switch away and remove it from there.
Each row filters by its branch, path, and — when it has a PR/MR — the PR/MR's number, title, and author, the same fields whether the PR is checked out (a worktree row) or listed via --prs. Plain digits go to the filter, so a number can be typed directly and the preview tabs move to Alt.
Typing a gutter sigil filters by row kind: + narrows to linked worktrees and @ to the current worktree. The other sigils don't filter cleanly — ^ and | are skim's prefix-anchor and OR query operators (so ^ matches every row and | none), and / matches most rows because every worktree path contains it.
Preview tabs:
- HEAD± — Diff of uncommitted changes
- log — Recent commits; commits already on the default branch have dimmed hashes
- main…± — Diff of changes since the merge-base with the default branch
- remote⇅ — Ahead/behind diff vs upstream tracking branch
- summary — LLM-generated branch summary; requires
[list] summary = trueandcommit.generation - pr — The selected row's PR/MR, for any row whose branch has one
- comments — The PR/MR's comment thread, fetched from the forge for any row whose branch has one
On narrow previews the tab bar compacts to digits — only the active tab keeps its label — so every Alt-N accelerator stays visible.
Pager configuration: The preview panel pipes diff output through git's pager. Override in user config:
[switch.picker]
pager = "delta --paging=never --width=$COLUMNS"
Pull requests and merge requests
The pr:<number> / mr:<number> shortcut and the PR/MR's web URL both resolve to its branch. For same-repo PRs/MRs, worktrunk switches to the branch directly. For fork PRs/MRs, it fetches the ref (refs/pull/N/head or refs/merge-requests/N/head) and configures pushRemote to the fork URL.
{{ terminal(cmd="wt switch pr:101 # GitHub PR #101|||wt switch https://github.com/owner/repo/pull/101 # ...the same PR, by URL|||wt switch mr:101 # GitLab MR !101|||wt switch https://gitlab.com/owner/repo/-/merge_requests/101 # ...the same MR, by URL|||wt switch --prs # Browse open PRs/MRs in the picker") }}
Both work anywhere a branch is accepted, including --base. The --create flag cannot be used with a PR/MR reference since the branch already exists.
If the PR or MR is on a fork, the local branch uses its branch name directly, so git push works normally. A pre-existing local branch with that name tracking something else requires renaming first.
The --prs flag adds the repository's open PRs (GitHub) or MRs (GitLab) to the interactive picker — only the ones not already there: a PR whose branch is already shown (as a worktree, or a local or remote branch) isn't listed twice, so --prs only adds the rest and the two pickers differ solely by those extra rows. Each added row resolves to the same pr:/mr: shortcut, so selecting one fetches the ref and switches to its branch. A --prs row has no local worktree, so its pr and comments preview tabs load the PR/MR's metadata and comments from the forge in the background. The log tab uses a local git log — graph and merge-base dimming included — whenever the head commit is already in the object store (a same-repo PR off a fetched remote), falling back to a flat forge-fetched commit list otherwise.
Requires gh (GitHub), glab (GitLab), or an equivalent CLI installed and authenticated; see forge platform for Gitea, Azure DevOps, and other supported platforms.
When wt switch fails
- Branch doesn't exist — Use
--create, or checkwt list --branches - Path occupied — Another worktree is at the target path; switch to it or remove it
- Stale directory — Use
--clobberto remove a non-worktree directory at the target path
To change which branch a worktree is on, use git switch inside that worktree.
See also
wt list— View all worktreeswt remove— Delete worktrees when donewt merge— Integrate changes back to the default branch
Command reference
{% terminal() %} wt switch - Switch to a worktree; create if needed
Usage: wt switch [OPTIONS] [BRANCH] [-- <EXECUTE_ARGS>...]
Arguments: [BRANCH] Branch, worktree path, shortcut, or PR/MR URL
Opens interactive picker if omitted. Shortcuts: <b>^</b> (default branch), <b>-</b> (previous), <b>@</b>
(current), <b>pr:{N}</b> (GitHub PR), <b>mr:{N}</b> (GitLab MR)
[EXECUTE_ARGS]... Additional arguments for --execute command (after --)
Arguments after <b>--</b> are appended to the execute command. Each argument is expanded for
templates, then POSIX shell-escaped.
Options: -c, --create Create a new branch
-b, --base <BASE> Base branch
Defaults to default branch. Supports the same shortcuts as the branch argument: <b>^</b>, <b>@</b>, <b>-</b>,
pr:{N}, mr:{N}.
-x, --execute <EXECUTE> Command to run after switch
Replaces the wt process with the command after switching, giving it full terminal control.
Useful for launching editors, AI agents, or other interactive tools.
Without a branch argument, the interactive picker opens and the command runs against the
selected worktree — so <b>wt switch -x claude</b> picks a worktree, then launches Claude Code
there.
Supports <u>hook template variables</u> (<b>{{ branch }}</b>, <b>{{ worktree_path }}</b>, etc.) and filters. <b>{{</b>
base }} and {{ base_worktree_path }} describe the source: the selected base with --create, or the invoking worktree when switching to an existing worktree.
Especially useful with shell aliases:
<b><b>alias wsc='wt switch --create -x claude'</b></b>
<b>wsc feature-branch -- 'Fix GH #322'</b>
Then <b>wsc feature-branch</b> creates the worktree and launches Claude Code. Arguments after <b>--</b>
are passed to the command, so <b>wsc feature -- 'Fix GH #322'</b> runs <b>claude 'Fix GH #322'</b>,
starting Claude with a prompt.
Template example: <b>-x code -- '{{ worktree_path }}'</b> opens VS Code at the worktree, <b>-x tmux</b>
-- new -s '{{ branch | sanitize }}' starts a tmux session named after the branch.
<b><span class=c>--clobber</span></b>
Remove stale paths at target
<b><span class=c>--no-cd</span></b>
Skip directory change after switching
Hooks still run normally. Useful when hooks handle navigation (e.g., tmux workflows) or
for CI/automation. Use --cd to override.
-h, --help Print help (see a summary with '-h')
Picker Options: --branches Include branches without worktrees
<b><span class=c>--remotes</span></b>
Include remote branches
<b><span class=c>--prs</span></b>
Include open PRs/MRs
Automation: --no-hooks Skip hooks
<b><span class=c>--format</span></b><span class=c> <FORMAT></span>
Output format
JSON prints structured result to stdout. Designed for tool integration (e.g., Claude Code
WorktreeCreate hooks).
[default: text]
[possible values: text, json]
Global Options: -C <path> Working directory for this command
<b><span class=c>--config</span></b><span class=c> <path></span>
User config file path
<b><span class=c>--config-set</span></b><span class=c> <toml></span>
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 {% end %}