---
title: Subcommand reference
description: Every `gwm` subcommand with synopsis, flags, exit codes, and examples.
---
# Subcommand reference
`gwm <subcommand>` is the scriptable face of gwm. Every command exits with a meaningful code (`0` ok, `1` warning, `2` failure) so you can wire `gwm doctor` into CI without parsing stdout.
Bare `gwm` (no subcommand) opens the [TUI](/tui) on the current repo.
## `gwm init [--preset <name>] [--list-presets] [--show]`
Write a `.gwm.toml` to the current repo — the generic documented template by default, or an opinionated stack preset.
```bash
gwm init
# → wrote /path/to/repo/.gwm.toml
gwm init --preset rust # seed a Rust-flavoured .gwm.toml
gwm init --list-presets # enumerate the built-ins, write nothing
gwm init --preset node --show # print the resolved TOML, write nothing
```
Refuses to overwrite an existing `.gwm.toml`. Tweak the generated file and re-run `gwm doctor` to validate.
| Flag | Action |
|:------------------|:------------------------------------------------------------------------------------------------|
| `--preset <NAME>` | Seed an opinionated `.gwm.toml` for a known stack instead of the generic template (issue #37) |
| `--list-presets` | List the built-in presets with one-line descriptions and exit (writes nothing, needs no git repo) |
| `--show` | Print the resolved preset (or template) to stdout instead of writing — handy for diffing |
### Stack presets (issue #37)
`--preset <name>` seeds an opinionated `.gwm.toml` for a known stack instead of the generic template — env copies, no-symlink invariants, and the right install command pre-wired. `gwm init --list-presets` enumerates them:
```text
generic The fully-documented default template (same as `gwm init`).
go Go module: bin/ no-symlink + `go mod download`.
laravel Laravel: env copies + AWS-RDS guard + vendor/ no-symlink + composer install.
node Node / Nuxt: node_modules/ no-symlink + bun-or-npm install. (alias: nuxt)
python-uv Python (uv): .venv/ no-symlink + `uv sync`.
rust Rust crate: target/ no-symlink + `cargo fetch`.
```
`nuxt` is an alias for `node` (same body), and `generic` is the documented default — `gwm init` with no flag writes it byte-for-byte. Preset bodies are embedded in the binary and kept in sync with [`examples/presets/<name>.toml`](https://github.com/kbrdn1/gwm-cli/tree/main/examples/presets). `--show` prints the resolved TOML to stdout without touching disk, so you can diff a preset against an existing config:
```bash
gwm init --preset laravel --show | diff - .gwm.toml
```
## `gwm config`
Read, edit, and validate `.gwm.toml` without opening the file by hand.
```bash
gwm config get tui.confirm_countdown_secs
gwm config set tui.confirm_countdown_secs 5
gwm config set 'labels[+].name=bug'
gwm config unset review.tool
gwm config list --prefix worktree
gwm config validate
vi "$(gwm config path)"
gwm config edit
```
Keys use dot-path notation. Array tables use indexes (`labels[0].name`), and `set` also accepts `[+]` to append the next table entry. Writes use `toml_edit`, so existing comments and formatting are preserved; after every write, gwm reloads the same runtime schema used by the rest of the CLI.
## `gwm types [--gitmoji]`
List the supported branch types (the `<type>` slot of `gwm create`).
```bash
gwm types
# → feat
# fix
# hotfix
# docs
# …
gwm types --gitmoji
# → feat ✨ :sparkles:
# fix 🐛 :bug:
# …
```
Override per repo via `[worktree].branch_types` in `.gwm.toml`.
| Flag | Action |
|:-------------|:--------------------------------------------------------------------------------------------------|
| `--gitmoji` | Extend the list with two columns — the unicode emoji and its `:shortcode:` form (issue #85) |
`--gitmoji` resolves each type's emoji from the built-in defaults plus any per-repo `[gitmoji]` overrides in `.gwm.toml`. Custom branch types are supported — `migration = ":truck:"` round-trips through `gwm types --gitmoji`. See [`gwm commit-prefix`](#gwm-commit-prefix---branch-name---unicode) for the matching commit-prefix surface.
## `gwm commit-prefix [--branch <name>] [--unicode]`
Print the canonical Gitmoji + Conventional Commits prefix for the current (or named) branch — handy for shell prompts, AI assistants, and scripted commit composition.
```bash
gwm commit-prefix
# → :sparkles: feat(#41):
gwm commit-prefix --unicode
# → ✨ feat(#41):
gwm commit-prefix --branch feat/#41-tui-search
# → :sparkles: feat(#41):
```
Without `--branch`, reads the current branch from `HEAD` via libgit2 (requires the CWD to be inside a git repo). `--branch` resolves the prefix for an explicit branch name; it still reads `.gwm.toml` for the `[gitmoji]` overrides.
| Flag | Action |
|:-----------------|:---------------------------------------------------------------------------------------------------------|
| `--branch <name>`| Resolve the prefix for a named branch (e.g. `feat/#41-tui-search`) instead of `HEAD` |
| `--unicode` | Emit the real emoji character (`✨`) instead of the `:shortcode:` form (`:sparkles:`) |
Under `--unicode`, known `:shortcode:` overrides are normalised to their emoji (e.g. a `[gitmoji]` `feat = ":rocket:"` prints `🚀 feat(#1):`); unknown shortcodes fall through verbatim — no panic, no substitution. The emoji mapping is the built-in default plus any `[gitmoji]` overrides in `.gwm.toml`.
## `gwm hooks install commit-msg [--force]`
Install an opt-in `commit-msg` git hook that auto-prepends the resolved commit prefix when a message doesn't already start with one.
```bash
gwm hooks install commit-msg # install into .git/hooks/commit-msg
gwm hooks install commit-msg --force # overwrite an existing commit-msg hook
```
The hook is **never installed implicitly** — you opt in once per repo. It is non-destructive by default: it refuses to overwrite a pre-existing `commit-msg` hook (husky / commitlint / pre-commit) and exits non-zero with the conflicting path unless `--force` is passed. The hook honours `core.hooksPath`, resolves a linked-worktree `.git` file via `Repository::discover`, and degrades gracefully if `gwm` is not on `$PATH` at commit time (the message is left untouched). `commit-msg` is the only hook kind today; clap rejects any other value at parse time.
| Flag | Action |
|:------------|:-----------------------------------------------------------------------------|
| `--force` | Replace an existing hook of the same name (otherwise the install refuses) |
The prefix it prepends is exactly what [`gwm commit-prefix`](#gwm-commit-prefix---branch-name---unicode) prints for the worktree's branch.
## `gwm create <type> <issue> <desc>`
Create a worktree and matching branch.
```bash
gwm create feat 123 user-authentication
# → branch feat/#123-user-authentication
# → worktree ~/cc-worktree/<repo>/feat-123-user-authentication
gwm create feat 123 foo --no-bootstrap # skip the bootstrap pipeline
```
| Flag | Action |
|:------------------------|:--------------------------------------------------------------------------------------------|
| `--no-bootstrap` | Skip the `.gwm.toml` bootstrap stages (copies / guards / commands / hooks) |
| `--reuse-branch` | Attach to an already-existing local branch of the same name instead of refusing (issue #99) |
| `--skip-hooks <PHASES>` | Skip the comma-separated lifecycle hook phases (e.g. `pre_create,post_create`) |
| `--name <NAME>` | Name the worktree freely instead of the `<type> <issue> <desc>` triple (issue #416). Exclusive with the positionals |
| `--repo <NAME>` | In [workspace mode](#workspace-mode-global---workspace-issue-36), which child repo gets the worktree — required there to disambiguate; ignored in single-repo mode (issue #36) |
By default `gwm create` refuses to silently reuse a stale local branch — it ends with an error naming the stale tip so you can audit it; `--reuse-branch` is the opt-in escape hatch.
### free-form naming (`--name`)
Not every worktree is an issue. `--name` skips the convention entirely:
```bash
gwm create --name spike-redis
# → branch spike-redis
# → worktree ~/cc-worktree/<repo>/spike-redis
```
The name becomes the branch **verbatim** — it is validated exactly as typed, so `--name " spike"` is refused rather than trimmed into a different branch than the one you asked for. `branch_pattern` and `path_pattern` do not apply — they are written in terms of `{type}` / `{issue}` / `{desc}`, and a free-form name has none of them. `[worktree].base` still applies, so free-form worktrees land beside the structured ones, for the placeholders it documents (`{home}`, `{repo}`, `{repo_path}`, `{repo_parent}`); a base written with `{type}` / `{issue}` / `{desc}` is refused instead, since there is nothing to resolve it against and it would otherwise end up literal in the path. A `/` is legal in the branch and flattens to `-` in the directory name, the same relationship the default pattern pair already has.
**What you give up.** Everything that reads the branch name back goes quiet — that is the deal, not a bug. This table describes a name that does **not** match the branch convention; nothing records *how* a worktree was named, only what its branch is, so a free-form name that happens to look structured (`--name 'feat/#42-x'`) is read back as structured and keeps every row below:
| Feature | On a free-form worktree |
|:--------|:------------------------|
| issue auto-linking | inactive — use `gwm link --issue <N>` to attach one by hand |
| PR/MR detection | **still works** — it queries the forge with the whole branch name |
| gitmoji / `gwm commit-prefix` | errors: a prefix is derived from the branch *type*, and there is none |
| `doctor` orphan check | treats it as a user-managed branch and never flags it |
| hook placeholders (create / remove / bootstrap) | `{type}` / `{issue}` / `{desc}` resolve empty |
| TUI edit form | not applicable — that form rebuilds the triple; rename with git |
**Accepted names.** A free-form name has to be three things at once, and the rules follow from that rather than from a hand-written list of bad examples.
It is **a git branch** — checked against libgit2's own branch-level rule set, which is stricter than the reference-level one (`refs/heads/HEAD` is a valid *reference* name, `HEAD` is not a usable *branch* name). It is **a single filesystem path component**, the worktree directory, which a branch name is not: no `.` or `..` component (a directory named `..` would escape the base), and at most 255 bytes — `a×130/b×130` is a legal ref and an illegal directory name, and without the cap the branch gets created before the directory fails, leaving it orphaned. And it is **a literal value during hook expansion**, so no `{` or `}`: placeholders are substituted in sequence, and a branch called `spike-{issue}` would have its own name rewritten inside the `{branch}` value a hook receives.
One more rule belongs to none of those: no leading `-`. Git accepts it; `gwm remove` and `git branch -d` would read it as a flag.
`Spike_Redis`, `2026.07.27` and `réécriture` are all fine.
The directory has to be hostable on **Windows** too, so three more rules apply on every platform, not just there ([#475](https://github.com/kbrdn1/gwm-cli/issues/475)). No `<`, `>`, `"` or `|`, which Win32 forbids in a path component and git accepts. No path component that is a reserved device name (`CON`, `PRN`, `AUX`, `NUL`, `COM1`–`COM9`, `LPT1`–`LPT9`), case-insensitively and with or without an extension, since Win32 reads `NUL.tar.gz` as `NUL`. And no path component ending in `.`, which Windows refuses as a directory name.
The last two are checked per `/`-separated segment, because a loose ref is a file at `.git/refs/heads/<name>`, which makes every segment a path component there: `spike/CON` flattens to the perfectly legal directory `spike-CON` and is still an unwritable ref on Windows. The trailing period is the case worth knowing about, because git *almost* covers it: its own rule applies to the whole branch name, so `spike.` is refused by git while `foo./bar` is not. `v1.2/spike` stays legal, a period inside a segment not being a trailing one.
The rules are not gated to Windows because a branch travels to a teammate's machine through the forge. A name no Windows checkout can host is a hazard for the whole team, not a local one. The residual set is exactly what libgit2 does not already reject: git itself refuses `:`, `\`, `?`, `*` and a space in every position, so those need no rule of their own. `COM0` and `LPT0` stay legal, being absent from the Win32 reserved list.
In the TUI, `Ctrl-T` toggles the create form — and only that form — between the structured triple and a single free-form `Name` field.
End-to-end walkthrough lives in [Getting Started → First worktree](/getting-started/first-worktree).
## `gwm new <type> <desc>`
Create a GitHub issue from the repo's configured issue form, then create the matching worktree from the returned issue number.
```bash
gwm new feat add-config-types
# → created issue #142 [Feature]: add-config-types
# → branch feat/#142-add-config-types
```
`gwm new` reads `[issue_template]` from `.gwm.toml`, renders the selected `.github/ISSUE_TEMPLATE/*.yml` file to markdown, calls `gh issue create --body-file`, then hands off to the same worktree creation path as `gwm create`.
| Flag | Action |
|:-----------------|:--------------------------------------------------------------------|
| `--no-bootstrap` | Create the worktree without running bootstrap |
| `--reuse-branch` | Attach to an existing local branch after the issue is created |
| `--skip-hooks` | Skip comma-separated lifecycle hook phases |
## `gwm review <PR#> [--name <branch>] [--bootstrap] [--skip-hooks <phases>]` (issue #308)
Materialise an existing GitHub PR into an isolated worktree — fetch the PR head, attach a worktree, link the PR, and you're reviewing the contributor's code in seconds.
```bash
gwm review 310 # fetch PR #310 into review/pr-310-<author>-<slug>
gwm review 310 --name pr-310 # override the local review branch name
gwm review 310 --bootstrap # ...and run bootstrap + lifecycle hooks (opt-in)
```
`gwm review` resolves the PR head via `gh` and fetches origin's universal `refs/pull/<N>/head` ref, so it is **cross-fork aware** and valid for PRs in any state (open / draft / closed / merged). It creates a local `review/pr-<N>-<author>-<slug>` branch, attaches a worktree (the directory is derived from the branch name, slashes become dashes), and links the PR so the [TUI sidebar / CI indicator](/tui) light up immediately. Tear down like any worktree: `gwm remove <dir> --delete-branch`.
| Flag | Action |
|:------------------------|:---------------------------------------------------------------------------------------------|
| `--name <BRANCH>` | Override the local review branch name (default `review/pr-<N>-<author>-<slug>`); the worktree dir is derived from it |
| `--bootstrap` | Run bootstrap + lifecycle hooks against the PR's code after creation (off by default) |
| `--skip-hooks <PHASES>` | Skip the comma-separated lifecycle hook phases (e.g. `pre_create,post_create`) |
**Safe-by-default:** bootstrap and lifecycle hooks are **not** run. A review worktree holds a contributor's (possibly fork) code, and those steps execute commands against it (`npm install`, `composer install`, `direnv allow`, `post_create` hooks …) — i.e. arbitrary code. Pass `--bootstrap` to opt in once you trust the PR enough to set it up. This is distinct from the `[review]` config block (which drives the TUI `R` review-launcher key); `gwm review` is the worktree-materialisation subcommand.
## `gwm pr [--draft] [--base <ref>] [--render]`
Render the PR body from `[pr_template]` and shell out to `gh pr create` against the resolved trunk.
```bash
gwm pr # creates the PR
gwm pr --draft # creates a draft PR
gwm pr --base develop # diff against develop instead of [doctor].trunks
gwm pr --render # prints the rendered body to stdout (no PR created)
gwm pr --render | gh pr create --body-file -
```
`gwm pr` reads the current branch (parsing `<type>/#<N>-<desc>` for the `{type}` / `{issue}` / `{desc}` placeholders), picks the first existing trunk from `[doctor].trunks` (or falls back to `main`), then renders the per-branch-type template under `[pr_template.by_type.<type>]` (inline `body` wins over `path`), with `[pr_template].default` as the fallback.
| Flag | Action |
|:--------------------|:--------------------------------------------------------------------|
| `--render` | Print the rendered Markdown to stdout; never shell out to `gh` |
| `--draft` | Forward `--draft` to `gh pr create` so the PR opens as a draft |
| `--base <REF>` | Override the comparison base instead of the first matching trunk |
On success the new PR number is recorded under `branch.<head>.gwm-pr` (same key `gwm link pr` writes), so `gwm status` and `gwm open pr` resolve the link without a separate `gwm link` call.
Body resolution and placeholder semantics are documented under [Configuration → `[pr_template]`](/configuration/gwm-toml#pr_template-issue-84).
## `gwm list [--format=table|names|json] [--detect-pr] [--workspace <dir>]`
List the worktrees in the current repo.
```bash
gwm list # human-readable table
gwm list --format=names # one worktree name per line (for shell completion)
gwm list --format=json # machine-readable JSON array (issue #38)
gwm list --detect-pr # add a PR column, auto-detecting each branch's PR via gh
gwm list --workspace ~/Projects # merged table across every child repo, leading REPO column
```
The `names` format excludes the main workdir — `gwm path / remove / bootstrap` never accept it as a target, so emitting it as a completion candidate would be misleading.
`--format=json` (issue #38) emits a stable JSON array — schema documented at [`docs/schema/worktree-list.schema.json`](https://github.com/kbrdn1/gwm-cli/blob/main/docs/schema/worktree-list.schema.json). Unlike `names`, it **includes** the main worktree: a JSON consumer (an editor, a statusbar) wants the full set and resolves the active worktree from it. Each entry carries `name`, `id`, `path`, `branch`, `head`, `is_main` / `is_locked` / `is_prunable`, a `status` object (`is_dirty`, `has_upstream`, `ahead`, `behind`, `unknown`), `age_seconds`, and linked `issue` / `pr` numbers. Pipe into `jq`:
```bash
gwm list --format=json | jq '.[] | select(.status.is_dirty) | .name'
```
`--detect-pr` adds a `PR` column populated by [PR auto-detection](/integrations/github-linking#auto-detection) (`gh pr list --head <branch>` per worktree). It is **off by default** so the plain listing stays network-free — one `gh` call per worktree is only paid when the flag is set. Ignored with `--format=names`.
`--workspace <dir>` (issue #36) prints the merged worktree table across every git repo one level below `<dir>`, with a leading **REPO** column naming each row's repo. See [Workspace mode](#workspace-mode-global---workspace-issue-36) for the full behaviour.
## `gwm agents [attach|detach] [--format=table|json]` (issue #408)
List the AI-agent sessions detection matched to each worktree, or pin one
manually. Detection reads each agent's on-disk session artefacts (Claude
Code, Codex, opencode, Mistral Vibe) — no process enumeration, same code
path on Linux, macOS and Windows. One refinement on Unix: when Claude
Code's live registry records a session's PID and that process is gone,
the session drops to idle immediately instead of riding out the activity
window; elsewhere (and for the other agents) classification stays
artefact-only. The same data feeds the TUI's AGENT
column and `a` overlay, `gwm list` (table column + JSON `agents` field), the
daemon and `gwm statusline` (fed by the daemon transport on every
platform — unix socket, or a named pipe on Windows, #439).
```bash
gwm agents # sessions per worktree: agent, freshness, last activity, id, name
# + an `unmatched` section for sessions no worktree matched
gwm agents --format=json # the same worktree rows as `gwm list --format=json`
gwm agents attach . 019f6b95-… # pin session <id> to the enclosing worktree
gwm agents attach feat-42 <id> # …or to the worktree matching a name substring
gwm agents detach feat-42 <id> # remove that one pin
gwm agents detach feat-42 # remove every pin — back to pure auto-detection
```
**Auto-detection is the default; a pin is an override.** A pin covers the
cases the recorded working directory cannot: an agent launched from a
subdirectory, a moved worktree, a session you want on a specific worktree
regardless of where it was started. Pins **accumulate** — several agents
can work one worktree, so attach adds a pin and `detach <wt> <id>` removes
just that one (bare `detach <wt>` clears them all). Stored in git branch
config (multi-valued `gwm-agent-pin`), never committed. The sidebar's
Agents pane shows **only pinned** sessions — pinning is how a session earns
its place there.
Attaching a session id that detection cannot resolve exits with status 1; a pin whose
artefacts later disappear degrades silently back to detection. Detached-HEAD
worktrees cannot hold a pin. Sessions matched to **no** worktree — launched
in another repo, a subdirectory, an old path — are listed under an
`unmatched` section: precisely the ids `attach` takes.
`GWM_AGENTS_HOME` overrides the home directory the artefact scans read —
mainly a deterministic seam for tests and CI.
## `gwm path <pattern> [--format=text|json]` (alias: `gwm cd <pattern>`)
Print the on-disk path of a worktree matching `<pattern>` (fuzzy). Use with `$(...)` to `cd`:
```bash
cd "$(gwm path auth)"
cd "$(gwm cd auth)" # same — framing for the cd flow
gwm path auth --format=json # { "name": ..., "path": ..., "branch": ... }
```
The default `text` form prints the bare path for `$(...)` consumption. `--format=json` (issue #38) emits the `{ name, path, branch }` triple — schema at [`docs/schema/path.schema.json`](https://github.com/kbrdn1/gwm-cli/blob/main/docs/schema/path.schema.json).
Both forms share semantics: fuzzy resolve, exit `0` on a unique hit, `1` on miss / ambiguous / not in a repo. Pair with `gwm shell-init` for the `gcd` one-liner — see [Getting Started → Shell init](/getting-started/shell-init).
## `gwm switch` (alias: `gwm s`)
Open the TUI in **picker mode** — same table as bare `gwm`, fuzzy filter bar pre-open, create / delete / bootstrap disabled. Press `Enter` to print the highlighted worktree's path on stdout, `Esc` / `q` / `Ctrl-C` to cancel with exit code `1`.
```bash
cd "$(gwm switch)" # open picker, type to narrow, Enter to commit
gcd # same, via the `gwm shell-init` wrapper
```
## `gwm bootstrap [<pattern>]`
Re-run the `.gwm.toml` bootstrap pipeline on a worktree without recreating it.
```bash
gwm bootstrap # on the CWD worktree
gwm bootstrap auth # on a fuzzy-matched name
```
Useful after editing `.gwm.toml` or adding new `[[bootstrap.copy]]` rules. Same `✓ / ! / ✗` report as `gwm create`.
| Flag | Action |
|:------------------------|:--------------------------------------------------------------------------------|
| `--skip-hooks <PHASES>` | Skip the comma-separated lifecycle hook phases (e.g. `pre_bootstrap,post_bootstrap`) |
## `gwm sync [<pattern>] [--merge]`
Fetch a worktree's upstream and bring its branch up to date — rebase by default, or merge with `--merge`.
```bash
gwm sync # the CWD worktree, rebase onto upstream
gwm sync auth # a fuzzy-matched worktree
gwm sync auth --merge # merge the upstream instead of rebasing
```
Resolves the target like `gwm bootstrap` (fuzzy pattern, defaults to the worktree containing the CWD — which may be the main worktree, so you can sync trunk too). It runs `git fetch` for the upstream's remote, recomputes how far behind the branch is, then integrates only when there's something to integrate. Reports a single `✓` line (`already up to date` / `rebased N commit(s)` / `merged N commit(s)`).
Guard rails:
- **Dirty working tree** → refuses before touching the remote (`commit or stash`). A rebase/merge on top of uncommitted work is how changes get lost.
- **No upstream configured** → errors with the `git branch --set-upstream-to=<remote>/<branch>` fix.
- **Conflict** → the rebase/merge is **aborted** so the worktree is left usable, and you're told to reconcile by hand.
The fetch / rebase / merge steps shell out to your `git` (so SSH keys, credential helpers, and `insteadOf` rules all apply); the dirty / upstream / ahead-behind inspection uses libgit2.
## `gwm remove <pattern> [--delete-branch] [--dry-run]`
Remove a worktree by fuzzy match. The branch survives by default.
```bash
gwm remove auth # remove the worktree, keep the branch
gwm remove auth --delete-branch # remove the worktree AND drop the branch
gwm remove auth --dry-run # preview the plan, destroy nothing
gwm remove auth --dry-run --delete-branch # preview, including the branch drop
```
The CLI form has no countdown (the TUI's [`d` confirm-overlay countdown](/tui/confirm-countdown) is TUI-only). `--delete-branch` is destructive — only `git reflog` can resurrect a dropped branch.
| Flag | Action |
|:-------------------|:------------------------------------------------------------------------------------------------------------|
| `--delete-branch` | Also drop the local branch after removing the worktree (destructive) |
| `--dry-run` | Print the would-remove plan (name + path + branch) and exit `0` without touching anything (issue #31) |
| `--force` | Emergency removal mode: skip the `pre_remove` / `post_remove` [lifecycle hooks](/configuration/gwm-toml) |
| `--skip-hooks <PHASES>` | Skip the comma-separated lifecycle hook phases (e.g. `pre_remove,post_remove`) |
`--dry-run` resolves the fuzzy pattern, prints the plan, and exits `0` — no destruction, **no journal write** (see [`gwm undo` / `gwm history`](#gwm-undo---bootstrap)). With `--delete-branch` it tags the branch line `(would be deleted)`; on a detached-HEAD worktree it prints `branch: - (no branch to delete)` instead, mirroring the destructive path's behaviour. An ambiguous pattern fires the same non-zero candidate-list error as the destructive form — `--dry-run` only suppresses destruction, not resolution failures.
## `gwm prune [--dry-run]`
Clear stale entries in `.git/worktrees/` whose working directory was removed manually (e.g. `rm -rf` outside gwm).
```bash
gwm prune # prune every stale entry
gwm prune --dry-run # list the prunable entries, touch nothing
```
`gwm doctor` flags prunable entries as a Warning; `gwm prune` is the documented remediation.
| Flag | Action |
|:-------------|:----------------------------------------------------------------------------------------------------|
| `--dry-run` | List every prunable entry (name + path + reason) and exit `0` without touching the admin files (issue #31) |
`--dry-run` output is sorted by name for deterministic stdout diffing; the empty case prints `0 worktree(s) to prune` so scripted callers get a stable signal. Column widths are computed in Unicode characters so non-ASCII names stay aligned. The preview and the destructive pass share the same scanner, so they can never drift on what "prunable" means.
## `gwm undo [--bootstrap]`
Recover from a misfired `gwm remove` without `git reflog` archaeology. Pops the most recent destructive op recorded for the current repo, recreates `refs/heads/<branch>` at the saved OID, and re-adds the worktree at the saved path (with `reuse_branch` so the resurrected branch attaches cleanly).
```bash
gwm undo # bring back the last removed worktree + branch
gwm undo --bootstrap # ...and re-run the per-worktree bootstrap
```
| Flag | Action |
|:---------------|:--------------------------------------------------------------------------------|
| `--bootstrap` | Re-run the per-worktree bootstrap after the resurrection (off by default) |
The journal entry is consumed **only after** a successful resurrection — a mid-flight failure leaves the recovery anchor intact so you can retry. A detached-HEAD entry (no branch to recreate) is refused with an explicit error rather than silently doing nothing. The journal is shared with [`gwm history`](#gwm-history---limit-n---all); see it for the file location and rotation policy.
## `gwm history [--limit N] [--all]`
List the recent destructive operations recorded by gwm, newest first.
```bash
gwm history # last 20 ops for the current repo
gwm history --limit 50 # last 50
gwm history --all # every op across every repo (forensic / multi-repo)
```
| Flag | Action |
|:---------------|:-----------------------------------------------------------------------------------------|
| `--limit N` | Maximum entries to print, newest first. Default `20` |
| `--all` | List ops across every repo, not just the current one |
By default it filters to the current repo's canonicalised workdir; `--all` surfaces every entry. An empty result prints `no operations recorded` as a stable scripted signal. The journal lives at `$XDG_DATA_HOME/gwm/history.toml` (override with `$GWM_HISTORY_FILE`; macOS falls back under `Application Support`, Windows under `%LOCALAPPDATA%`) and is capped at 100 entries — the oldest is dropped on overflow. Every `gwm remove` (with or without `--delete-branch`) appends an entry; `gwm remove --dry-run` does **not** write the journal, so previewing a destruction can never let you "undo" something that never happened.
## `gwm completions <shell>`
Print a static completion script. Supported shells: `zsh`, `bash`, `fish`, `powershell`, `elvish`. See [Shell completions](/cli/completions) for installation per shell.
## `gwm shell-init <shell>`
Print the `gcd` shell wrapper. Supported shells: `zsh`, `bash`, `fish`, `powershell`. See [Getting Started → Shell init](/getting-started/shell-init).
## `gwm tmux <pattern> [-p|--split]`
Open the matched worktree in a new tmux window of the **current** session. `--split` substitutes `split-window` for `new-window`. Requires `$TMUX` to be set.
```bash
gwm tmux auth # new tmux window inside the matched worktree
gwm tmux auth -p # split the current pane instead
```
Outside a tmux session, exits non-zero with a clear error (does not spawn a stray server).
## `gwm zellij <pattern> [-p|--split]`
Same as `gwm tmux` but for zellij. Uses `zellij action new-tab --cwd <path>` (requires zellij ≥ 0.40 for the `--cwd` flag) or `new-pane --cwd` with `-p`. Requires `$ZELLIJ`.
See [CLI → Multiplexer integration](/cli/multiplexer) for the full surface and edge cases.
## `gwm link {issue|pr} <N> [--worktree <pattern>]`
Link the current (or named) worktree to a GitHub issue or PR.
```bash
gwm link issue 42 # link the current worktree to issue #42
gwm link pr 61 # link a PR
gwm link issue 42 --worktree feat-auth # ...or to a fuzzy-matched worktree
```
The link is stored in `git config branch.<name>.gwm-issue` / `gwm-pr` — local, per-branch, no extra file. Issue numbers are **auto-detected** from `<type>/#<N>-<slug>` branches, so `gwm link issue <N>` is only needed for explicit overrides. PR numbers are not auto-detected.
## `gwm unlink {issue|pr} [--worktree <pattern>]`
Remove the explicit link override on the current (or named) worktree.
```bash
gwm unlink issue # remove the issue link (auto-detect resurfaces)
gwm unlink pr # remove the PR link
```
Idempotent — safe to run when nothing is linked.
## `gwm open {issue|pr} [--worktree <pattern>] [--print-url]`
Open the linked issue / PR in the browser via the OS opener.
```bash
gwm open issue # spawn the OS opener on the linked issue URL
gwm open pr --print-url # print the URL on stdout, no spawn
```
Useful in headless shells and tests with `--print-url`.
## `gwm status [--worktree <pattern>] [--json]`
Show the link plus (when `gh` is available) live GitHub state.
```bash
gwm status
# → Issue #42 [open] TUI: fuzzy search
# → PR #61 [draft] · checks 2/3
gwm status --json # stable schema for scripts
```
Degrades gracefully to local-link-only output when `gh` is missing or the repo has no GitHub remote.
## `gwm labels {list|push}`
Manage the declarative GitHub label set from `.gwm.toml`. Declare the labels you want once in `[[labels]]`, push them to the upstream `origin` remote as needed — no more drift across repos. Without a `[[labels]]` block in `.gwm.toml`, both subcommands are no-ops (`0 labels declared, nothing to push`) and never shell out to `gh`.
```bash
gwm labels list # show the diff against the remote
gwm labels push # apply create + update
gwm labels push --dry-run # plan only, no remote mutations
gwm labels push --prune # also delete labels not in config
gwm labels push --random-colors # random pastel for entries with no `color`
```
| Flag | Action |
|:-----------------|:------------------------------------------------------------------------------------------------------------------------------------------------|
| `--dry-run` | Print the plan without mutating the remote. Still reads remote labels via `gh label list` to compute the diff; only create / update / delete calls are skipped. |
| `--prune` | Delete labels on the remote that aren't declared in `.gwm.toml` (destructive — opt-in) |
| `--random-colors`| Use a random pastel for entries with no `color` field (overrides the deterministic hash) |
`list` output sigils mirror the diff buckets:
```
+ bug (will create, color #d73a4a)
~ good first issue (color #008672 → #7057ff)
= documentation (match)
- wontfix (on remote, not in config)
```
Requires `gh` on `$PATH` (the same soft dependency as `gwm status`). Schema reference and authoring tips: [Configuration → `.gwm.toml`](/configuration/gwm-toml#labels-issue-81).
## `gwm milestones {list|push}`
Manage the declarative GitHub milestone set from `.gwm.toml`. Same shape as `gwm labels`; the REST endpoint is used because `gh` has no native `gh milestone` subcommand. Without a `[[milestones]]` block in `.gwm.toml`, both subcommands are no-ops (`0 milestones declared, nothing to push`) and never shell out to `gh`.
```bash
gwm milestones list # show the diff against the remote
gwm milestones push # apply create + update
gwm milestones push --dry-run # plan only, no remote mutations
gwm milestones push --prune # also delete milestones not in config
```
| Flag | Action |
|:------------|:----------------------------------------------------------------------------------------------------------------------------------------------------|
| `--dry-run` | Print the plan without mutating the remote. Still reads remote milestones via `gh api …/milestones` to compute the diff; only create / update / delete calls are skipped. |
| `--prune` | Delete milestones on the remote that aren't declared in `.gwm.toml` (destructive — opt-in) |
`list` output sigils mirror the diff buckets:
```
+ v0.7.0 (will create, state open, due 2026-07-15T23:59:59Z)
~ v0.6.0 (due 2026-07-01T23:59:59Z → 2026-07-15T23:59:59Z)
= v0.5.0 (match)
- old-sprint (#3 on remote, not in config)
```
Requires `gh` on `$PATH`. Schema reference and authoring tips: [Configuration → `.gwm.toml`](/configuration/gwm-toml#milestones-issue-82).
## `gwm doctor [--format=text|json]`
Run 8 health checks; report each with `✓ / ! / ✗`; exit `0 / 1 / 2`. Designed for CI and pre-commit hooks. See [Integrations → `gwm doctor`](/integrations/doctor) for the per-check breakdown.
`--format=json` (issue #38) emits the checks array plus aggregate `severity` and `exit_code` — schema at [`docs/schema/doctor.schema.json`](https://github.com/kbrdn1/gwm-cli/blob/main/docs/schema/doctor.schema.json). The **process exit code is identical** to the text form (the JSON also carries it as a field), so `gwm doctor --format=json` still works in an `if`-guard:
```bash
gwm doctor --format=json | jq '.checks[] | select(.status == "failed")'
```
## `gwm daemon [--socket <path>] [--poll-ms <ms>]` (issue #38)
Run gwm as a long-running **JSON-RPC 2.0 daemon** over a unix domain socket (a named pipe on Windows, #439), so editors / statusbars / tooling connect once instead of spawning `gwm` per query.
```bash
gwm daemon # bind $XDG_RUNTIME_DIR/gwm.sock (→ $TMPDIR → /tmp)
gwm daemon --socket /tmp/gwm.sock # explicit socket path
gwm daemon --poll-ms 500 # faster subscribe push, more git scans
```
**Wire format:** newline-delimited JSON (NDJSON) — one request object per line, one response per line.
| Method | Params | Result |
|--------|--------|--------|
| `list` | — | array of worktrees ([schema](https://github.com/kbrdn1/gwm-cli/blob/main/docs/schema/worktree-list.schema.json)) |
| `doctor` | — | doctor report ([schema](https://github.com/kbrdn1/gwm-cli/blob/main/docs/schema/doctor.schema.json)) |
| `path` | `{ "pattern": "<str>" }` | `{ name, path, branch }` ([schema](https://github.com/kbrdn1/gwm-cli/blob/main/docs/schema/path.schema.json)) |
| `subscribe` | — | stream of `worktrees.changed` notifications (first = current snapshot) |
```bash
# request/response (one line in, one line out)
printf '{"jsonrpc":"2.0","method":"list","id":1}\n' | nc -U "${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock"
```
`subscribe` turns the connection into a one-way push stream: the daemon sends a `worktrees.changed` notification with the current snapshot, then one on every change. Change detection is **interval polling** of the worktree set (tuned by `--poll-ms`, default `1000`) — a deliberate MVP choice over a filesystem watch so there's no extra dependency and the behaviour is deterministic; update latency is bounded by the poll interval.
**Platform / build:** behind the default-on `daemon` Cargo feature. Unix binds a unix domain socket (`--socket` is a filesystem path); Windows binds a named pipe (#439, `--socket` is the pipe NAME under `\\.\pipe\`, default `gwm-<user>.sock`, restricted to the owner by its security descriptor). On a `--no-default-features` build the subcommand exits with an explanatory error (it stays listed so `--help` is identical everywhere).
`--poll-ms` must be `≥ 1` (`0` is rejected — it would spin the `subscribe` loop with no wait, re-scanning git as fast as the CPU allows).
## `gwm statusline [--socket <path>] [--watch]` (issue #309)
Print a compact one-line worktree summary for a shell prompt — the first bundled **consumer** of `gwm daemon`. It connects to the daemon socket, asks for the worktree set, and renders the active branch, worktree count, dirty / ahead / behind, and the linked issue / PR.
```bash
gwm statusline # one-shot, prints one line and exits
gwm statusline --watch # subscribe; reprint on every change
gwm statusline --socket /tmp/gwm.sock # explicit daemon socket
```
```text
feat/#309-daemon-consumer · 3 wt · * ↑1 · #309 · PR #310
```
Tokens: branch (or worktree name when detached), `N wt` count, `*` dirty, `↑n`/`↓n` ahead/behind, `#N` issue, `PR #N`. The **active** worktree is the one enclosing the current directory; outside any worktree only the count is shown. A CI rollup is intentionally not included (not part of the daemon's stable schema).
**Graceful degradation:** when no daemon is reachable, `gwm statusline` prints an empty line and exits `0`, so a prompt substitution degrades to nothing instead of erroring. Same `--socket` resolution as `gwm daemon`. See [Integrations → Daemon consumers](/integrations/daemon-consumers) for prompt recipes (zsh / tmux / starship) and an editor recipe (Zed / VS Code).
## Workspace mode (global `--workspace`) (issue #36)
`--workspace <dir>` is a **global** flag (`global = true`, so it is accepted before *or* after the subcommand) that operates across every git repo one level below `<dir>` instead of a single repo. It is an orthogonal dimension on top of single-repo mode.
```bash
gwm --workspace ~/Projects # open the TUI over every direct-child repo
gwm list --workspace ~/Projects # merged worktree table, leading REPO column
gwm --workspace ~/Projects list # same — the global flag may precede the subcommand
gwm --workspace ~/Projects create feat 12 search --repo my-api # disambiguate the target
gwm exec --workspace ~/Projects -- git fetch # fan out across every child repo's worktrees
gwm clean --workspace ~/Projects --yes # reclaim artifacts across every child repo
```
- **TUI / `gwm list`** gain a leading **REPO** column naming each row's repo. In the TUI the active repo follows the selection, so every selection-driven action (lazygit, terminal, sync, delete, link, open, …) operates on the highlighted worktree's own repo.
- **`gwm create`** in workspace mode requires `--repo <name>` to disambiguate which child repo gets the new worktree; an absent or unknown name lists the candidates.
- **`gwm exec` / `gwm clean`** (issue #326) fan out across every child repo. Each repo's command / dir-set is resolved upfront (a missing `--profile`, a malformed `[exec]`/`[clean]`, or an unopenable child errors before anything runs), then repos run **sequentially** (parallelism stays bounded *within* a repo) under a `══ <repo>` header, with a `<repo>/<worktree>`-tagged rollup / report and an aggregated exit code. `--profile` resolves per repo against that repo's own `.gwm.toml`; a slug matching nothing in a repo contributes nothing there. `gwm clean --workspace` aggregates one report and one `--yes` decision across all repos; a delete failure in one worktree is reported but doesn't abort the rest. **`--workspace` is still refused on commands that don't implement it.**
- **Bare `gwm`** in a directory that is *not* itself a git repo but holds child repos prompts `No git repo here. Open <dir> as a workspace? [Y/n]`. The prompt is **declined silently when stdin is not a terminal**, so pipes / CI keep the old single-repo behaviour.
- **`.gwm.toml` stays per-repo** — each row inherits its own repo's config. There is no workspace-level config in this version; the keymap and theme resolve once from the first repo, matching the single-repo "resolved once, relaunch to change" contract.
## `gwm trust {list|revoke|show}` (issue #95)
Manage the TOFU trust ledger that gates `.gwm.toml` bootstrap on `gwm create` / `gwm bootstrap`. Ledger lives at `~/.config/gwm/trust.toml` by default; override with `$GWM_TRUST_LEDGER`.
- `gwm trust list` — print every recorded `(origin, sha-prefix, trusted_at, trusted_by)` tuple. Empty ledger prints `0 entries in trust ledger (<path>)` and exits 0.
- `gwm trust revoke <origin>` — drop every entry matching `<origin>` verbatim (SSH and HTTPS flavours of the same GitHub repo are distinct trust paths). Reports `0 entries matched` when nothing changes.
- `gwm trust show` — print the active ledger path and its raw TOML body (or a "file does not exist yet" notice on fresh installs). Useful for triaging "why is gwm re-prompting?" — eyeball the recorded hash vs. `sha256sum .gwm.toml`.
Two **global** flags interact with the ledger on every subcommand that runs bootstrap (`gwm create`, `gwm bootstrap`):
- `--allow-bootstrap` (also `GWM_ALLOW_BOOTSTRAP=1`) skips the trust prompt without recording. Use in CI runners and other non-interactive contexts.
- `--deny-bootstrap` refuses to run bootstrap even if the ledger says trusted. Forensic mode for first-look inspection of an unfamiliar repo.
Threat model and full rationale: see the module-level comment in [`src/trust.rs`](https://github.com/kbrdn1/gwm-cli/blob/main/src/trust.rs).
## `gwm aliases list` (issue #86)
Print the resolved CLI alias chain — every alias reachable from `gwm <name>`, grouped by source. Read-only; declarative editing happens directly in `.gwm.toml` (repo-level) and `~/.config/gwm/aliases.toml` (user-level).
```bash
gwm aliases list
```
Sample output:
```text
built-in:
cd → path
s → switch
repo (.gwm.toml):
ll → list --format names
wip → create feat 0 wip
user (~/.config/gwm/aliases.toml):
copy → path (shadowed by repo)
```
Resolution order (highest precedence first):
1. **Built-in subcommands** (`gwm list`, `gwm switch`, …) — never shadowable.
2. **Built-in visible aliases** (`s → switch`, `cd → path`) — also never shadowable.
3. **Repo (`.gwm.toml` `[aliases]`)** — follows the repo across machines.
4. **User (`~/.config/gwm/aliases.toml` `[aliases]`)** — survives machine reinstalls; invisible to teammates.
Aliases are **argv substitution only** — `wip = "create feat 0 wip"` makes `gwm wip` behave as `gwm create feat 0 wip`. The expansion happens BEFORE clap parses argv. Shell pipelines (`&&`, `||`, `|`, `;`, backticks) in values are refused at load time — use a shell alias instead.
Names that shadow a built-in subcommand or visible alias are a hard config error surfaced by `Config::load_for_repo` (e.g. you can't define `list = "..."`). Single-pass expansion — chained aliases don't recurse.
## `gwm theme {list|show <name>}` (issue #33)
Inspect the built-in TUI colour presets that back the `[theme]` block in `.gwm.toml`.
```bash
gwm theme list # print every built-in preset name
gwm theme show catppuccin # dump the preset as a [theme] block
gwm theme show claude-dark | tee -a .gwm.toml # paste a preset into config
```
- `gwm theme list` — print the names of every built-in preset: `catppuccin`, `gruvbox`, `tokyo-night`, `claude-dark` (the last also resolves under the alias `claude`).
- `gwm theme show <name>` — dump the named preset as a copy-pasteable, round-trippable `[theme]` TOML block you can drop into `.gwm.toml` and tweak per role.
Schema, role list, and per-role overrides: [Configuration → `[theme]`](/configuration/gwm-toml#theme). The TUI keymap-aware help overlay and modal frames pull their colours from the resolved theme — see the [TUI keybindings page](/tui/keybindings).
## `gwm tui keys` (issue #87)
Print the resolved TUI keymap — built-in defaults layered with the `[tui.keys]` overrides from `.gwm.toml` — with the source per row.
```bash
gwm tui keys
# → action keys source
# down j, Down default
# up Ctrl+n .gwm.toml
# top g g default
# …
```
The action column lists the slugs accepted in `[tui.keys]`; the keys column shows every chord bound to that action (comma-separated). An empty keys column means the action is currently unbound (the user explicitly cleared it). Reserved as a sub-tree (`gwm tui …`) so future TUI knobs land without crowding the top-level surface.
Keymap reference and chord grammar: [TUI → Keybindings](/tui/keybindings); the `[tui.keys]` schema: [Configuration → `[tui.keys]`](/configuration/gwm-toml#tuikeys).
## `gwm exec [<slug>...] [--profile <name>] [--jobs <n>] -- <cmd>` (issues #313, #324)
Run a command in each worktree — sequentially by default, or with bounded parallelism — a fleet chore across every worktree of the repo.
```bash
gwm exec -- git fetch # run `git fetch` in every non-main worktree
gwm exec feat-1 fix-2 -- cargo check # scope to two fuzzy-matched worktrees
gwm exec -- git log --oneline -5 # everything after `--` is forwarded verbatim
gwm exec --profile test # run the saved [exec.profiles.test] command
gwm exec --jobs 4 -- cargo build # fan out 4 worktrees at a time
```
Positional slugs **before** `--` scope the set (fuzzy-matched, same matcher as `gwm path` / `remove`); with none, it targets every non-main worktree. Everything **after** `--` is the command, forwarded verbatim — flags and all. gwm prints a `━━ <name> (<path>)` header per worktree, then a per-worktree `✓ / ✗` rollup, and exits non-zero if any worktree's command failed (so you can gate CI on it). An empty target set prints `no worktrees to run in` and exits `0`.
`--profile <name>` runs a saved [`[exec.profiles.<name>]`](/configuration/gwm-toml#exec-and-clean) command instead of an inline one. The profile's `command` is an argv **array** run with **no shell** — the same contract as the inline form, and a deliberate divergence from the shell-line `command` of `[git_tui]` / `[review]`. `--profile` and an inline `-- <cmd>` are **mutually exclusive** (passing both exits `1`); an **unknown** profile name exits `1`.
`--jobs <n>` sets **bounded parallelism**. `1` (the default) runs sequentially with live, inherited output. `> 1` runs up to N worktrees at once, capturing each one's output and printing it as a per-worktree block (in worktree order) once the fan-out finishes — so concurrent runs don't interleave. Precedence: `--jobs` > a profile's [`jobs`](/configuration/gwm-toml#exec-and-clean) > `[exec] jobs` > `1`. The aggregate exit code is unchanged.
This runs the user's own command against their own worktrees, so **no bootstrap trust gate** ([`gwm trust`](#gwm-trust-listrevokeshow-issue-95)) applies. It is **not** journaled into [`gwm history`](#gwm-history---limit-n---all).
## `gwm clean [<slug>...] [--profile <name>] [--yes]` (issues #313, #324)
Report — and optionally reclaim — heavy build artifacts across worktrees. **Report-only by default.**
```bash
gwm clean # report reclaimable artifacts in every non-main worktree
gwm clean feat-1 # scope to a fuzzy-matched worktree
gwm clean --yes # actually delete the listed artifacts
gwm clean --profile deep # use the [clean.profiles.deep] directory set
```
`gwm clean` scans each target worktree for `target/`, `node_modules/`, `dist/`, and `build/` and prints the reclaimable size per worktree. Positional slugs scope the set (fuzzy); with none, it targets every non-main worktree. Without `--yes` it only reports and prints `re-run with --yes to delete the listed artifacts`; an empty result prints `nothing to reclaim`.
| Flag | Action |
|:-------------------|:---------------------------------------------------------------------|
| `--profile <name>` | Reclaim the [`[clean.profiles.<name>]`](/configuration/gwm-toml#exec-and-clean) directory set — a **complete** set that replaces the built-ins (unknown name exits `1`) |
| `--yes` | Delete the listed artifacts instead of only reporting them |
Without `--profile`, `gwm clean` uses `[clean.profiles.default]` when that profile is defined, else the built-in `target`/`node_modules`/`dist`/`build`. A profile's `dirs` **replaces** the built-ins (never adds to them); the safety gate below still applies to every directory.
**Safety:** `--yes` deletes a directory **only** when git treats it as ignored *and* it holds no tracked files. A `dist/` or `build/` that is tracked or hand-authored (hence non-regenerable) is reported as `skipped … not git-ignored, or holds tracked files`, never removed. Because the artifacts are regenerable, `gwm clean` is **deliberately not journaled** into [`gwm history`](#gwm-history---limit-n---all) — there is no `gwm undo` for it.
## exit codes
| Code | Meaning |
|:-----|:-----------------------------------------------------------------------|
| `0` | success — also "all green" for `gwm doctor` |
| `1` | recoverable failure — fuzzy miss, ambiguous match, doctor Warning |
| `2` | hard failure — bootstrap `✗`, doctor Failure, unrecoverable git error |