@@ -19,6 +19,10 @@ concurrency:
1919jobs :
2020 release-please :
2121 runs-on : ubuntu-24.04
22+ outputs :
23+ prs_created : ${{ steps.release.outputs.prs_created }}
24+ release_branch : ${{ steps.release_branch.outputs.name }}
25+ release_pr : ${{ steps.release_branch.outputs.pr }}
2226 steps :
2327 - name : Create delivery token
2428 id : delivery-token
@@ -56,11 +60,13 @@ jobs:
5660 RELEASE_PRS : ${{ steps.release.outputs.prs }}
5761 run : |
5862 release_branch="$(jq -r '.[0].headBranchName // empty' <<< "${RELEASE_PRS}")"
59- if [[ -z "${release_branch}" ]]; then
60- echo "Release Please reported a created PR without a head branch" >&2
63+ release_pr="$(jq -r '.[0].number // empty' <<< "${RELEASE_PRS}")"
64+ if [[ -z "${release_branch}" || -z "${release_pr}" ]]; then
65+ echo "Release Please reported a created PR without a head branch or number" >&2
6166 exit 1
6267 fi
6368 echo "name=${release_branch}" >> "${GITHUB_OUTPUT}"
69+ echo "pr=${release_pr}" >> "${GITHUB_OUTPUT}"
6470
6571 - uses : actions/checkout@v4
6672 if : steps.release.outputs.prs_created == 'true'
@@ -99,93 +105,114 @@ jobs:
99105 git commit -m "chore: refresh release lockfiles"
100106 git push origin "HEAD:${RELEASE_BRANCH}"
101107
108+ # A separate job so that "Re-run failed jobs" re-dispatches and re-evaluates every trusted check
109+ # and repaints their statuses and Verify on the current head of the release branch.
110+ release-checks :
111+ name : Release pull request checks
112+ needs : release-please
113+ if : needs.release-please.outputs.prs_created == 'true'
114+ runs-on : ubuntu-24.04
115+ steps :
116+ - uses : actions/checkout@v4
117+ with :
118+ ref : ${{ needs.release-please.outputs.release_branch }}
119+
102120 - name : Run release pull request checks
103- if : steps.release.outputs.prs_created == 'true'
104121 env :
105122 GH_TOKEN : ${{ github.token }}
106- RELEASE_BRANCH : ${{ steps.release_branch.outputs.name }}
107- RELEASE_PRS : ${{ steps.release.outputs.prs }}
123+ RELEASE_BRANCH : ${{ needs.release-please.outputs.release_branch }}
124+ RELEASE_PR : ${{ needs.release-please.outputs.release_pr }}
125+ REQUEST_ID : ${{ github.run_id }}-${{ github.run_attempt }}
108126 run : |
109- release_pr="$(jq -r '.[0].number // empty' <<< "${RELEASE_PRS}")"
110- if [[ -z "${release_pr}" ]]; then
111- echo "Release Please reported a created PR without a number" >&2
112- exit 1
113- fi
114127 head_sha="$(git rev-parse HEAD)"
128+ recovery="rerun the failed Release Please job to re-check"
129+ post_status() {
130+ gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${head_sha}" \
131+ -f "state=$1" -f "context=$2" -f "description=$3" ${4:+-f "target_url=$4"} >/dev/null
132+ }
133+ fail_verify() {
134+ post_status failure Verify "$1; ${recovery}" "${2:-}"
135+ {
136+ echo "### Release pull request checks failed"
137+ echo
138+ echo "$1. After fixing or confirming a transient cause, use **Re-run failed jobs**"
139+ echo "on this workflow run: it re-dispatches every trusted check and repaints Verify."
140+ } >> "${GITHUB_STEP_SUMMARY}"
141+ exit 1
142+ }
115143
116144 # Verify is the required status, so it only succeeds after every trusted check passes,
117- # including the same-runner comparison with the previous release.
145+ # including the same-runner comparison with the previous release. Each dispatched run
146+ # carries this attempt's request id in its run name and is matched on it exactly.
118147 workflows=(pull-request-metadata.yml version-guard.yml release-performance.yml)
119148 contexts=("Pull Request Metadata" "Block automatic 1.0 releases" "Release performance")
120149 for context in "${contexts[@]}"; do
121- gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${head_sha}" \
122- -f state=pending -f "context=${context}" \
123- -f description="Trusted release check is running" >/dev/null
150+ post_status pending "${context}" "Trusted release check is running"
124151 done
125- gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${head_sha}" \
126- -f state=pending -f context=Verify \
127- -f description="Validating trusted release metadata and performance" >/dev/null
152+ post_status pending Verify "Validating trusted release metadata and performance"
128153
154+ dispatched_after="$(date -u -d '-2 minutes' +%Y-%m-%dT%H:%M:%SZ)"
129155 gh workflow run pull-request-metadata.yml --ref "${RELEASE_BRANCH}" \
130- -f "pr_number=${release_pr}"
131- gh workflow run version-guard.yml --ref "${RELEASE_BRANCH}"
132- gh workflow run release-performance.yml --ref "${RELEASE_BRANCH}"
156+ -f "pr_number=${RELEASE_PR}" -f "request_id=${REQUEST_ID}"
157+ gh workflow run version-guard.yml --ref "${RELEASE_BRANCH}" \
158+ -f "request_id=${REQUEST_ID}"
159+ gh workflow run release-performance.yml --ref "${RELEASE_BRANCH}" \
160+ -f "request_id=${REQUEST_ID}"
133161 for index in "${!workflows[@]}"; do
134162 workflow="${workflows[${index}]}"
135163 context="${contexts[${index}]}"
136164 run_id=""
137165 for _ in {1..30}; do
138166 run_id="$(gh run list --workflow "${workflow}" --branch "${RELEASE_BRANCH}" \
139- --event workflow_dispatch --limit 20 --json databaseId,headSha \
140- --jq ".[] | select(.headSha == \"${head_sha}\") | .databaseId" | head -n 1)"
167+ --event workflow_dispatch --limit 50 \
168+ --json databaseId,headSha,displayTitle,createdAt \
169+ --jq ".[] | select(.headSha == \"${head_sha}\"
170+ and (.displayTitle | endswith(\" [${REQUEST_ID}]\"))
171+ and .createdAt >= \"${dispatched_after}\") | .databaseId" | head -n 1)"
141172 [[ -n "${run_id}" ]] && break
142173 sleep 2
143174 done
144175 if [[ -z "${run_id}" ]]; then
145- gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${head_sha}" \
146- -f state=failure -f "context=${context}" \
147- -f description="Trusted release check did not start" >/dev/null
148- gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${head_sha}" \
149- -f state=failure -f context=Verify \
150- -f description="${context} did not start" >/dev/null
151- exit 1
176+ post_status failure "${context}" "Trusted release check did not start"
177+ fail_verify "${context} did not start"
152178 fi
153179 run_url="$(gh run view "${run_id}" --json url --jq .url)"
154180 if gh run watch "${run_id}" --exit-status --interval 30; then
155- gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${head_sha}" \
156- -f state=success -f "context=${context}" -f target_url="${run_url}" \
157- -f description="Trusted release check passed" >/dev/null
181+ post_status success "${context}" "Trusted release check passed" "${run_url}"
158182 else
159- gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${head_sha}" \
160- -f state=failure -f "context=${context}" -f target_url="${run_url}" \
161- -f description="Trusted release check failed" >/dev/null
162- gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${head_sha}" \
163- -f state=failure -f context=Verify -f target_url="${run_url}" \
164- -f description="${context} failed" >/dev/null
165- exit 1
183+ post_status failure "${context}" "Trusted release check failed" "${run_url}"
184+ fail_verify "${context} failed" "${run_url}"
166185 fi
167186 done
168187
169- unexpected_files="$(gh pr view "${release_pr }" --repo "${GITHUB_REPOSITORY}" \
188+ unexpected_files="$(gh pr view "${RELEASE_PR }" --repo "${GITHUB_REPOSITORY}" \
170189 --json files --jq '[.files[].path | select(. != ".release-please-manifest.json" and . != "CHANGELOG.md" and . != "pyproject.toml" and . != "uv.lock" and . != "Cargo.lock" and . != "crates/sqlbuild-rules-native/Cargo.toml")] | join("\n")')"
171190 if [[ -n "${unexpected_files}" ]]; then
172191 echo "Release pull request contains non-metadata changes:" >&2
173192 echo "${unexpected_files}" >&2
174- gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${head_sha}" \
175- -f state=failure -f context=Verify \
176- -f description="Release pull request contains non-metadata changes" >/dev/null
193+ post_status failure Verify "Release pull request contains non-metadata changes"
177194 exit 1
178195 fi
179- gh api --method POST "repos/${GITHUB_REPOSITORY}/statuses/${head_sha}" \
180- -f state=success -f context=Verify \
181- -f description="Product changes passed verification before release metadata update" >/dev/null
196+ post_status success Verify \
197+ "Product changes passed verification before release metadata update"
198+
199+ # The checks can wait longer than an installation token lives, so mint one just before use.
200+ - name : Create delivery token
201+ id : delivery-token
202+ uses : actions/create-github-app-token@v2
203+ with :
204+ app-id : ${{ vars.CHIO_DELIVERY_APP_ID }}
205+ private-key : ${{ secrets.CHIO_DELIVERY_PRIVATE_KEY }}
206+ owner : ${{ github.repository_owner }}
207+ repositories : ${{ github.event.repository.name }}
208+ permission-actions : write
209+ permission-contents : write
210+ permission-issues : write
211+ permission-pull-requests : write
182212
183213 - name : Enable release pull request auto-merge
184- if : steps.release.outputs.prs_created == 'true'
185214 env :
186215 # The delivery app merges, so the resulting push to main starts the tag and publish runs.
187216 GH_TOKEN : ${{ steps.delivery-token.outputs.token }}
188- RELEASE_PRS : ${{ steps.release.outputs.prs }}
189- run : |
190- release_pr="$(jq -r '.[0].number // empty' <<< "${RELEASE_PRS}")"
191- gh pr merge "${release_pr}" --repo "${GITHUB_REPOSITORY}" --auto --squash
217+ RELEASE_PR : ${{ needs.release-please.outputs.release_pr }}
218+ run : gh pr merge "${RELEASE_PR}" --repo "${GITHUB_REPOSITORY}" --auto --squash
0 commit comments