Files
max-sixty__worktrunk/docs/content/switch.md
Maximilian Roos 6d09125b7b test: converge suite on semantic boundaries (#3663)
This follows the first test-simplification tranche by converging the
remaining suite around distinct semantic and pragmatic contracts rather
than raw case count. The branch removes false-confidence tests, invalid
setup variants, repetitive snapshots, and expensive PTY overlap while
strengthening the retained route, precondition, and interaction proofs.

## What changed

- Replace obsolete CI-status integration mocks and blank snapshots with
direct provider semantics, mixed-priority cases, and strict
GitHub/GitLab route assertions.
- Remove free-riding merge, push, remove, list, security, config, and
switch cases whose setup never reached the named behavior; consolidate
repetitive direct cases into labeled tables.
- Reduce the switch picker from 42 PTYs to 19 distinct terminal
contracts, using causal release gates for asynchronous loading and
repaint behavior.
- Add a cached main-only picker fixture, eliminating 138 unnecessary Git
subprocesses across the retained PTYs, and integrate it with main's
generated hermetic standard fixture.
- Tighten test guidance around proving setup preconditions and mock
invocation routes, and correct the comments-tab help text and generated
mirrors.

## Reviewer map

- `tests/integration_tests/ci_status.rs` and
`src/commands/list/ci_status/`: provider semantics and route coverage.
- `tests/integration_tests/switch_picker.rs`, `src/commands/picker/`,
and `src/testing/`: retained PTY contracts, causal mocks, and fixture
design.
- `tests/integration_tests/config_show.rs`, `src/config/deprecation.rs`,
`src/config/expansion.rs`, and worktree type/resolve tests:
direct-boundary consolidation.
- `tests/CLAUDE.md`: the testing rules extracted from the
false-confidence cases found during the survey.

The measured loop removed 98 tests, 65 snapshots, and 23 picker PTYs.
Controlled warm Nextest execution improved from a 79.593-second mean to
72.574 seconds (8.8%), while comparable production-line coverage moved
from 97.32% to 97.23%. The tracked PR diff is a net deletion of more
than 5,700 lines.

## Validation

- `cargo run -- hook pre-merge --yes` after syncing current `main`:
4,468 passed, one configured skip; docs, doctests, clippy, formatting,
policy checks, and snapshots green.
- `task coverage` on the completed change before the base sync: 4,465
passed, one configured skip; 97.23% comparable production-line coverage.
- Three independent final audits found no remaining lost beliefs,
fixture hazards, or safe PTY consolidations.

> _This was written by Claude Code on behalf of max_.
2026-07-30 00:01:32 -07:00

14 KiB
Raw Permalink Blame History

+++ 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.

wt switch demo

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:

  1. Runs pre-switch hooks, blocking until complete
  2. Creates worktree at configured path
  3. Switches to new directory
  4. Runs pre-start hooks, blocking until complete
  5. 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.

wt switch picker demo

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
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:

  1. HEAD± — Diff of uncommitted changes
  2. log — Recent commits; commits already on the default branch have dimmed hashes
  3. main…± — Diff of changes since the merge-base with the default branch
  4. remote⇅ — Ahead/behind diff vs upstream tracking branch
  5. summary — LLM-generated branch summary; requires [list] summary = true and commit.generation
  6. pr — The selected row's PR/MR, for any row whose branch has one
  7. 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 check wt list --branches
  • Path occupied — Another worktree is at the target path; switch to it or remove it
  • Stale directory — Use --clobber to 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 worktrees
  • wt remove — Delete worktrees when done
  • wt 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=&#39;wt switch --create -x claude&#39;</b></b>
        <b>wsc feature-branch -- &#39;Fix GH #322&#39;</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 -- &#39;Fix GH #322&#39;</b> runs <b>claude &#39;Fix GH #322&#39;</b>,
      starting Claude with a prompt.

      Template example: <b>-x code -- &#39;{{ worktree_path }}&#39;</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> &lt;FORMAT&gt;</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> &lt;path&gt;</span>
      User config file path

  <b><span class=c>--config-set</span></b><span class=c> &lt;toml&gt;</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 %}