Skip to content

Land - the ship loop

/flow-next:land is the third flow-next loop. Where pilot and Ralph deliberately stop at a draft PR, land babysits those PRs the rest of the way: CI green, automated reviews converged, merged, and released if your project defines a release. Fittingly, it was the first spec pilot drove end-to-end.

It is /loop-shaped rather than /goal-shaped, because babysitting waits on external events (CI runs, reviewer bots) over hours. The loop wakes on a cadence, acts on every PR it owns, and sleeps. A 30-minute cadence comfortably brackets the default patience window.

/loop 30m /flow-next:land

Driver snippets for every host are on Driving a loop.

Terminal window
/flow-next:land # one tick over all build-loop-authored PRs
/flow-next:land --dry-run # full gate classification, zero mutations

Land is fully autonomous by design: there is no interactive mode. It never asks questions, and ambiguity maps to NEEDS_HUMAN. It requires gh and jq on PATH and gh auth status passing. Gate surfaces were verified against gh 2.93.0, so re-verify gh pr checks --json bucket, --match-head-commit, and mergeStateStatus on major gh bumps.

For each open PR the build loop authored, land takes at most one action class per PR per tick:

  1. Discover. Open specs with all tasks done, probed by branch. A PR qualifies only with both authorship signals: its head branch matches a spec’s branch_name AND the make-pr breadcrumb is in the PR body. Branch-only matches are reported NEEDS_HUMAN and never acted on, so your hand-opened PRs are never touched. Specs with in-flight tasks stay pilot’s, which is the pilot-concurrency interlock.
  2. CI. Tri-state over all checks (gh pr checks --json bucket): any fail means diagnose from the failing run’s logs, scope a fix, push, and report FIXING_CI; any pending (or an empty check list right after a push) means wait, never treated as green. Fix attempts are bounded (land.ciFixBudget, default 3, strikes ledgered under .git/); on exhaustion the PR gets a durable flow-next:needs-human label, is reported NEEDS_HUMAN, and is skipped on later ticks until a human removes the label. Unrelated infra flakes get one gh run rerun instead of an edit.
  3. Reviews. Wait for automated reviewers inside a patience window (land.patienceMinutes, default 30), anchored to the last push, since a CI-fix push invalidates prior reviews and restarts the wait. With land.patienceMinutesAfterReview set, the silence gate instead measures its wait from the latest head-current bot review once zero threads are unresolved - the window is the human-objection grace period, so this stays opt-in. Report AWAITING_REVIEW until reviews land or the window elapses. With land.requestReviewers set (4.2.0), the tick where a human review is the only missing merge input also flips the draft to ready and requests the configured reviewers, once per PR per head SHA, author filtered, never as a merge gate.
  4. Resolve. Unresolved threads route through /flow-next:resolve-pr dispatched with mode:autonomous (fix, verify, reply, resolve), and land gates on its machine-readable terminal line (RESOLVE_PR_VERDICT=<RESOLVED|PENDING|NEEDS_HUMAN> …), looping tick over tick until convergence.
  5. Merge. CI green + threads addressed + the review signal satisfied means flip the draft to ready (gh pr ready) and merge explicitly: gh pr merge --squash --delete-branch --match-head-commit <head-sha>. Never gh pr merge --auto: land itself is the gate, and --auto insta-merges on unprotected repos. A behind or conflicted base gets one server-side catch-up (gh pr update-branch — merge-based, so your commit SHAs and the evidence recorded against them survive; land never rebases and never force-pushes); GitHub refusing the base merge reports an honest BLOCKED, because land never hand-resolves hunks. Since 4.8.0, each tick also claims its clone atomically before touching any state — owner-aware, so a live tick is never reaped, an idle tick never leaves a lock behind, and overlapping cadence ticks can no longer overwrite each other’s ledger.
  6. Close and release. After the merge, flowctl spec close prevents the build loop from re-selecting merged work. Land then makes one flowctl tracker sync <spec-id> --op push --event land.merged facade call when the bridge is active. This is the only path that flips the issue to Done, gated on the GitHub MERGED probe, and it carries the release/verdict comment in a private input file. Land then follows your project’s own release instructions (docs/RELEASING.md, RELEASING.md, agent_docs/releasing.md, or docs referenced from CLAUDE.md/AGENTS.md) if present, using deterministic non-interactive commands and an idempotency probe so re-entry never re-tags. No release docs means stop at merge. Land never invents versioning or publish steps.

A merged-but-unclosed spec re-enters idempotently: a later tick resumes close → tracker → release, never a second merge.

Per-PR verdicts are MERGED | RELEASED | FIXING_CI | AWAITING_REVIEW | RESOLVING | BLOCKED | NEEDS_HUMAN, echoed as evidence blocks. Every tick ends with exactly one machine-greppable line, the last line of output with nothing after it (the PILOT_VERDICT precedent):

LAND_VERDICT=<verdict|NO_WORK> prs=<n> pr=<deciding-pr-url|-> reason="<one line>"

The tick-level verdict is the worst severity across PRs (NEEDS_HUMAN > BLOCKED > FIXING_CI > RESOLVING > AWAITING_REVIEW > RELEASED > MERGED), and NO_WORK when discovery finds zero authored PRs.

Land is the only place in flow-next licensed to auto-merge, an explicit and confined override of the repo-wide “no gh pr merge from skills” rule. The license is bounded by the gate:

  • CI green: every check’s bucket is pass or skipping, fixed by the loop if needed.
  • Every review thread addressed: resolve-pr convergence, bounded at its existing fix-verify cycles.
  • The configured review signal (land.reviewSignal) satisfied:
SignalSatisfied when
silence (default)≥1 review by an automated reviewer of the current head + zero unresolved threads + the patience window elapsed since the last push. Built for bot reviewers (Codex and others) that comment but never file formal APPROVEs, including the case where the bot signals a clean pass via a comment naming the reviewed commit (see below)
approveFormal reviewDecision == APPROVED
<github-login>That reviewer’s latest review is APPROVED, or COMMENTED with zero unresolved threads

An automated reviewer is a review author whose login ends in [bot], plus any login you list in land.automatedReviewers. No automated review ever plus no signal configured means land never merges unreviewed; it reports NEEDS_HUMAN instead. A repo that dismisses stale approvals on push is detected (approve → re-push → re-required ping-pong) and reported NEEDS_HUMAN rather than re-looped.

Clean-review comments count too (2.1.1+). Some bots, notably Codex, only file a formal review when they have findings; on a clean pass they post an issue comment (“Didn’t find any major issues. Reviewed commit abc1234”) that never reaches the reviews API. Under silence, land also scans the PR comments: a comment from an automated reviewer that matches land.cleanReviewCommentPattern and names the current head SHA counts as a head-current review. It only ever adds evidence, never overriding a formal review, an open thread, or a red check. The SHA must be the current head, so a land-authored fix push forces a fresh clean comment before merge. Set land.cleanReviewCommentPattern to an empty string to turn the comment path off.

All keys ship with seeded defaults: flowctl config get land.reviewSignal returns "silence" on a fresh repo, not null.

KeyDefaultMeaning
land.releasetrueRun the release-follow step after merge (also no-ops when no release docs are discovered)
land.patienceMinutes30Reviewer patience window, anchored to the last push
land.patienceMinutesAfterReviewnullOpt-in: under silence, once a head-current automated review exists with zero unresolved threads, the wait is this many minutes measured from that review event instead of from the last push (it replaces the push window, so a late review can lengthen the wait). A fix push reverts to the push anchor until the bot re-reviews; approve/login signals and the merge command are untouched; the report names the binding anchor=. Any positive integer activates; unset, null, 0 are off (a hand-edited non-integer value reads as off rather than failing the tick)
land.reviewSignalsilenceMerge signal: silence, approve, or a GitHub login
land.automatedReviewers""CSV allowlist of reviewer logins counted as automated, supplementing the [bot]-suffix rule
land.reviewTrigger""One-shot comment posted to summon a reviewer bot on a draft PR with zero automated reviews (bots don’t auto-review drafts, and the build loop’s PRs are born draft). Recommended opt-in text scopes the bot so its prose findings stay FYI: "@codex review - focus on integration effects, the diff as narrative, and cross-task regressions. Spec/doc-prose findings are welcome as FYI, not merge-gating." Empty means never post
land.cleanReviewCommentPatternbuilt-in EREUnder silence, a regex matching a bot’s clean-review comment (default matches Codex’s “no issues … Reviewed commit <sha>”); a match that names the current head SHA counts as a head-current review. Set to an empty string "" to disable the comment path
land.ciFixBudget3CI-fix attempts per PR before the durable needs-human label
land.requestReviewers""Opt-in (4.2.0): csv of GitHub logins and/or org/team slugs and/or the literal codeowners. When a human review is the only missing merge input (CI green, zero unresolved threads; approve/login signal unsatisfied without CHANGES_REQUESTED, or silence satisfied with reviewDecision == REVIEW_REQUIRED), land flips a draft PR to ready and requests the list minus the PR author, exactly once per PR per head SHA (ledger-recorded, atomically claimed). codeowners rides the ready flip and GitHub resolves the owners. Never gates a merge; land.reviewSignal does. The evidence line reports reviewers=<requested|would-request|already:<sha8>|skipped:<reason>|failed:<reason>|off>
land.mergeVerdictCommand""Opt-in (3.31.0) fail-closed repo merge-verdict gate: a shell command run once per merge attempt after every other gate passes. Exit 0 merges; any non-zero (including missing or timeout) blocks with NEEDS_HUMAN. Context arrives via env only (FLOW_HEAD_SHA, FLOW_BASE_REF, FLOW_PR_NUMBER, FLOW_SPEC_ID)
Terminal window
flowctl config set land.reviewSignal approve
flowctl config set tracker.perEvent.land.merged push # opt-in tracker touchpoint
  • A separate skill: projects that never run it carry zero risk and zero new surface.
  • Touches only PRs the build loop authored, with both authorship signals required before any mutation.
  • The release step has its own off-switch (land.release), independent of the rest of the loop.
  • Refuses to nest under the Ralph harness (FLOW_RALPH / REVIEW_RECEIPT_PATH), same as pilot; refuses a dirty working tree at tick start; restores your branch and asserts a clean tree between PRs and at tick end.

With land in place the three loops close the lifecycle end to end:

board / flowctl pilot land
bless a spec ──▶ plan → review → work ──▶ CI → reviews → merge → release
(your judgment) (draft PR out) (shipped)

Run both loops concurrently for the full assembly line, one instance each in separate clones since both mutate the working tree. See Running the full pipeline.