Land Stack
Take a finished orchestrate-implementation run from "every ticket has a
draft PR" to "every PR is merged, every issue is closed, every worktree is
gone". Landing is one-way, so each step proves its precondition before acting.
Contract
- Land oldest first. A PR merges only when it targets the target branch, its
head equals the ledger's accepted head, and its checks are green.
- Human approval is optional. Without
--require-approvals, the loop gate and
green checks are the review. Say so in the report.
- Stop at the first PR that cannot merge. Merged tickets stay merged; the run
pauses with the ledger and evidence, and
resume continues from the next.
- Archive a ticket environment only after its merge is confirmed on the remote.
- Work from the ledger: read
run.json, write every transition.
References
Follow ../review-fix-loop/references/bb-workers.md for pausing, notification,
auto-resume, and its GitHub section: prefer the gh-axi skill over raw gh,
reading current syntax from the CLI. The gh commands below define what must
be true, not which binary runs it. Use
../orchestrate-implementation/references/pr-stack.md to bring each child onto
the target branch. The ledger schema is
../orchestrate-implementation/references/ledger.md; landing fills
tickets.<id>.landing and the top-level landing object.
Prepare
- Read the run ledger: the attached path, else
$BB_THREAD_STORAGE/orchestrate-implementation/run.json. Require
state: finished, or landing already present for resume. Reconcile
every PR with gh pr view before the first transition.
- Pick the merge method:
--method when given, else the first allowed of
merge, squash, rebase from
gh api repos/{owner}/{repo} --jq '[.allow_merge_commit,.allow_squash_merge,.allow_rebase_merge]'.
A merge commit keeps children retargetable; squash and rebase need the
rebase path for every child.
- Verify authenticated
gh, push access, and the tracker workflow in
docs/agents/issue-tracker.md. Record landing in the ledger.
Land one ticket
For the oldest unmerged ticket in order:
Bring it onto the target with the parent-merge procedure in pr-stack.md
(direct retarget or rebase). Its PR must now target <target>. The first
ticket of a run already does, so it needs neither.
Gate:
gh pr view "$URL" --json state,isDraft,baseRefName,headRefOid,mergeable,mergeStateStatus,reviewDecision
gh pr checks "$URL" --watch --fail-fast
Require baseRefName equal to the target, headRefOid equal to
accepted_head, mergeable: MERGEABLE, and no failing check
outside the run's ci_baseline. A check that already failed on the target
branch when the run started is inherited, not caused: report it and merge.
A run whose ledger has no ci_baseline measures one now against the target
branch rather than treating a red repository as a blocked stack. With
--require-approvals, also require reviewDecision: APPROVED and zero
unresolved review threads:
gh api graphql -F o=<owner> -F r=<repo> -F n=<number> -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){pullRequest(number:$n){reviewThreads(first:100){nodes{isResolved}}}}}'
Unresolved threads without --require-approvals are findings: run
/review-fix-loop <target> <ticket> --from-pr <url>, refresh the PR body
with gh pr edit --body-file, then gate again.
Merge:
gh pr ready "$URL"
gh pr merge "$URL" --<method> --match-head-commit "$ACCEPTED_HEAD" --delete-branch
gh pr view "$URL" --json state,mergeCommit,mergedAt
state must be MERGED. Record merge_commit and merged_at.
bb environment pull-request merge takes only --method, so it cannot
assert the head or delete the branch; keep gh pr merge here.
Close the ticket through the tracker workflow with a comment that links the
PR. GitHub closes issues named by Fixes #n on its own, so check first:
gh issue view <n> --json state
gh issue close <n> --reason completed --comment "Landed in $URL"
Clean up unless --keep-environments:
bb environment archive-threads "$ENV"
bb environment show "$ENV" --json # .status becomes destroying, then destroyed
Archiving every live thread of a managed worktree destroys that worktree and
its local branch. Confirm both the environment status and the missing
worktree path before recording environment_state: archived.
Write tickets.<id>.state: merged, then continue with the next ticket. Its
parent is now merged, so step 1 retargets or rebases it.
Pause
A failing check, a conflict, a head that moved, a non-mergeable PR, or a
review decision that blocks pauses the landing. Nothing merged is undone.
resume reconciles the ledger and starts at the first unmerged ticket.
Finish
Report each ticket with its PR, merge commit, issue state, and environment
state, plus the merge method and whether approvals were required. Set
state: landed in the ledger.
1---2name: land-stack3description: Merge a finished ticket pull request stack oldest first, close each ticket, and archive its environment. Use when an orchestrator finishes a run with --land, when the user asks to land, merge, or ship a finished stack, or to clean up worktrees after merging.4---56# Land Stack78Take a finished `orchestrate-implementation` run from "every ticket has a9draft PR" to "every PR is merged, every issue is closed, every worktree is10gone". Landing is one-way, so each step proves its precondition before acting.1112## Contract1314- Land oldest first. A PR merges only when it targets the target branch, its15 head equals the ledger's accepted head, and its checks are green.16- Human approval is optional. Without `--require-approvals`, the loop gate and17 green checks are the review. Say so in the report.18- Stop at the first PR that cannot merge. Merged tickets stay merged; the run19 pauses with the ledger and evidence, and `resume` continues from the next.20- Archive a ticket environment only after its merge is confirmed on the remote.21- Work from the ledger: read `run.json`, write every transition.2223## References2425Follow `../review-fix-loop/references/bb-workers.md` for pausing, notification,26auto-resume, and its GitHub section: prefer the `gh-axi` skill over raw `gh`,27reading current syntax from the CLI. The `gh` commands below define what must28be true, not which binary runs it. Use29`../orchestrate-implementation/references/pr-stack.md` to bring each child onto30the target branch. The ledger schema is31`../orchestrate-implementation/references/ledger.md`; landing fills32`tickets.<id>.landing` and the top-level `landing` object.3334## Prepare35361. Read the run ledger: the attached path, else37 `$BB_THREAD_STORAGE/orchestrate-implementation/run.json`. Require38 `state: finished`, or `landing` already present for `resume`. Reconcile39 every PR with `gh pr view` before the first transition.402. Pick the merge method: `--method` when given, else the first allowed of41 merge, squash, rebase from42 `gh api repos/{owner}/{repo} --jq '[.allow_merge_commit,.allow_squash_merge,.allow_rebase_merge]'`.43 A merge commit keeps children retargetable; squash and rebase need the44 rebase path for every child.453. Verify authenticated `gh`, push access, and the tracker workflow in46 `docs/agents/issue-tracker.md`. Record `landing` in the ledger.4748## Land one ticket4950For the oldest unmerged ticket in `order`:51521. Bring it onto the target with the parent-merge procedure in `pr-stack.md`53 (direct retarget or rebase). Its PR must now target `<target>`. The first54 ticket of a run already does, so it needs neither.552. Gate:5657 ```bash58 gh pr view "$URL" --json state,isDraft,baseRefName,headRefOid,mergeable,mergeStateStatus,reviewDecision59 gh pr checks "$URL" --watch --fail-fast60 ```6162 Require `baseRefName` equal to the target, `headRefOid` equal to63 `accepted_head`, `mergeable: MERGEABLE`, and no failing check64 outside the run's `ci_baseline`. A check that already failed on the target65 branch when the run started is inherited, not caused: report it and merge.66 A run whose ledger has no `ci_baseline` measures one now against the target67 branch rather than treating a red repository as a blocked stack. With68 `--require-approvals`, also require `reviewDecision: APPROVED` and zero69 unresolved review threads:7071 ```bash72 gh api graphql -F o=<owner> -F r=<repo> -F n=<number> -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){pullRequest(number:$n){reviewThreads(first:100){nodes{isResolved}}}}}'73 ```7475 Unresolved threads without `--require-approvals` are findings: run76 `/review-fix-loop <target> <ticket> --from-pr <url>`, refresh the PR body77 with `gh pr edit --body-file`, then gate again.783. Merge:7980 ```bash81 gh pr ready "$URL"82 gh pr merge "$URL" --<method> --match-head-commit "$ACCEPTED_HEAD" --delete-branch83 gh pr view "$URL" --json state,mergeCommit,mergedAt84 ```8586 `state` must be `MERGED`. Record `merge_commit` and `merged_at`.87 `bb environment pull-request merge` takes only `--method`, so it cannot88 assert the head or delete the branch; keep `gh pr merge` here.894. Close the ticket through the tracker workflow with a comment that links the90 PR. GitHub closes issues named by `Fixes #n` on its own, so check first:9192 ```bash93 gh issue view <n> --json state94 gh issue close <n> --reason completed --comment "Landed in $URL"95 ```96975. Clean up unless `--keep-environments`:9899 ```bash100 bb environment archive-threads "$ENV"101 bb environment show "$ENV" --json # .status becomes destroying, then destroyed102 ```103104 Archiving every live thread of a managed worktree destroys that worktree and105 its local branch. Confirm both the environment status and the missing106 worktree path before recording `environment_state: archived`.1076. Write `tickets.<id>.state: merged`, then continue with the next ticket. Its108 parent is now merged, so step 1 retargets or rebases it.109110## Pause111112A failing check, a conflict, a head that moved, a non-mergeable PR, or a113review decision that blocks pauses the landing. Nothing merged is undone.114`resume` reconciles the ledger and starts at the first unmerged ticket.115116## Finish117118Report each ticket with its PR, merge commit, issue state, and environment119state, plus the merge method and whether approvals were required. Set120`state: landed` in the ledger.