gwm-cli 1.6.1

git worktree manager — TUI + CLI, native libgit2, per-repo bootstrap
Documentation
---
title: gwm doctor
description: 9 health checks with ✓ / ! / ✗ reporting and 0 / 1 / 2 exit codes for CI.
---

# `gwm doctor`

`gwm doctor` runs a battery of cheap checks against the current repo, prints a per-check report, and exits with a meaningful code so it can be wired into CI or a pre-commit hook without parsing stdout.

![Coloured gwm doctor output with ✓ sigils](./_assets/doctor.png)

## exit codes

| Code | Meaning      | Triggered by                                         |
|:-----|:-------------|:-----------------------------------------------------|
| `0`  | all green    | every check returns ``                              |
| `1`  | warning      | at least one `!`, no ``                              |
| `2`  | failure      | at least one ``                                      |

The Warning / Failure split matches the bootstrap pipeline's sigil convention — see [Bootstrap pipeline](/configuration/bootstrap#stage-reports).

## sample output

```bash
$ gwm doctor
✓ .gwm.toml parses
    /path/to/repo/.gwm.toml parses cleanly
✓ guard references resolve
    2 guard reference(s) resolve
✓ `when` predicates supported
    1 predicate(s) recognised
✓ external binaries on PATH
    3/3 binaries found
✓ no prunable worktrees
    5 worktree(s) tracked, none prunable
✓ no orphan gwm branches
    7 merged gwm-style branch(es) preserved per CONTRIBUTING, no unmerged orphans
✓ base directory writable
    /home/you/cc-worktree/myrepo is writable
✓ [tui.keys] keymap resolves
    14 action(s) bound
```

A run with issues:

```
! no prunable worktrees
    1 prunable entry: feat-12-old
    → run `gwm prune` to clear them
! no orphan gwm branches
    1 unmerged orphan branch(es): feat/#99-wip-experiment
    → git branch -d feat/#99-wip-experiment
```

Each Warning / Failure carries a one-line remediation hint, copy-pasteable.

## checks performed

### 1. `.gwm.toml` parses

Reads `.gwm.toml` from the repo root.

- **``** — file parses cleanly, **OR** file is absent (defaults assumed)
- **``** — TOML is malformed, or contains an unknown `[tui.open].mode` value

The "absent" case is intentionally a `✓` — `gwm` works without `.gwm.toml` and the user-facing message says so.

### 2. guard references resolve

Walks every `[[bootstrap.copy]].guards = [...]` and verifies that each name resolves to an existing `[[bootstrap.guard]]`.

- **``** — every reference points at a real guard
- **``** — at least one reference is dangling

Catches typos like `guards = ["no-aws-dbs"]` when the guard is `no-aws-rds`.

### 3. `when` predicates supported

Parses every `[[bootstrap.command]].when` and reports unknown keywords.

- **``** — every atom uses a recognised keyword
- **`!`** — at least one keyword is unrecognised (Warning, not Failure — the offending command still runs, defaulting to `true`)

Recognised keywords: `file_exists:`, `cmd_exists:`, `env_set:`, `env_eq:`, `glob_exists:`. Boolean composition (`!`, `&&`, `||`) is also validated. See [`when:` predicates](/configuration/when-predicates).

### 4. external binaries on PATH

Probes `$PATH` for every binary gwm or `.gwm.toml` plans to spawn:

- **`lazygit`** — only if `[git_tui]` doesn't override the command
- **the first token of `[git_tui].command`** — when overridden
- **the first token of `[review].command` / preset**
- **`direnv`** — only if `.envrc` exists in the worktree (the `seed-from-example` action may try `direnv allow`)
- **the first executable token of every `[[bootstrap.command]].run`**

Reporting:

- **``** — all probed binaries resolved
- **`!`**`[review]` binary missing (review is opt-in; a CI pre-commit hook keeps passing)
- **``**`[git_tui]` binary missing or a bootstrap command's binary missing

> The v0.6 update to this check (probing `[git_tui]` and `[review]`) landed with [#75]https://github.com/kbrdn1/gwm-cli/issues/75. Pre-v0.6, only `lazygit` and `direnv` were checked.

### 5. no prunable worktrees

Looks at `.git/worktrees/` and flags entries whose working directory was removed manually (e.g. someone `rm -rf`'d the worktree directory outside gwm).

- **``** — every tracked entry points at a real directory
- **`!`** — at least one stale entry, with `gwm prune` as the remediation

### 6. no orphan gwm branches

Walks local branches matching `<type>/#<N>-<slug>` and flags those with no associated worktree.

- **``** — every gwm-style branch is either active (has a worktree) or merged into a trunk
- **`!`** — at least one **unmerged** orphan, with `git branch -d <name>` as the remediation

The "merged into a trunk" exemption respects `CONTRIBUTING.md`'s "never delete the source branch after merge" rule — merged branches are preserved, not flagged. The trunk list is configurable via `[doctor].trunks` (default `["dev", "main"]`); empty list disables the exemption.

User-managed branches (`main`, `release-*`, `dependabot/...`, anything not matching the gwm pattern) are silently ignored.

### 7. base directory writable

Checks that the configured `[worktree].base` exists and is writable, or — if it doesn't exist yet — that its **parent** is writable. gwm creates the base lazily on the first `gwm create`, so a non-existent base is fine as long as it can be created.

- **``** — base (or its parent) is writable
- **``** — neither is writable (gwm cannot create worktrees here)

### 8. `[tui.keys]` keymap resolves

Re-runs the same `[tui.keys]` resolution path the TUI itself uses at startup, so any keymap mistake surfaces in `gwm doctor` before the TUI fails to dispatch. Added by [#87](https://github.com/kbrdn1/gwm-cli/issues/87) / [#165](https://github.com/kbrdn1/gwm-cli/pull/165) alongside the configurable keymap.

- **``** — the keymap resolves and `quit` has at least one user-visible binding; the detail reports how many actions are bound (`N action(s) bound`)
- **`!`** — the keymap resolves, but `quit` has been unbound entirely. `Ctrl+C` still exits the TUI as a hard-coded fallback in `run_app`, but no discoverable quit key remains — the hint suggests adding e.g. `quit = ["q", "Esc"]` to `[tui.keys]`
- **``** — the keymap fails to resolve (parse error, unknown action slug, chord conflict, or prefix collision); the detail repeats the underlying `[tui.keys]` error verbatim, and the hint points at `gwm tui keys` for the full action-slug list

Only actions with at least one chord count toward the `N action(s) bound` figure — an action set to `[]` in `[tui.keys]` is unbound and excluded. See [`.gwm.toml` schema → `[tui.keys]`](/configuration/gwm-toml#tuikeys), [TUI → Keymap & command palette](/tui/keymap-and-palette), and [TUI → Keybindings](/tui/keybindings) for the action slugs and chord grammar.

### 9. `worktree.branch_pattern` round-trips through the parser

`branch_pattern` drives how gwm **writes** a branch name, but the parser that **reads one back** is a hardcoded regex for the default `{type}/#{issue}-{desc}`. When the two disagree, every feature keyed on the parsed segments reads the wrong thing — silently. Added by [#415](https://github.com/kbrdn1/gwm-cli/issues/415).

The check is a real round-trip probe, not a comparison against the default string: **a custom pattern is not automatically a broken one**. `{type}/#{issue}-prefix-{desc}` still yields `feat/#42-…`, so `type` and `issue` survive and only `desc` comes back wrong.

- **``**`parse_branch` reads back the segments `branch_name` writes, for every branch the repo can produce
- **`!`** — the pattern does not round-trip. Three shapes:
  - **nothing parses** → issue auto-linking from the branch name, gitmoji / `gwm commit-prefix`, `gwm pr` template selection and body placeholders, hook placeholders on the remove / bootstrap paths, the TUI rename and the branch-convention check (#6 above) are all inactive. **PR/MR detection is not affected**`Forge::find_pr_for_branch` queries the forge with the whole branch name and never parses it
  - **only some values parse** → the detail names a failing example and scopes the loss to the branches that come out like it. `{desc}/#{issue}-{type}` is unreadable for a desc carrying a `-` and perfectly readable for one without; calling the whole pattern dead would be false for half the branches it produces
  - **everything parses but a segment comes back different** → the detail names which one and every feature reading it: `type` → gitmoji selection, `issue` → issue auto-linking, and both → remove/bootstrap hook placeholders and the TUI rename, which consume all three segments

  The hint stays neutral — restore the default, or keep the pattern and accept exactly the loss the detail names. Which workaround applies depends on which segment broke, so recommending one unconditionally would be wrong.

### how the probe space is chosen

Round-trip is value-dependent, so probing a couple of arbitrary values proves nothing either way. The probe enumerates the value space `gwm create` actually admits, by construction rather than by sampling:

| Segment  | Probed with                          | Why that is the whole space |
|:---------|:-------------------------------------|:----------------------------|
| `type`   | every configured branch type         | finite, so this is exhaustive — and a type your `[[branch_types]]` forbids must not raise a warning about branches that cannot exist |
| `issue`  | one single-digit, one multi-digit    | `\d+` is the only distinction `BRANCH_RE` can split on |
| `desc`   | one with a `-`, one without, one all-digits | the dash is the only character that collides with a literal separator; digits-only is the only desc `BRANCH_RE`'s `\d+` issue group can swallow (`{type}/#{desc}-{issue}` parses for `123` and for nothing else) |
| `{repo}` | the **real** repo name               | the verdict depends on it: `{repo}/#{issue}-{desc}` is fine in a repo called `gwm` and unparseable in one called `gwm-cli`, whose dash the `[a-z]+` type charset rejects |

A pattern that survives all of it is the strongest claim this check can make short of deriving the parser from the pattern. And the verdict never quantifies beyond what the probes saw: when only part of the probed shapes lose something, the detail counts them (`on 27 of the 30 branch shapes probed, …`) rather than claiming every branch is affected.

This check states the limitation rather than fixing it. Deriving the parser from the pattern is tracked by [#417](https://github.com/kbrdn1/gwm-cli/issues/417). `gwm config validate` prints the same message on stderr and still exits `0` — a custom pattern is valid configuration, not an error — and reads the **effective** pattern, so one set only in the global `~/.config/gwm/config.toml` is caught too.

## CI integration

```yaml
# .github/workflows/ci.yml
- name: gwm health
  env:
    GWM_ALLOW_BOOTSTRAP: "1"          # if the job also runs `gwm create` / `gwm bootstrap`
  run: gwm doctor                    # exits 1 on Warning, 2 on Failure
```

`gwm doctor` itself doesn't go through the [TOFU trust gate](/configuration/trust-ledger) (the doctor never invokes `bootstrap::run` — it only reads config), so `GWM_ALLOW_BOOTSTRAP=1` is harmless here. Set it for jobs that also create worktrees in the same workflow run.

> **What doctor does NOT audit (yet)**: the trust ledger contents themselves. A job that wants to assert "this CI host has trusted exactly the configs the workflow expects" would have to parse `~/.config/gwm/trust.toml` (or `$GWM_TRUST_LEDGER`) manually. Auditing the ledger from `gwm doctor` is on the post-#95 follow-up list.

Or as a pre-commit hook:

```bash
# .git/hooks/pre-commit (or via pre-commit framework)
gwm doctor || { echo "gwm doctor reported issues, see above"; exit 1; }
```

Because `[review]` missing only triggers a Warning (exit `1`), a pre-commit hook can choose to allow Warnings (`gwm doctor; [ $? -le 1 ]`) and only block on Failures.

## related

- [Bootstrap pipeline]/configuration/bootstrap — same `✓ / ! / ✗` convention used by stage reports
- [TUI → Configurable launchers]/tui/launchers#interaction-with-gwm-doctor — why the review launcher's missing-binary is a Warning, not a Failure
- [`.gwm.toml` schema → `[doctor]`]/configuration/gwm-toml#doctor — the `trunks` knob