Skip to content

fix: supersede completed backport approval check runs instead of updating them - #423

Merged
dsanders11 merged 2 commits into
mainfrom
fix/backport-approval-completed-check-supersede
Aug 25, 2026
Merged

fix: supersede completed backport approval check runs instead of updating them#423
dsanders11 merged 2 commits into
mainfrom
fix/backport-approval-completed-check-supersede

Conversation

@claude

@claude claude Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Requested by John Kleinschmidt · Slack thread

Follow-up to #422. Makes the "Backport Approval Enforcement" check create a fresh queued run when the existing run has already completed, instead of attempting an in-place reset that the Checks API silently ignores.

Before

A completed "Backport Approval Not Required" run could not be un-greened. When backport/requested 🗳 landed after the run had concluded, the labeled-event reset from #422 called checks.update({ status: 'queued' }) on the completed run — but the Checks API treats a completed run as terminal, so the PATCH only rewrote the output text while keeping status: completed / conclusion: success. The result was a green check whose output read "Needs Backport Approval", exactly what happened on electron/electron#53035 (check run 96268219963), leaving the PR mergeable without backport approval.

This window is routine for trop-created backports: trop opens the PR first and adds its labels (backport, target branch, backport/requested 🗳, semver) in a separate API call a few seconds later, so the opened-event evaluation from live labels (the #422 fix) genuinely sees no labels and concludes success before the labels arrive.

After

A late approval-request label supersedes the completed run: queueBackportApprovalCheck still resets a run in place while it is queued/in_progress, but when the existing run is completed it creates a fresh queued run — branch protection consults only the latest run per name, so the stale green run no longer gates anything. Additionally, the opened-event evaluation no longer concludes "not required" for any declared backport — a PR whose body carries a Backport of #N declaration, regardless of author; the check stays queued until the labeled events that follow trop's label writes settle the verdict, closing the create-then-label window at the source (and failing safe — pending — if the labels never arrive).

How

  • src/utils/checks-util.ts (queueBackportApprovalCheck): keep the fix: evaluate backport approval from live labels to prevent check race #422 dedupe/update path only for runs with status !== 'completed'; otherwise fall through to checks.create with status: 'queued'.
  • src/index.ts: in the not-approved/not-requested branch, action === 'opened' no longer concludes for declared backports (getPRNumbersFromPRBody(pr).length > 0). The general rule: a declared backport's labels are always written by trop itself — backportImpl labels trop-created backports in a separate API call after opening them, and updateManualBackport labels manually-opened backports with at least the base-ref label — so every declared backport is guaranteed a subsequent labeled event, and opened can safely leave the check pending for any author and any webhook timing. PRs without a Backport of #N declaration (fast-track / roller PRs targeting release branches) receive this check too but get no guaranteed labeled event and never re-evaluate otherwise, so opened must still conclude "Backport Approval Not Required" for them — always-queue-on-opened would hang those checks forever. As delivery-reordering protection, a late opened does not re-queue a run that an earlier-processed labeled delivery already concluded from the same live labels — superseding it would leave a queued run no follow-up event ever completes.
  • Tests: spec/checks-util.spec.ts covers queued/in_progress → checks.update and completed → checks.create (no update); spec/index.spec.ts covers the any-author pending-on-opened rule, the undeclared-PR conclude-on-opened rule, the late-opened no-re-queue rule, and a labeled-event regression test mirroring #53035 (stale completed-success run + backport/requested 🗳 → pending state re-asserted).

Note on the audit of other writes to this check: updateBackportApprovalCheck and the other update*Check helpers always PATCH with a conclusion, i.e. completed → completed transitions, which the API does apply (the validity check has always flipped success/failure this way); only transitions out of the completed state are silently ignored, and the enforcement check's only such transition is the queued reset fixed here. Firsthand evidence from #53035: the queued-reset PATCH succeeded but left status/conclusion/completed_at frozen while applying the new output.

Refs: electron/electron#53035, electron/electron#52973, #422

…ting them

The Checks API treats a completed check run as terminal: a PATCH asking to
move it back to 'queued' succeeds and applies the output, but silently keeps
the old status and conclusion. The dedupe path added in #422 therefore could
not un-green a run that had already concluded success - the labeled-event
reset only rewrote the output text, leaving a green check whose output read
'Needs Backport Approval' (observed on electron/electron#53035).

- queueBackportApprovalCheck now only updates an existing run in place while
  it is still pending; a completed run is superseded by a fresh queued run,
  which branch protection consults as the latest run per name.
- The opened-event evaluation no longer concludes 'not required' for
  backport PRs authored by trop itself: trop adds labels in a separate API
  call shortly after creating the PR, so the live labels are still empty in
  that window. The check stays queued until the labeled events that follow
  trop's own label writes settle the verdict.
@jkleinsc
jkleinsc marked this pull request as ready for review August 20, 2026 21:30
@jkleinsc
jkleinsc requested a review from a team as a code owner August 20, 2026 21:30
@dsanders11
dsanders11 self-requested a review August 20, 2026 22:33
…kports

The opened-event guard special-cased backports authored by trop's bot
user, but the window it protects against is not author-specific: a
declared backport's labels are always written by trop itself -
backportImpl labels trop-created backports in a separate API call after
opening them, and updateManualBackport labels manually-opened backports
with at least the base-ref label - so labels can land after the opened
delivery for any author, and webhook deliveries can be delayed or
reordered besides.

Gate the guard on the author-agnostic property that actually matters:
whether the PR body declares 'Backport of #N'. Every declared backport
is guaranteed at least one trop-written label and therefore a labeled
event that settles the verdict, so opened can safely leave the check
pending. PRs without a declaration (e.g. fast-track PRs targeting
release branches) get no guaranteed labeled event and would hang queued
forever, so opened still concludes 'not required' for them.

Also skip the re-queue when the check has already concluded: a labeled
delivery processed before a late opened delivery has already settled
the verdict from the same live labels, and superseding that concluded
run would leave a queued check no follow-up event ever completes.

@dsanders11 dsanders11 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have some concerns, doing a more thorough review.

@dsanders11
dsanders11 dismissed their stale review August 25, 2026 22:23

Fine for now

@dsanders11
dsanders11 merged commit 0bd0cfc into main Aug 25, 2026
7 checks passed
@dsanders11
dsanders11 deleted the fix/backport-approval-completed-check-supersede branch August 25, 2026 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants