gwm-cli 1.0.0

git worktree manager — TUI + CLI, native libgit2, per-repo bootstrap
Documentation
---
title: when: predicates
description: file_exists / cmd_exists / env_set / env_eq / glob_exists, with !, &&, || composition.
---

# `when:` predicates

The `when:` field on a lifecycle hook (`[[hooks.*]]`) — and on the legacy `[[bootstrap.command]]` — conditionalises shell steps. Predicates compose with boolean operators, so a single step can express "run `bun install` if `package.json` exists AND `bun` is on `$PATH`, but never in CI".

The same grammar applies to every `[[hooks.<phase>]]` block (`pre_create`, `post_create`, `pre_bootstrap`, `post_bootstrap`, `pre_remove`, `post_remove`). The examples below use `[[bootstrap.command]]`, but `when = "…"` reads identically on `[[hooks.post_create]]`.

## supported atoms

| Atom                          | True when …                                                                          |
|:------------------------------|:-------------------------------------------------------------------------------------|
| `file_exists:<path>`          | `<worktree>/<path>` resolves on disk                                                  |
| `cmd_exists:<binary>`         | `<binary>` resolves on `$PATH` (`which` lookup)                                       |
| `env_set:<NAME>`              | `std::env::var(NAME)` returns `Ok` (the var is **defined**, possibly empty)           |
| `env_eq:<NAME>=<value>`       | `NAME` is set and its value equals `<value>` **exactly**                              |
| `glob_exists:<pattern>`       | at least one path under the worktree matches `<pattern>` (supports `**`)              |

Atoms are case-sensitive. Whitespace around the colon and around boolean operators is tolerated. Unknown keywords default to `true` so old configs keep running while `gwm doctor` (check #3) surfaces them as Warning.

## boolean composition

| Operator | Meaning | Precedence       |
|:---------|:--------|:-----------------|
| `!`      | NOT     | highest          |
| `&&`     | AND     | middle           |
| `\|\|`   | OR      | lowest           |

So `!a && b || c` parses as `((!a) && b) || c` — same as in most languages.

Whitespace around operators is tolerated, but operators themselves must be exactly `!`, `&&`, `||` — no `not`, `and`, `or`, no Unicode.

## examples

```toml
# Run only when composer.json exists at the worktree root.
[[bootstrap.command]]
name = "composer install"
run  = "composer install --no-interaction --prefer-dist"
when = "file_exists:composer.json"

# Prefer bun if available, otherwise fall back to npm — and never in CI.
[[bootstrap.command]]
name = "install (bun)"
run  = "bun install"
when = "file_exists:package.json && cmd_exists:bun && !env_set:CI"

[[bootstrap.command]]
name = "install (npm fallback)"
run  = "npm ci"
when = "file_exists:package.json && !cmd_exists:bun && !env_set:CI"

# Build docs only when there's something to build and we're not in CI.
[[bootstrap.command]]
name = "build docs"
run  = "./scripts/full-build.sh"
when = "glob_exists:docs/**/*.md && !env_set:CI"

# Apply staging-only seeds when APP_ENV is exactly "staging".
[[bootstrap.command]]
name = "staging seed"
run  = "php artisan db:seed --class=StagingSeeder"
when = "file_exists:artisan && env_eq:APP_ENV=staging"

# Allow a long-running prep step only when the user opts in via env.
[[bootstrap.command]]
name = "warm up cache"
run  = "./scripts/warmup.sh"
when = "env_eq:GWM_WARMUP=1"
```

## omitted `when:`

A step without `when:` runs unconditionally — equivalent to `when = "true"` (which is not a literal you can type; just omit the field). Common for housekeeping commands like `git lfs pull` that are cheap and always safe.

## doctor coverage

`gwm doctor` check **#3** (``when` predicates supported`) parses every `[[bootstrap.command]].when` and surfaces unknown keywords:

```
! `when` predicates supported
    1 unsupported keyword: file_exits
    → did you mean file_exists?
```

Note the **`!`** (Warning) — the offending command still runs (defaults to `true`), so the bootstrap is not blocked, but the user sees the typo at config-time instead of waiting for a confused failure.

## related

- [Bootstrap pipeline]/configuration/bootstrap — where the `[[bootstrap.command]]` step sits
- [`.gwm.toml` schema]/configuration/gwm-toml#bootstrapcommand — the full field listing
- [Integrations → `gwm doctor`]/integrations/doctor — checks #2 (guard refs) and #3 (predicate grammar)