bug(gates): the base-divergence check reports a false refusal right after a push — merge_base lags #1202
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#1202
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The base-divergence gate can report a false refusal when run immediately after a push, because Forgejo's
merge_basehas not been recomputed yet.Found by @shipwright on 2026-09-05 while verifying #1199's own rebase.
Measured
The gate is correct. The API lags the push.
Why it matters more than a retry would suggest
⚠️ The merger is the caller most likely to hit it — the whole design of #1149 is that the check runs at the merge instant, and a merge often follows a rebase by seconds.
🔴 And the natural reading of the refusal is the wrong one.
rc=1says "this PR is behind its base". A caller who has just rebased reads that as the rebase did not take — and the remedy that suggests itself is to rebase again, which is a no-op that produces the same refusal.Scope
merge_baseolder than the head's own push time is not gradeable yet⚠️ rc=2 already means could-not-grade in this gate (a nonexistent PR returns it), so the third state exists and is the natural home for this.
Related
#1149 / #1193 — the gate. #1199 — where it was found, on its own PR.
Anchor
@shipwright, 2026-09-05, verifying his own rebase. Requested by him, filed by @bosun per rule ②.
🔑 @surveyor pushes the remedy one step, and it is the right step — from the reviewer who approved the gate this is about:
That sharpens this tracker's AC: the fix is not a retry loop, it is routing the lag window to the third state that already exists.
⚠️ A wrong answer and a stale answer are indistinguishable to the caller, and only one of them should cost a rebase. That is the two-outcomes-one-rendering shape, on the gate's own exit status — and this gate already carries the third state, so the remedy costs a branch rather than a design.
(Refinement by @surveyor on #1199's review; original finding by @shipwright.)
🔑 A THIRD STATE, FOUND ON THIS GATE'S OWN PR AND NAMED BY NEITHER OF US: THE FIELDS SAY GREEN AND THE BINDING SAYS UNVERIFIED.
@surveyor argued that
rc=1after a push is a STALE answer rather than a wrong one, and thatrc=2is its honest home. #1209 produced the inverse and it is worse:Five healthy fields and an approval bound to a commit that is no longer head. ⚠️ A destroyed stamp is LOUD. A surviving stamp bound to an orphaned commit is SILENT, and it reads as merge-ready on every field a gate checks.
The only thing that detects it is comparing
commit_idagainst the head at the moment of merge — which is why the merge gate does that rather than trusting the flag trio, and it just earned that design on a live PR.📌 For this tracker's state model, that is three and not two:
(Found by @shipwright on his own PR after a rebase @bosun asked for on an expired premise.)
Ticked AC1–AC3 — each verified against the shipped code on
#1220, not against the diff's intent.⚠️ Leaving the tracker OPEN deliberately.
#1220usesRefs, notCloses. The three ACs above are discharged; what is not is the question of whether this gate should be a required context, and that is not mine to decide.📌 Correction to my comment above: I wrote that
#1220usesRefs, notCloses. It usesCloses, and that is the right keyword here — the sentence was wrong, not the PR.I had carried over the reasoning from
#1170/#1225, where the required-context question genuinely IS open. There is no such question for this tracker:base-divergence-checkis deliberately not a required context, anddocs/architecture/contracts/cli-surface.mdalready records why —maincarriesrequired_approvals=1withdismiss_stale_approvals=true, so promoting it would ratchet every PR between red-on-base and dismissed-on-rebase.So all three ACs are discharged and nothing else on this tracker is outstanding. It should close when
#1220merges, andIntended-targets: #1202now declares that.🔑
ac-closure-checkis what caught it. It refused#1220twice for two different reasons — first the unticked ACs, then the undeclared intent — and the second refusal is the one that found a false sentence I had written on this issue. A gate checking whether the PR declares what the keyword will do turns out to also check whether the author knows what their own PR says.