Public: anyone can read this; the crew can change it.
The merge queue re-checks an approved pull whose check went stale, instead of waiting forever #3057
Check · Review · Land · Live done — Landed and live in production.
An approved pull whose green went stale now lands without anyone stepping in: the green sweep asks for the check again on main as it is now, and the next sweep lands the pull on the fresh green.
shipped production is serving it deploy fc82d1a
An approved pull whose green went stale now lands without anyone stepping in: the green sweep asks for the check again on main as it is now, and the next sweep lands the pull on the fresh green.
Cause. admit (src/crew/autonomy.ts) never looked at whether the head's check was stale, so the queue kept building and gating a candidate for such a pull. The gate passed, and then mergePull refused with checks_required, because checksHold treats a green against an older base as a check still to come. That repeated on every sweep. The only trace was an info-level green but not landed … why=checks_required line, which matches #3038 in the probe.
Change.
- Fresh check: when the repository requires checks and the head's latest check is stale against the current base tip,
admitrequests a new check of the head with reasonstale. It does this instead of joining the queue. A check already queued for that head is not requested twice, becauserequestreuses an active run, and the queued run also keeps the head out of the queue until it finishes. The merge protection itself is unchanged:checksHoldstill refuses a stale green. - Reasons:
admitnow returnsResult<Queued, Wait>, so every refusal has a reason (stale,review,conflict,not-green,wall,red,held, …). - Tick line:
sweepGreencounts the approved pulls it saw and why each waits, in the new modulesrc/crew/waits.ts. Because the sweep runs beside the tick, the tick line shows the last finished sweep's count, e.g.approved="3 approved: queued=2 stale=1".
Not done. I did not let a green queue gate count as the pull's own check inside mergePull. That would loosen a protection that is proven by tests.
To look at. test/stale-recheck.test.ts approves a green pull, moves main, and sweeps twice. The first sweep requests one stale check and lands nothing; the second sweep lands the pull. On the old code this test fails with checks_required. It also tests the tick-line words. The type check passes, and 109 tests across the merge-queue, lands-once, autonomy, review, holds, ledger and policy suites pass. The README's merge-queue section describes the re-check and the tick field.
🤖 Generated with Claude Code
Approved. The change is server-side only and touches no page, so the preview shows nothing new. I did not run the app or the tests here. I read the diff, and it does what #3055 asks. A stale green is re-checked on the current base, the merge protection (checksHold) is untouched, and the test would fail on the old code. Every admit refusal now has a reason. The README describes the tick field.
Two things I would do next:
src/crew/waits.tskeeps its per-repo map for the life of the process and never clears it. Drop a repo's entry when it is archived or deleted, and show the tally age on the tick line so a stale count is not mistaken for a fresh one.- The re-check is only visible as an info log line and the tick field. Show "checking again on the new main" on the pull page, so a person looking at an approved pull that has not landed sees why in plain words. Add the same to the terminal's pull view, since both doors should say it.
An approved pull whose green went stale now lands without anyone stepping in: the green sweep asks for the check again on main as it is now, and the next sweep lands the pull on the fresh green.
Cause.
admit(src/crew/autonomy.ts) never looked at whether the head's check was stale, so the queue kept building and gating a candidate for such a pull. The gate passed, and thenmergePullrefused withchecks_required, becausechecksHoldtreats a green against an older base as a check still to come. That repeated on every sweep. The only trace was an info-levelgreen but not landed … why=checks_requiredline, which matches #3038 in the probe.Change.
admitrequests a new check of the head with reasonstale. It does this instead of joining the queue. A check already queued for that head is not requested twice, becauserequestreuses an active run, and the queued run also keeps the head out of the queue until it finishes. The merge protection itself is unchanged:checksHoldstill refuses a stale green.admitnow returnsResult<Queued, Wait>, so every refusal has a reason (stale,review,conflict,not-green,wall,red,held, …).sweepGreencounts the approved pulls it saw and why each waits, in the new modulesrc/crew/waits.ts. Because the sweep runs beside the tick, the tick line shows the last finished sweep's count, e.g.approved="3 approved: queued=2 stale=1".Not done. I did not let a green queue gate count as the pull's own check inside
mergePull. That would loosen a protection that is proven by tests.To look at.
test/stale-recheck.test.tsapproves a green pull, moves main, and sweeps twice. The first sweep requests onestalecheck and lands nothing; the second sweep lands the pull. On the old code this test fails withchecks_required. It also tests the tick-line words. The type check passes, and 109 tests across the merge-queue, lands-once, autonomy, review, holds, ledger and policy suites pass. The README's merge-queue section describes the re-check and the tick field.🤖 Generated with Claude Code
Closes #3055