shelbi zen <SUBCOMMAND> [OPTIONS]

shelbi zen is the CLI face of Zen Mode. The toggle subcommands (on, off, pause) flip the project's mode in state.json and write a <ts> project=<project> mode=zen <prev> -> <new> reason=user:cli line to ~/.shelbi/events.log (the leading project=<project> scope keeps one project's toggle from being read by another project's orchestrator). status is read-only: it prints the current mode and writes no event. The orchestrator reads those events to know when to switch behavior.

The remaining subcommands are single-purpose primitives. The orchestrator sequences them per the merge-conditions policy in the project's zenmode.md file; each one does exactly one thing and reports on stdout in a fixed, parseable shape: probe prints JSON, scan prints one task id per line, dry-run streams one line per decision, and pr-create, ci-watch, and pr-merge lead with a single result line (pr-merge follows it with the post-merge eligibility list). The same primitives are available to you at the shell when you want to probe a branch or open a PR by hand without flipping the global mode.

Every subcommand accepts the global -p / --project <PROJECT> flag, omitted from the per-subcommand tables.

on

shelbi zen on

Turn Zen Mode on. The orchestrator may auto-promote and auto-merge finished work through the exact-provenance PR flow. Idempotent. Mirrors Alt+Z in the TUI.

on, off, and pause each print the resulting zen status block. Like probe, pr-create, and pr-merge, they refuse to run when the hub daemon's version doesn't match the CLI's; status, scan, ci-watch, and dry-run only warn.

off

shelbi zen off

Turn Zen Mode off. Every promotion goes through manual review. In-flight workspaces keep going; nothing already running is cancelled.

pause

shelbi zen pause

Pause Zen Mode: no new auto-promotions, but tasks already on the Zen track may still complete their merge flow. Use this to triage incoming work yourself while preserving work already in motion.

status

shelbi zen status

Print the current mode, the configured local check commands, the effective CI timeout, the resolved danger-paths list (labeled project override, or with the detected project shapes), any per-workflow zen: overrides, the last crash (with its crash-record path, or never), the orchestrator's last heartbeat, and the count of in-flight issues on the Zen track. Cheap and safe to call repeatedly.

zen mode: on
checks:
  - cargo test --workspace
ci timeout: 15m
danger paths (project override):
  - .github/workflows/**
  - Cargo.lock
last crash: never
orchestrator alive: 2026-10-29T14:24:00+00:00
in-flight zen issues: 1

scan

shelbi zen scan

Print backlog task ids that are mechanically eligible for Zen auto-promotion, one per line, in priority order. "Mechanical" means not blocked on dependencies, not opted out via task frontmatter, and no file overlap with anything currently in flight. It also skips tasks you moved from todo back to backlog by hand, until you move them out of backlog again. On a GitHub board, it only considers backlog issues assigned to you, and lists nothing if your login can't be resolved. Ties in priority are broken by id. File overlap is a heuristic: a file path named in the task's body also appears in an in-progress task's body. The orchestrator's prompt applies the judgment categories on top of this list. See the judgment layer.

dry-run

shelbi zen dry-run [OPTIONS]

Preview what Zen Mode would do without changing task, board, PR, or branch state. On every tick the backlog scan and merge-conditions bar are evaluated, and each "would have …" decision is logged to stdout, the dedicated dry-run log (~/.shelbi/logs/zen-dryrun.log), and the activity feed. No PRs, merges, or board moves happen. Use it before flipping Zen on for real to confirm the policy matches your intent.

FlagTypeDefaultDescription
--for <DURATION>durationrun until Ctrl-CStop after this long. Accepts 30s, 5m, 2h, 1d, or a bare integer of seconds.
--interval <DURATION>duration5sOverride the per-tick interval. Same duration grammar as --for. Values below 1s are raised to 1s.

probe

shelbi zen probe <TASK_ID>

Run every readiness probe for the task and print the full report as pretty JSON. Covers the four pre-PR conditions the orchestrator checks before opening a PR: local checks, merge-conflict probe, diff size, and danger-path matches.

The task must be assigned to a workspace so the probe can locate the repository that holds its named branch. Shelbi checks out that exact branch commit in an isolated temporary worktree, so a newer task reusing the assigned workspace is not modified or tested by mistake. A successful rebase advances the durable task branch. The report freezes repository, repository_id, base_branch, base_sha, integration_sha, and head_sha. Together those fields identify the exact repository and workflow base used for every check and fact, plus the reviewed task commit and exact squash candidate. Pass the same six values to every later PR command.

The squash candidate (integration_sha) is always the base at base_sha plus the branch's own changes. When the branch already contains the base, that is exactly the branch's tree. When it does not (an unrebased or conflicted tip), Shelbi builds the real three-way merge instead of taking a branch tree that predates the base. diff_size measures base_sha to integration_sha, the commit that actually lands, so a candidate that would revert work on the base shows it there. When the three-way merge conflicts, merge_conflict names the files and integration_sha is null: there is nothing to publish or land.

The probe fails closed (exit 3 with a JSON error naming the reason) instead of reporting a candidate it can't vouch for: a branch whose history carries merge commits and doesn't rebase cleanly onto the base, a candidate that would drop or rewrite a file present at base_sha that the branch never touched, or a local task ref that doesn't hold the published work.

That last case comes up when a card built on one machine is reassigned to a slot on another, where the local ref can still sit where dispatch cut it. The error names the published tip and the git fetch that brings the ref up to date. A branch whose content already landed on the base under another commit isn't this case: the probe reports it with branch_divergence.already_merged.

ArgumentTypeDefaultDescription
<TASK_ID>string—Task id to probe (required).

pr-create

shelbi zen pr-create <TASK_ID> --match-repository <REPOSITORY> --match-repository-id <REPOSITORY_ID> --match-base-branch <BRANCH> --match-base-commit <BASE_SHA> --match-integration-commit <INTEGRATION_SHA> --match-head-commit <HEAD_SHA> [--match-published-head-commit <SHA>]

Push the task's named branch and open a PR. Idempotent. If an open PR for the branch already exists, Shelbi updates the branch and returns its number only after the PR head matches the exact task branch commit that was pushed. Otherwise, it opens a new PR. Prints the PR number on stdout.

Exits 0 on success. Exits 75 with empty stdout when the push landed but GitHub could not yet confirm the PR head; that's retry-safe, so re-run the same command. Exits 1 on a provenance mismatch that must not be merged.

Reuse also requires the exact task branch, resolved workflow base branch and commit, and origin repository identity. A same-named PR aimed at another base or coming from another repository is rejected.

The six --match-* flags above are required. Copy them from the immediately preceding probe report without re-resolving configuration or origin. Shelbi rejects the operation if any part of that identity moved after the probe, so mutable workflow, remote, or branch state cannot silently replace what was reviewed.

A seventh flag, --match-published-head-commit <SHA>, is optional and only pr-create reads it. Pass the probe report's published_head_sha when the probe rebased the task branch onto a moved base — so head_sha is a never-pushed local commit while the remote task branch still points at the pre-rebase tip. It lets pr-create recognize that pre-rebase remote tip as the flow's own prior state rather than a concurrent update. Omit it to preserve the default acceptance rules. (ci-watch and pr-merge also accept the flag because they share the same pinned-identity group, but neither acts on it.)

pr-create refuses to replace a remote task branch that has changes with an integration commit that has none, and pushes nothing. If the work really landed another way, close the PR by hand.

Argument or flagTypeDefaultDescription
<TASK_ID>string—Task id whose branch to push and PR (required).
--match-repository <REPOSITORY>stringrequiredRequire the origin repository selector to equal probe repository.
--match-repository-id <REPOSITORY_ID>stringrequiredRequire the immutable GitHub repository id to equal probe repository_id.
--match-base-branch <BRANCH>stringrequiredRequire the PR target to equal probe base_branch.
--match-base-commit <SHA>stringrequiredRequire the resolved target commit to equal probe base_sha.
--match-integration-commit <SHA>stringrequiredRequire the published PR head to equal probe integration_sha.
--match-head-commit <SHA>stringrequiredRequire the durable task branch to match probe head_sha.
--match-published-head-commit <SHA>string—Optional. Probe published_head_sha — the pre-rebase remote tip, recognized as the flow's own prior state when the probe rebased onto a moved base.

ci-watch

shelbi zen ci-watch <PR_NUMBER> --match-repository <REPOSITORY> --match-repository-id <REPOSITORY_ID> --match-base-branch <BRANCH> --match-base-commit <BASE_SHA> --match-integration-commit <INTEGRATION_SHA> --match-head-commit <HEAD_SHA> [OPTIONS]

Watch the PR's checks until they settle or the timeout fires. Prints one of:

  • green — every watched check passed; exit 0.
  • red:<check>:<summary> — at least one check failed; exit 1.
  • timeout — checks still pending when the deadline hit; exit 2.

Which checks it watches is auto-selected from the target branch: with branch-protection required checks configured, it watches only those; on an unprotected branch (or one with no required set) it falls back to every check reported on the PR and requires GitHub's merge state to be clean. When required rows exist, a blocked merge state keeps waiting for a required context that has not reported, while an optional failed check does not override passing required checks.

All six --match-* values must be the unchanged probe identity passed to pr-create. Each poll verifies that original repository, base name and commit, and head commit. Shelbi reads the PR identity and status rollup together, including which contexts are required, and grades that one atomic snapshot. There is no separate check-result read that a brief A-to-B-to-A head change could confuse. Incomplete, paginated, or moved-head results fail closed and the whole pinned flow must restart from a fresh probe.

The landing is pinned to what CI graded

probe, pr-create, and ci-watch keep a record of each task's flow in <project>/zen-flow/<task-id>.json, so the done move that lands the task takes no extra flags. While Zen is on or paused, the workflow's merge action reads that record and refuses to land a probed task unless all of these hold:

  • ci-watch last reported green for the commit the latest probe pinned.
  • The task's open PR is the one CI graded, and its head is still that commit.
  • The base branch is still at the commit CI graded against.

A refusal fails the move and merges nothing, and the card stays where it is. To recover, run shelbi zen probe <task-id> again and restart the flow. To override, merge the PR by hand: the move then finds it already merged and completes.

The merge action also refuses a PR with an empty diff, with Zen on or off, because every check passes on one and merging it lands nothing. The exception is a workflow that produces no branch: none of its edges push, open a PR, merge, or restack. If the work really landed another way, close the PR and move the card with --skip-transition-actions.

FlagTypeDefaultDescription
<PR_NUMBER>integer—The PR number returned by pr-create (required).
--match-repository <REPOSITORY>stringrequiredRequire probe repository on every snapshot.
--match-repository-id <REPOSITORY_ID>stringrequiredRequire probe repository_id on every snapshot.
--match-base-branch <BRANCH>stringrequiredRequire probe base_branch on every snapshot.
--match-base-commit <SHA>stringrequiredRequire probe base_sha on every snapshot.
--match-integration-commit <SHA>stringrequiredRequire the live PR head to stay at probe integration_sha.
--match-head-commit <SHA>stringrequiredBind the candidate back to probe head_sha.
--timeout <DURATION>durationproject's zen.ci_timeout (default 15m)Override the wait. Accepts 30s, 5m, 2h, 1d, or a bare integer of seconds.
--task <TASK_ID>string—Resolve the default timeout against the task's workflow's zen.ci_timeout (falling back to the project value). Without it, the project default is used directly.

pr-merge

shelbi zen pr-merge <PR_NUMBER> --match-repository <REPOSITORY> --match-repository-id <REPOSITORY_ID> --match-base-branch <BRANCH> --match-base-commit <BASE_SHA> --match-integration-commit <INTEGRATION_SHA> --match-head-commit <HEAD_SHA>

Land the prebuilt squash candidate after ci-watch reports green. Shelbi verifies that integration_sha has sole parent base_sha and exactly the three-way integration tree of head_sha onto it, then advances only refs/heads/<base_branch> from base_sha to integration_sha with Git's compare-and-swap lease. A concurrent same-head PR retarget cannot redirect that update. This atomicity applies to the remote ref update, not to GitHub PR metadata.

Rulesets are checked rule by rule. Non-PR rules such as deletion and non_fast_forward don't block the leased push. When the base branch has a pull_request rule Zen can satisfy (no required approvals or code-owner review, and squash allowed), Zen lands through GitHub's own squash merge of the PR, pinned to integration_sha, instead of the leased push; the landed commit is then a new GitHub-authored SHA. Classic branch protection (or protection state that can't be read), a merge-queue rule, required approvals or code-owner review, a disallowed squash method, auto-merge or a merge queue on the PR, fork heads, or a non-squash merge_strategy all stop the flow: Zen leaves the base unchanged and tells a human to review and merge through the repository's required GitHub workflow.

On success it prints the landed SHA (sha-pending if GitHub hasn't recorded its merge commit yet), deletes the task branch on the remote, and then prints the next mechanically eligible backlog ids, or next eligible: none.

Argument or flagTypeDefaultDescription
<PR_NUMBER>integer—The PR number to merge (required).
--match-repository <REPOSITORY>stringrequiredRequire probe repository.
--match-repository-id <REPOSITORY_ID>stringrequiredRequire probe repository_id.
--match-base-branch <BRANCH>stringrequiredRequire probe base_branch.
--match-base-commit <SHA>stringrequiredRequire probe base_sha.
--match-integration-commit <SHA>stringrequiredRequire probe integration_sha.
--match-head-commit <SHA>stringrequiredRequire probe head_sha.

Examples

Flip Zen on for the current project:

shelbi zen on

See the resolved danger-path list and configured local checks:

shelbi zen status

Inspect what Zen would consider promoting right now (without promoting anything):

shelbi zen scan

Watch the policy in motion for ten minutes without committing to it:

shelbi zen dry-run --for 10m

Run the full pre-merge probe by hand on a finished branch and pipe it through jq:

shelbi zen probe docs-write-cli-reference-pages | jq .

Drive the full merge flow manually, one primitive at a time, exactly the sequence the orchestrator runs internally:

REPORT=$(shelbi zen probe docs-write-cli-reference-pages)
REPOSITORY=$(printf '%s\n' "$REPORT" | jq -r .repository)
REPOSITORY_ID=$(printf '%s\n' "$REPORT" | jq -r .repository_id)
BASE_BRANCH=$(printf '%s\n' "$REPORT" | jq -r .base_branch)
BASE_SHA=$(printf '%s\n' "$REPORT" | jq -r .base_sha)
INTEGRATION_SHA=$(printf '%s\n' "$REPORT" | jq -r .integration_sha)
HEAD_SHA=$(printf '%s\n' "$REPORT" | jq -r .head_sha)
PIN=(--match-repository "$REPOSITORY" --match-repository-id "$REPOSITORY_ID" --match-base-branch "$BASE_BRANCH" --match-base-commit "$BASE_SHA" --match-integration-commit "$INTEGRATION_SHA" --match-head-commit "$HEAD_SHA")
PR=$(shelbi zen pr-create docs-write-cli-reference-pages "${PIN[@]}")
shelbi zen ci-watch "$PR" "${PIN[@]}" --timeout 10m
shelbi zen pr-merge "$PR" "${PIN[@]}"

Pause Zen during a release window without aborting in-flight merges:

shelbi zen pause

See also

  • Zen Mode — the full mental model, the high-confidence bar, and how to tune the judgment categories per project.
  • Orchestrator — how the prompt template that drives Zen's policy is loaded.
  • The events log — the mode=zen and orchestrator:zen-* lines the toggles and primitives emit.
  • Review workspaces — the user-driven counterpart, where approving a reviewed task lands its branch without the Zen bar.