From db1acdf1ce6bd5c31449a8ed1cc2c35168cecda0 Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:35:53 +0200 Subject: [PATCH] fix(ci): drain the merge queue oldest-first, not newest-first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `gh pr list` returns newest-first, and the sweep merges the first eligible PR then stops. So the newest green PR wins every sweep, and an older one can wait indefinitely. Observed in maonakamoto/fleetcrown on 2026-08-06: two consecutive sweeps merged the two newest PRs while three older green ones sat untouched and were never even evaluated. With several agent sessions opening PRs continuously that is starvation — and it starves the worst candidate, since the longest-waiting PR is the one whose checks were proven against the most now-stale base. PR numbers increase monotonically with creation, so sorting ascending is FIFO. The ordering was never a decision, just whatever gh happened to return. Fixed upstream first (fleetcrown #182) and verified in production there: the sweep immediately after it landed correctly took the oldest open PR rather than the newest. This is that one-line change, applied to the fleet. Co-Authored-By: Claude Opus 5 --- scripts/ci/auto-merge-sweep.sh | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/ci/auto-merge-sweep.sh b/scripts/ci/auto-merge-sweep.sh index a4398148..5b7cb79e 100644 --- a/scripts/ci/auto-merge-sweep.sh +++ b/scripts/ci/auto-merge-sweep.sh @@ -20,8 +20,8 @@ # a red or pending PR simply waits, and a draft waits forever. To hold a ready # PR back, mark it a draft or add one of the hold labels below. # -# ONE PR PER SWEEP, AND ONLY ONTO A GREEN BASE -# -------------------------------------------- +# ONE PR PER SWEEP, OLDEST FIRST, AND ONLY ONTO A GREEN BASE +# ---------------------------------------------------------- # A PR's checks prove *that PR against the base it branched from* — not against # the other PRs sitting next to it. Merging a batch in one pass would put a # combination onto the base that nothing ever built. So this script merges at @@ -98,7 +98,16 @@ fi merged_any=0 -for number in $(printf '%s' "$prs_json" | jq -r '.[].number'); do +# OLDEST FIRST. `gh pr list` returns newest-first, and this loop merges the +# first eligible PR and stops — so the newest green PR wins every sweep and an +# older one can wait indefinitely. Observed in maonakamoto/fleetcrown on +# 2026-08-06: two consecutive sweeps merged the two newest PRs while three +# older green ones were never even evaluated. With several agent sessions +# opening PRs continuously, "newest wins" is starvation, and it starves the PR +# whose checks were proven against the most now-stale base. +# +# PR numbers increase monotonically with creation, so sorting ascending is FIFO. +for number in $(printf '%s' "$prs_json" | jq -r 'sort_by(.number) | .[].number'); do pr=$(printf '%s' "$prs_json" | jq -c --argjson n "$number" '.[] | select(.number == $n)') title=$(printf '%s' "$pr" | jq -r '.title')