From 8eba0d9285e2b1e7638f1ff498399c8c6f8a2791 Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Thu, 17 Sep 2026 11:09:07 +0200 Subject: [PATCH 1/2] fix(ci): treat an unknown release state as pending, not as none Carries the review outcome from the five sibling PRs back to py, which merged first. The guarded lookups read a failed call as nothing to release, release-pr gated fail-open on !cancelled(), and the per-PR jq had lost its merged_at check, which matters because closed-unmerged PRs keep a speculative merge_commit_sha. --- .github/workflows/release.yml | 52 +++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bbdb0a3b..d0d20ee8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,15 +26,10 @@ jobs: release-pr: name: Release PR needs: detect - # No status function of its own would mean an implicit success(), so one transient - # gh api error inside detect would stop the reversible half too. Its own event and - # ref guard still has to be repeated here, because detect is skipped on the - # publish_tag path and this job must skip with it. - if: >- - !cancelled() && - (github.event_name == 'push' || inputs.publish_tag == '') && - (github.ref_name == 'main' || endsWith(github.ref_name, '.x')) && - 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: @@ -63,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 }} @@ -78,6 +75,7 @@ jobs: run: | pending=false ready=false + lookup_failed=false # 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 @@ -85,9 +83,15 @@ jobs: # 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. - nums="$(gh api --paginate "repos/${GITHUB_REPOSITORY}/issues" \ + # --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')" + --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 @@ -98,9 +102,10 @@ jobs: # 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(.base.ref == env.BASE) | .merge_commit_sha // empty')"; then + --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 @@ -109,7 +114,12 @@ jobs: 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. @@ -124,6 +134,20 @@ jobs: 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" + # 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="" + 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" --body-file "${RUNNER_TEMP}/release-stuck.md" \ + || echo "::warning::Could not comment on #${num}." + fi else pending=true ready=true From 565637e84084c7d1e4a36f00f40301a29d995169 Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Thu, 17 Sep 2026 13:59:38 +0200 Subject: [PATCH 2/2] ci: give the stuck-release comment an explicit repo detect never checks the repository out, so gh pr comment had no git remote to infer the base repo from and would have failed behind its own || guard. Matches the fix already on the five sibling branches. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d0d20ee8..a1e65af3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -145,7 +145,7 @@ jobs: 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" --body-file "${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