---
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
| `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
| `!` | 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)