ci: add a watchdog for required checks stuck past a threshold - #7411
Merged
Conversation
Directly motivated by today's incident: "Superagent Security Scan" (a required, third-party check this repo has zero control over) hung in_progress for 90+ minutes, silently stalling the entire auto-merge pipeline -- this repo's gate merges based on GitHub's own mergeable_state, which only goes "clean" once every required check resolves, so one stuck required check blocks ALL open PRs, not just the one it's running on. Finding it required manually pulling check-run data PR-by-PR; nothing surfaced it proactively. This can't fix a stuck check -- nothing on this repo's side can, for a third-party check it doesn't control -- it just makes the situation visible fast: a scheduled job (every 15 minutes) flags any open, non-draft PR where a required check has been running past a threshold (default 20 minutes, comfortably above the ~1-2.5 minutes these checks normally take) via a single PR comment, idempotent (checks for an existing marker before posting again) so it doesn't spam the same stuck check repeatedly. Required-check names are hardcoded (validate, Superagent Security Scan) rather than read live from branch protection: GET .../protection/ required_status_checks needs "Administration" repository read permission, which the default GITHUB_TOKEN doesn't get even with an elevated permissions: block -- confirmed against GitHub's own docs that scope isn't in the grantable set for the ephemeral per-run token at all. Hardcoding avoids a dependency that would otherwise 403 on every scheduled run. Verified against the real repo (dry-run, both default and threshold-minutes=0): correctly found zero stuck checks with the incident resolved, and confirmed the threshold=0 dry-run correctly excluded a real currently-queued-but-not-yet-started validate job on a live open PR rather than misflagging it -- a job whose check-run has no started_at yet is legitimately queued, not stuck, and the script treats those differently on purpose.
Contributor
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7411 +/- ##
=======================================
Coverage 91.21% 91.21%
=======================================
Files 716 716
Lines 72789 72789
Branches 20858 20859 +1
=======================================
Hits 66392 66392
Misses 5355 5355
Partials 1042 1042
Flags with carried forward coverage won't be shown. Click here to find out more. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Directly motivated by today's incident: "Superagent Security Scan" (a required, third-party check this repo has zero control over) hung
in_progressfor 90+ minutes, silently stalling the entire auto-merge pipeline — this repo's gate merges based on GitHub's ownmergeable_state, which only goes "clean" once every required check resolves, so one stuck required check blocks ALL open PRs, not just the one it's running on. Finding it required manually pulling check-run data PR-by-PR; nothing surfaced it proactively.This can't fix a stuck check — nothing on this repo's side can, for a third-party check it doesn't control — it just makes the situation visible fast.
.github/workflows/stuck-check-watchdog.ymlrunsscripts/check-stuck-required-checks.mjsevery 15 minutes: it flags any open, non-draft PR where a required check has been running past a threshold (default 20 minutes, comfortably above the ~1-2.5 minutes these checks normally take) via a single PR comment, idempotent — it checks for an existing marker before posting again, so it won't spam the same stuck check repeatedly.One real design constraint surfaced during this: required-check names are hardcoded (
validate,Superagent Security Scan) rather than read live from branch protection.GET .../protection/required_status_checksneeds "Administration" repository read permission, which the defaultGITHUB_TOKENdoesn't get even with an elevatedpermissions:block — confirmed against GitHub's own docs that this scope isn't in the grantable set for the ephemeral per-run token at all. Hardcoding avoids a dependency that would otherwise 403 on every scheduled run; the script has a comment explaining how to update the list by hand if the required checks onmainever change.Scope
type(scope): short summaryConventional Commit format.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run actionlintnode --check scripts/check-stuck-required-checks.mjs--dry-runat the default 20-minute threshold, and--dry-run --threshold-minutes=0to force-check anything currently in-flight): correctly found zero stuck checks now that today's incident has resolved.threshold-minutes=0dry run also incidentally proved a real distinction the script needs to get right: a currently-open PR hadvalidate-testsshards genuinely queued-but-not-yet-started (nostarted_atyet, waiting onvalidate-code) at the exact moment I ran this — the script correctly did NOT flag them, since a job with nostarted_atis legitimately queued, not stuck. Confirmed this wasn't a silent detection failure by cross-checking the same PR's real state viagh pr checksdirectly.npm run typecheck/npm run test:coverage— not applicable; this PR touches nosrc/**file, only a standalone.mjsscript and a workflow file.npm audit --audit-level=moderate— not applicable; no dependency changes.If any required check was skipped, explain why: this PR is CI/build-tooling configuration — the skipped commands aren't applicable, and every actually-relevant command (including live verification against the real repo in dry-run mode, and independently cross-checking the one edge case that came up) is listed above.
Safety
Notes
.github/workflows/**(a guarded path), so it'll be held for manual owner review rather than auto-merged.