Uh oh!
There was an error while loading. Please reload this page.
fix(fr-gate): an ancestry-only promotion has nothing to gate - #109
Conversation
The no-items fallback gated the promotion PR itself, which is unsatisfiable BY CONSTRUCTION: set-pr-status parks that PR in 'Code review', and it can only leave that column by merging -- which this gate blocks. data-ingestors deadlocked on exactly this in today's prod hop. It happened because the range's only new commit was the train's own mirror merge, whose content master already held (a manual promotion had carried it there first): 1 commit ahead, 0 files changed. Both associated PRs were correctly filtered as train plumbing, leaving no cargo -- so the fallback fired on a promotion that ships nothing. Now distinguished by whether the range changes any file: 0 files -> ancestry-only, nothing shipped, nothing to gate -> pass >0 files -> real content we could not attribute -> gate the promotion PR, i.e. fail closed and force a human look An unset file count defaults to the fail-closed branch, so a failure to compute it can never turn into a pass.
LukasWodka
commented
Jul 30, 2026
bugbot run |
LukasWodka
commented
Jul 30, 2026
👋 Heads-up — Code review queue is at 35 / 30 Above the WIP limit. The team convention is to review existing PRs before opening new work. Open PRs currently in Code review (oldest first):
Pull from review before opening new work. (This is a nudge from the kanban WIP check, not a block.) |
Uh oh!
There was an error while loading. Please reload this page.
The early exit sat ~150 lines above the UNATTRIB fail-closed check, so a
tree-identical range containing a commit we could not verify (e.g.
commits/{sha}/pulls erroring three times) would have passed the gate --
and my PR body wrongly claimed that check was untouched.
0 files changed means 'nothing shipped' only if every commit was
successfully accounted for, so the ancestry pass now requires UNATTRIB to
be empty as well.LukasWodka
commented
Jul 30, 2026
Valid, and a real fail-open in my own fix — fixed. The early exit sat ~150 lines above the The premise needed tightening too: 0 files changed means "nothing shipped" only if every commit was successfully accounted for. An unverifiable commit means we don't actually know what the range contains, so the file count proves nothing. The ancestry pass now requires Re-tested every path:
|
LukasWodka
commented
Jul 30, 2026
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d5c8518. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
The emptiness check used a two-dot git diff, which counts files the BASE moved on alone -- so it measured 'how do the two tips differ', not 'what did this promotion contribute'. On data-ingestors that reports 6 files (master's own commits that staging lacks) where the correct three-dot answer is 0, so the ancestry-only branch would never have been taken and the deadlock this PR exists to fix would have survived. I had verified the premise with the compare API (three-dot) but implemented local two-dot git -- validating with one method and shipping another. Now uses the compare API directly, so the check and its verification are the same operation. The API also avoids local three-dot's silent merge-base pick: this repo's history has multiple merge bases after the mirror reconciliations. Any failure to count yields -1, which takes the fail-closed branch.
LukasWodka
commented
Jul 30, 2026
Valid, and worse than the description suggests — my fix would not have fixed its own motivating case. Fixed. I used two-dot Those 6 are all base-only: The underlying mistake is worth naming: I verified the premise with the compare API (three-dot) and then implemented local two-dot git. Validating with one operation and shipping another is how a fix passes review while not working. It now calls the compare API directly, so the check and the thing I verified are literally the same operation. That also sidesteps a second trap: local Any failure to count now yields Re-verified against the live repos:
bugbot run |

Fixes the deadlock that kept data-ingestors out of today's prod hop.
The deadlock
The no-items fallback did this:
which is unsatisfiable by construction:
set-pr-statusparks a promotion PR inCode review, and the only way out of that column is merging — which this gate is what blocks. So the train could never merge that repo, and the failure read as a legitimate governance block (❌ #423 — Status='Code review', required 'Ready for prod') rather than as a bug.Why the item set was legitimately empty
data-ingestors'
master...stagingrange contained exactly one commit — the mirror merge from the staging hop — and 0 changed files, because a manual promotion (data-ingestors#421) had already carried that content tomaster. Both PRs associated with that commit were correctly filtered as train plumbing (#415 by itsrelease-train/*head, #423 by still being open). So there was no cargo, because the promotion genuinely ships nothing.The fix
Distinguish the two causes of an empty item set by whether the range touches any file:
An unset file count falls into the fail-closed branch, so failing to compute it can never become a pass. The existing unattributable-commit check is untouched, so a direct push into the range still fails closed on its own terms.
Verification
actionlint+shellcheckclean at CI-pinned versions. All four decision paths exercised locally:Confirmed against the live repo that data-ingestors' range really is
ahead=1, files_changed=0— I checked that the "nothing to gate" premise holds empirically before relying on it, rather than assuming an ancestry-only range implies no content.Note
Medium Risk
Changes promotion merge gating logic; the early-pass path is narrow (0 files + empty UNATTRIB) but a wrong file count could theoretically allow or block merges incorrectly.
Overview
Fixes a release-train deadlock when the FR gate found no attributable PRs and always fell back to gating the promotion PR itself—an unsatisfiable loop because that PR sits in
Code reviewuntil merge.The Discover items step now counts how many files the promotion range actually contributes using the GitHub compare API with three-dot semantics (
BASE...HEAD), with retries and fail-closed behavior if the count cannot be obtained. That count is exposed aschanged_files.The Verify step uses that signal when the attributed PR list is empty: if
changed_filesis 0 and there are no unattributable commits, the workflow passes with a notice (ancestry-only mirror merge, no new content to review). If files changed or attribution is incomplete, it still gates the promotion PR and fails closed as before.Reviewed by Cursor Bugbot for commit ce45d7f. Bugbot is set up for automated code reviews on this repo. Configure here.