The loop
Merge order
/pitwall:merge-orderOrder the open PRs by stack and readiness; humans merge.
What it does
/pitwall:merge-order tells you which of your open PRs to merge first, and why the others must wait. It sorts stacked PRs parent before child, gives each PR a level, and fixes what it safely can: it retargets a child whose parent merged, keeps children in draft, and resolves conflicts in the plan’s log and rows. It never merges; you press the button.
Use it when
- several agent PRs are open, some stacked on others
- you just merged one and want to know what is next
- the other PRs started to conflict in the plan after a merge
Try it
/pitwall:merge-order all your open PRs
/pitwall:merge-order 101 only the stack that holds #101
The levels
| Level | Means |
|---|---|
| 🟢 | can merge now: on the base, checks green, nothing pending, a passing verdict at its current code |
| 🟡 | can merge, but something is pending: an unticked item, or no verdict yet at its current code. You decide whether it can wait until after merge |
| ⏸ | waiting for the PR it is stacked on |
| 🔴 | blocked: draft, conflict, failing check, changes requested, or the verdict failed |
| ⏳ | a check is still running |
What you’ll see
The example comes from a made-up repo, acme/shop.
retarget: #59 → main (#58 merged)
Stack A — 3 PR (main ← #59 ← #60 ← #61)
| # | PR | Level | Pending |
|---|---------------------------|-------|----------------------|
| 1 | #59 price filter | 🟢 | |
| 2 | #60 filter chips | ⏸ | waiting for #59 |
| 3 | #61 saved filters | ⏸ | waiting for #60 |
Standalone
| # | PR | Level | Pending |
|---|---------------------------|-------|----------------------|
| 1 | #62 lint batch 3 | 🟡 | needs re-verify |
Next: merge #59 → then press "Delete branch" in that PR (GitHub moves #60 to main itself)
When you merge
- Use Create a merge commit, never squash or rebase a PR in a stack: the child would see its parent’s commits twice and conflict.
- Merge one at a time, in order, and only PRs whose base is the profile’s base.
- Press Delete branch right after, so GitHub moves the child onto the base. Forgot? Run
/pitwall:merge-orderagain and it retargets. - The other agent PRs usually conflict in the plan’s log after a merge. Run it again, or let the next lap fix them.
The playbook the agent followsThe full spec for /pitwall:merge-order. This page sums it up; when the two differ, the playbook wins.
/pitwall:merge-order — which one to merge first
The merge order lives here and only here — PR bodies / round reports / messages to the user never explain the merge order themselves, they write only “merge order: /pitwall:merge-order”
Arguments: none = all my open PRs · <PR number> = only the stack containing that PR · Claude does not merge itself (forbidden in the profile) — a human presses
1. Pull data
gh pr list --author "@me" --state open --limit 100 \
--json number,title,url,headRefName,baseRefName,isDraft,mergeable,mergeStateStatus,reviewDecision,statusCheckRollup,body
Verdicts: state verified (path in the profile; multi-machine → receive first; none → no PR has a verdict) · git fetch origin -q → the current patch-id of every PR (/pitwall:gauge “Independent verifier”)
A “destination” base = base in the profile + branches the profile forbids pushing to · any other base = another PR’s branch (stack)
A PR whose head is a destination (e.g. <base> → release, shipping to prod) = a release PR: put it in a separate “release” table at the end of the report, never walk on to children from that head (otherwise the base stack shows up twice)
2. Retarget before sorting
When the repo has “Automatically delete head branches” off (check repo settings) → after a parent PR merges, GitHub does not move the child PR’s base (it only moves it when the parent branch is deleted); the child still points at the already-merged parent branch — if merged at that point, the work lands in the parent branch, not base
Every PR whose base is not a destination: gh pr list --state merged --head <base> --json number,baseRefName --limit 1
- Found (parent merged) →
gh pr edit <child> --base <parent's baseRefName>→ base is now a destination →gh pr ready <child>and note it in the report (“retarget #child →because #parent merged”) - Not found and no open PR with head = that base → 🔴 “base
<branch>has no PR” (closed and dropped?) never guess, ask the user
After retargeting, pull step 1’s data again (mergeable is recomputed slowly → treat UNKNOWN as ⏳, not 🔴)
A retarget starts no CI run: GitHub’s pull_request trigger leaves out edited by default, so the checks still shown are the run against the old parent branch (step 4 marks them ⏳) · an agent/* head whose workflow has no base-change trigger (the init line in step 4 of playbooks/init.md) → push an empty commit in the step 2b worktree, git commit --allow-empty with the profile’s commit pattern (docs / chore type, e.g. chore(ci): run checks on the new base), never --no-verify: it starts a run on the new base and keeps the patch-id, so the verdict survives · anyone else’s head → say in the report that its checks need a new run
Child PRs in a stack are always Draft
GitHub cannot merge a Draft PR — this stops a child being pressed before its parent (it has happened: a 3-PR stack was merged bottom-up, the children’s work landed in the parent branch, not base, and had to be opened again)
- Every PR whose base is not a destination and is not Draft yet →
gh pr ready <n> --undo, note it in the report - A PR whose base = destination but is still Draft and belongs to the agent (
agent/*) →gh pr ready <n>
2b. Resolve docs conflicts yourself
The conflict that recurs every time is the logs every PR appends to at the same place (the plan’s progress log, a docs changelog, the open-questions table, the status board) — append-only logs collide; knowledge pages rarely do · GitHub does not read merge=union, so it has to be fixed in the branch
Per PR with mergeable = CONFLICTING, head starting with agent/ (agent branches only — never touch anyone else’s, including the user’s own) and base = destination:
- Conflicting files (read-only):
git fetch -q origin && git merge-tree --write-tree --name-only --no-messages origin/<base> origin/<head> | tail -n +2 - Any file outside the profile’s “resolve conflicts yourself” → do not touch 🔴
conflict in code: <file>for a human - Separate worktree (does not disturb the main checkout / dev server): the local branch has commits not yet pushed (
git rev-list origin/<head>..<head>not empty) → stop 🔴 · otherwisegit worktree add -B <head> <scratchpad>/wt-merge origin/<head>(this worktree uses a realnode_modules— run the profile’s install command the first time, in case a pre-push hook builds) git merge --no-edit origin/<base>→node <pitwall dir>/scripts/resolve-docs-conflict.mjs <conflicting files>— every hunk keeps both sides, base’s first, then the branch’s (what humans pick on the web for append-only logs), a line both sides hold once, and a task row both sides hold once, ticked if either side ticked · exit 1 = it found what union cannot decide and left that file as it was → fix it by this table, orgit merge --abort→ 🔴:
| The script reports | Fix |
|---|---|
| Two rows with the same id, e.g. an open-question number | The branch’s row → next free number, and fix the #<number> the branch refers to in files the branch changed (git diff --name-only origin/<base>...origin/<head>) |
| One status board line changed two ways | Merge into one line that states both things |
| diff3 markers / unsure the merge is right | git merge --abort → 🔴 conflict needs a human: <file:line> |
- Check:
git grep -n -e '^<<<<<<<' -e '^>>>>>>>' -e '^=======$' -- <files>is empty ·git diff --checkpasses · referenced paths / task numbers exist (docs of/pitwall:gauge) git commit --no-edit(messageMerge remote-tracking branch 'origin/<base>' into <head>— looks the same as the web button) · the repo’s commit-msg hook refuses that message → commit with the profile’s commit pattern instead (docs type, e.g.chore(plan): merge <base> into <head>), never--no-verify→git push -u origin <head>(hooks run) →git worktree remove <scratchpad>/wt-merge- The verdict survives: the patch-id leaves out these files (
/pitwall:gauge“Independent verifier”) — no re-verify for a docs-only resolution - Note in the report:
resolved conflict: #n (<file>) — kept both sides·mergeableis recomputed slowly → ⏳, then running/pitwall:merge-orderagain will show 🟢
Child PRs in a stack need nothing — the conflict shows up when it is retargeted to base, and is fixed in that round
3. Sort
Parent → child: start from the PR whose base = destination, then follow headRefName → the PR whose baseRefName equals it (depth-first, multiple children sorted by PR number) · separate stacks = one table per stack · standalone PRs (no child, no parent) go together in a final table
4. Levels (per PR, in order)
| Level | When |
|---|---|
| 🔴 blocked | isDraft · mergeable = CONFLICTING · any check in statusCheckRollup is FAILURE / ERROR (skip checks in the profile’s “ignored checks”) · reviewDecision = CHANGES_REQUESTED · the latest verdict at the current patch-id is FAIL (verdict FAIL) |
| ⏸ waiting for base | base is still an open PR (it is Draft — cannot be merged until its turn) |
| 🟡 can merge, but pending | base = destination, not blocked, but the body’s “Not done yet (before merge)” section still has - [ ] (count that section only · the “merge in stack order” item does not count) or the title starts with [PARTIAL] · or no passing verdict (item: "verdict", VERIFIED / PARTIAL) of this PR (pr / branch) at its current patch-id: older patch-ids only → needs re-verify, none at all → no verifier verdict (PRs the agent did not open stay here by design — the loop never verifies them) |
| 🟢 can merge now | base = destination, not blocked, no - [ ] pending, a passing verdict at the PR’s current patch-id |
| ⏳ | a check is still running (PENDING / IN_PROGRESS — not counting ignored checks) · mergeable = UNKNOWN · the checks predate the PR’s last base change: gh api repos/<owner>/<repo>/issues/<n>/timeline --paginate --jq '[.[]|select(.event=="base_ref_changed")|.created_at]|max' is later than the newest startedAt in statusCheckRollup → CI not run on the new base (a retarget reruns nothing, step 2) |
Release PRs (head = destination, the separate release table) need no verdict — the verdict clauses above skip them, as /pitwall:gauge “Record results + tick the PR yourself” item 2 does
Child PRs in a stack: a workflow limited to PRs into base shows no checks yet; one that runs on every PR shows checks against the parent branch · either way the retarget starts no run, so after it they count as ⏳ until a run on the new base exists (step 2)
🟡 does not mean do not merge — the user decides whether the pending items can wait until after merge (e.g. testing on dev)
5. Report (short, in the profile’s user language)
retarget: #102 → <base> (#101 merged) ← present when step 2 was done
Stack A — 12 PR (<base> ← #101 ← … ← #112)
| # | PR | Level | Pending |
|---|---|---|---|
| 1 | #101 currency label | 🟡 | C purchase flow |
| 2 | #102 carousel | ⏸ | waiting for #101 |
…
Next: merge #101 → then press "Delete branch" in that PR (GitHub moves #102 to base itself) · forgot to delete → run /pitwall:merge-order again and it retargets
- A long run of consecutive ⏸ PRs → collapse into one row
#103–#112 ⏸ waiting for the previous oneunless one of them is 🔴 (always shown separately) - “Pending” =
- [ ]items trimmed to ≤ 6 words / item, more than 2 items → “+N” · plusneeds re-verify/no verifier verdictwhen that is why it is 🟡
6. Merge-button rules (tell the user in one line every time there is a 🟢 / 🟡)
- The Create a merge commit button (keeps stack history intact) — never Squash / Rebase PRs in a stack: the parent’s commits get rewritten → the child sees the parent’s commits twice and conflicts
- Merge one at a time, in order · press only PRs whose base = the profile’s base (check the PR header before pressing)
- After merging, press Delete branch right away → GitHub moves the child PR to base (still Draft, and its CI does not rerun — run
/pitwall:merge-orderagain, step 2 handles both) - After each merge, the other open agent PRs usually conflict in the plan’s log and rows → run
/pitwall:merge-orderagain (2b fixes them in a minute), or leave it to the next/pitwall:lapround - After merging → the next
/pitwall:lapround syncs the task toreviewitself (or/pitwall:ticket)
Never
- Never
gh pr merge/ close a PR / delete a branch yourself — only retarget (gh pr edit --base) and merge base into anagent/*branch per step 2b - Never resolve conflicts in code / config / lockfile — report them to a human
- Never edit the body / tick checkboxes in this skill (that is the job of
/pitwall:lap+/pitwall:gauge)