-
Notifications
You must be signed in to change notification settings - Fork 3
ci: paginate the pending-release query and guard its lookups #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,7 +26,10 @@ jobs: | |
| release-pr: | ||
| name: Release PR | ||
| needs: detect | ||
| if: needs.detect.outputs.pending != 'true' | ||
| # Gate on the explicit value. If detect fails or is skipped the output is empty, and | ||
| # anything short of a definite "nothing pending" has to hold this job back, or it | ||
| # proposes a second Release PR on top of one that may still be untagged. | ||
| if: needs.detect.outputs.pending == 'false' | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| permissions: | ||
|
|
@@ -55,7 +58,9 @@ jobs: | |
| timeout-minutes: 5 | ||
| permissions: | ||
| contents: read | ||
| pull-requests: read | ||
| # write, not read: a stuck release is announced on its own Release PR, because | ||
| # nothing else reaches a person without them opening the run first. | ||
| pull-requests: write | ||
| issues: read | ||
| outputs: | ||
| pending: ${{ steps.find.outputs.pending }} | ||
|
|
@@ -70,35 +75,79 @@ jobs: | |
| run: | | ||
| pending=false | ||
| ready=false | ||
| lookup_failed=false | ||
|
|
||
| # Query the label directly. Listing closed PRs and filtering client-side loses | ||
| # a release that has slipped past the first page, which reads as "nothing to | ||
| # release" and passes. Filter before picking, too: a hotfix branch can hold | ||
| # its own pending release, and taking the newest label match would drop this | ||
| # branch's release on every run until the other one clears. | ||
| nums="$(gh api "repos/${GITHUB_REPOSITORY}/issues" \ | ||
| -X GET -f state=closed -f labels='autorelease: pending' -f per_page=20 \ | ||
| --jq '.[] | select(.pull_request != null) | .number')" | ||
| # Query the label directly, and page: release-please applies the label when it | ||
| # opens the Release PR, not when it merges, so every Release PR closed without | ||
| # merging keeps it forever and holds a slot in this listing. One page would | ||
| # eventually stop containing the genuinely pending release, which reads as | ||
| # "nothing to release" and passes. merged_at comes back in the listing, so | ||
| # filtering on it here keeps the per-PR lookups below to real candidates. | ||
| # --paginate makes this N requests, so guard it too, and remember that a failure | ||
| # here means "unknown", never "nothing to release". | ||
| if ! nums="$(gh api --paginate "repos/${GITHUB_REPOSITORY}/issues" \ | ||
| -X GET -f state=closed -f labels='autorelease: pending' -f per_page=100 \ | ||
| --jq '.[] | select(.pull_request.merged_at != null) | .number')"; then | ||
| echo "::warning::Could not list pending releases." | ||
| nums="" | ||
| lookup_failed=true | ||
| fi | ||
|
|
||
| # The base branch is not in that listing, so each candidate still needs a | ||
| # lookup: a hotfix branch can hold its own pending release, and taking the | ||
| # newest label match would drop this branch's release until the other clears. | ||
| num="" | ||
| sha="" | ||
| for n in $nums; do | ||
| sha="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${n}" \ | ||
| --jq 'select(.merged_at != null and .base.ref == env.BASE) | .merge_commit_sha // empty')" | ||
| # Under `bash -e` an unguarded assignment from a non-2xx would abort the | ||
| # step, which would fail detect and skip release-pr with it. | ||
| if ! sha="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${n}" \ | ||
| --jq 'select(.merged_at != null and .base.ref == env.BASE) | .merge_commit_sha // empty')"; then | ||
| echo "::warning::Could not read PR #${n}; skipping it." | ||
| sha="" | ||
| lookup_failed=true | ||
| continue | ||
| fi | ||
| if [ -n "$sha" ]; then | ||
| num="$n" | ||
| break | ||
| fi | ||
| done | ||
|
|
||
| if [ -z "$sha" ]; then | ||
| if [ -z "$sha" ] && [ "$lookup_failed" = true ]; then | ||
| # Unknown is not the same as nothing. Releasing on a guess is how a second | ||
| # Release PR lands on top of one that was never tagged. | ||
| pending=true | ||
| echo "::warning::Could not determine whether a release is pending on ${BASE}; standing down." | ||
| elif [ -z "$sha" ]; then | ||
| echo "No pending release on ${BASE}." | ||
| elif [ "$sha" != "$HEAD_SHA" ]; then | ||
| # Reached when an earlier release run failed after the Release PR merged. | ||
| # Tagging $sha here would tag a tree this run never tested, and failing | ||
| # would redden every later push, so stand down and say why. | ||
| pending=true | ||
| echo "::warning::Release PR #${num} is still pending at ${sha}, which is not this run's commit ${HEAD_SHA}. Re-run the workflow run for ${sha} to finish that release." | ||
| { | ||
| echo "### Release stuck" | ||
| echo | ||
| echo "Release PR #${num} merged at \`${sha}\` and was never tagged, so no Release PR will be opened or refreshed until it clears." | ||
| echo | ||
| echo "Re-run the \`Release\` run for \`${sha}\`. If that commit is genuinely broken, remove the \`autorelease: pending\` label from #${num} by hand and release forward." | ||
| } >> "$GITHUB_STEP_SUMMARY" | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The PR names the problem as "a stuck release stays stuck until somebody opens the run and reads the annotation", and a step summary is also only visible when somebody opens the run. The conclusion stays green and nothing notifies anyone.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in marker="<!-- release-stuck:${sha} -->"
seen="$(gh api "repos/${GITHUB_REPOSITORY}/issues/${num}/comments" --paginate --jq '.[].body' || echo "")"
if ! printf '%s' "$seen" | grep -qF "$marker"; then
...
gh pr comment "$num" --body-file "${RUNNER_TEMP}/release-stuck.md" || echo "::warning::Could not comment on #${num}."
fi
Keeping the step summary as well: it is free and it is where someone already in the run will look. |
||
| # A warning annotation and a step summary are both only visible to someone who | ||
| # already opened the run. Tell the Release PR's subscribers once per stuck sha. | ||
| marker="<!-- release-stuck:${sha} -->" | ||
| seen="$(gh api "repos/${GITHUB_REPOSITORY}/issues/${num}/comments" --paginate --jq '.[].body' || echo "")" | ||
| if ! printf '%s' "$seen" | grep -qF "$marker"; then | ||
| { | ||
| echo "$marker" | ||
| echo "This release is stuck: #${num} merged at \`${sha}\` and was never tagged, so no Release PR is opened or refreshed until it clears." | ||
| echo | ||
| echo "Re-run the \`Release\` run for \`${sha}\`. If that commit is genuinely broken, remove the \`autorelease: pending\` label here by hand and release forward." | ||
| } > "${RUNNER_TEMP}/release-stuck.md" | ||
| gh pr comment "$num" --repo "$GITHUB_REPOSITORY" --body-file "${RUNNER_TEMP}/release-stuck.md" \ | ||
| || echo "::warning::Could not comment on #${num}." | ||
| fi | ||
| else | ||
| pending=true | ||
| ready=true | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A failed lookup now reads as "nothing to release". release-please swaps the label for
autorelease: taggedwhen it tags, so in steady statenumsholds exactly one number: the pending release. One 502 on it warns, continues, and the loop ends withsha="", so the step prints "No pending release on master", setspending=false, andrelease-propens a second Release PR on top of the untagged one while the tag job never runs. Aborting was wrong, but reading unknown as "nothing pending" is worse than aborting.With two stuck candidates it also misreports: skipping the newest lets an older one match, so the "Release stuck" warning names the wrong PR and tells the operator to re-run the wrong sha.
Set
lookup_failed=truehere, and after the loop treat[ -z "$sha" ] && [ "$lookup_failed" = true ]aspending=true, so an unknown answer stands down instead of releasing.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in
3b1f371.lookup_failedis set on both failure paths, and the branch reads:Given how often this logic has been wrong, I stubbed
ghand ran the branch:The misreporting case you raise, where skipping the newest lets an older candidate match and the warning names the wrong PR, is now covered too: any skipped candidate sets
lookup_failed, so a wrong-PR match cannot be reported as a clean result.