From 1c3bb932a925d82141127a01791e827629b205c3 Mon Sep 17 00:00:00 2001 From: Alejandro Rosas Date: Sat, 7 Nov 2026 14:57:08 +0400 Subject: [PATCH] ci: report the automation's manual CI run as the commit status CI passed GitHub leaves the check runs of a workflow_dispatch run off the pull request (sdk-go#22 and sdk-node#28: the statusCheckRollup lists only the pull_request suite), so the required "CI passed" check of a bot pull request stayed "Expected" until a person approved its pull_request run. The new report job of a manual ci.yml run sets "CI passed" as a commit status on the head commit, which the pull request does show, and then starts the release workflows once auto-merge has merged the pull request (that merge starts no push workflow; sdk-go#22 merged at 10:48 and nothing ran on main). Also: any auto-merge disable by a person holds the pull request, whatever its reasonCode. Co-Authored-By: Claude Opus 5.5 --- .github/scripts/bot-pull-request.sh | 81 ++++++++++++++++++++--------- .github/workflows/ci.yml | 28 ++++++++++ docs/DESIGN.md | 2 +- 3 files changed, 85 insertions(+), 26 deletions(-) diff --git a/.github/scripts/bot-pull-request.sh b/.github/scripts/bot-pull-request.sh index 81123ed..ce04d2d 100755 --- a/.github/scripts/bot-pull-request.sh +++ b/.github/scripts/bot-pull-request.sh @@ -6,6 +6,7 @@ # bot-pull-request.sh spec-sync [passed|failed] # bot-pull-request.sh release # bot-pull-request.sh catch-up [] +# bot-pull-request.sh report # # spec-sync without passed|failed reads the conclusion of the latest finished Spec sync run on # main. catch-up starts every workflow that runs release-please (.github/workflows/*.yml using @@ -15,9 +16,11 @@ # # A pull request opened with GITHUB_TOKEN gets its pull_request CI run only after a maintainer # approves it (this repository is public). workflow_dispatch is exempt, so the script starts -# ci.yml on the pull request's branch: its checks land on the head commit, which is what the -# required "CI passed" check on main reads. It then turns on auto-merge (squash), so GitHub -# merges only once the required checks pass. +# ci.yml on the pull request's branch. GitHub leaves the check runs of a manual run off the pull +# request, so ci.yml's report job (mode report, in that run) sets a commit status "CI passed" on +# the head commit, which the pull request shows and main's required check reads, and then runs +# catch-up for the merge. The script turns on auto-merge (squash), so GitHub merges only once +# the required check passes. # # A pull request waits for a person (label needs-alex, one comment, auto-merge off) when: # - a spec sync failed a check (usually a missing openapi/overlay.json entry), is a draft, or @@ -29,12 +32,12 @@ # request labelled do-not-merge, one a person labelled needs-alex, and one whose auto-merge a # person turned off. # -# Needs GH_TOKEN (the workflow token with actions, contents, issues and pull-requests write), -# GITHUB_REPOSITORY, gh and jq. +# Needs GH_TOKEN (the workflow token with actions, contents, issues and pull-requests write; +# statuses write for report), GITHUB_REPOSITORY, gh and jq. set -euo pipefail usage() { - echo "usage: bot-pull-request.sh spec-sync [passed|failed] | release | catch-up []" >&2 + echo "usage: bot-pull-request.sh spec-sync [passed|failed] | release | catch-up [] | report " >&2 exit 2 } @@ -45,6 +48,31 @@ hold_label="needs-alex" person_hold_label="do-not-merge" bot_email="41898282+github-actions[bot]@users.noreply.github.com" +# Starts the release workflows on main for a newest commit that has no run of them, after +# waiting (at most three minutes) for the auto-merge of the open pull request from . +catch_up() { + local branch="$1" waiting sha + if [ -n "$branch" ]; then + for _ in $(seq 1 18); do + waiting="$(gh pr list --repo "$repo" --base "$base" --head "$branch" --state open \ + --json autoMergeRequest --jq '[.[] | select(.autoMergeRequest != null)] | length')" + [ "$waiting" -eq 0 ] && break + sleep 10 + done + fi + sha="$(gh api "repos/${repo}/commits/${base}" --jq '.sha')" + grep -l 'googleapis/release-please-action' .github/workflows/*.yml | while read -r file; do + workflow="$(basename "$file")" + runs="$(gh api "repos/${repo}/actions/workflows/${workflow}/runs?head_sha=${sha}&per_page=1" --jq '.total_count')" + if [ "$runs" -eq 0 ]; then + gh workflow run "$workflow" --repo "$repo" --ref "$base" + echo "Started ${workflow} on ${base} (${sha:0:10}): no run of it had started for that commit." + else + echo "${workflow} already ran on ${base} (${sha:0:10})." + fi + done +} + case "$kind" in spec-sync) branch="${2:-}" @@ -68,26 +96,29 @@ case "$kind" in --json number,headRefName --jq '.[] | select(.headRefName | startswith("release-please--")) | .number')" ;; catch-up) - branch="${2:-}" - if [ -n "$branch" ]; then - for _ in $(seq 1 18); do - waiting="$(gh pr list --repo "$repo" --base "$base" --head "$branch" --state open \ - --json autoMergeRequest --jq '[.[] | select(.autoMergeRequest != null)] | length')" - [ "$waiting" -eq 0 ] && break - sleep 10 - done + catch_up "${2:-}" + exit 0 + ;; + report) + result="${2:-}" + sha="${GITHUB_SHA:?GITHUB_SHA is not set}" + ref="${GITHUB_REF_NAME:?GITHUB_REF_NAME is not set}" + case "$result" in + success | failure) ;; + *) + echo "The CI passed job ended with '${result}'; no status reported." + exit 0 + ;; + esac + gh api "repos/${repo}/statuses/${sha}" -f state="$result" -f context="CI passed" \ + -f description="ci.yml run ${GITHUB_RUN_ID:-} (workflow_dispatch): ${result}" \ + -f target_url="${GITHUB_SERVER_URL:-https://github.com}/${repo}/actions/runs/${GITHUB_RUN_ID:-}" > /dev/null + echo "Set the commit status CI passed=${result} on ${sha:0:10} (${ref})." + if [ "$result" = success ]; then + case "$ref" in + chore/spec-sync | release-please--*) catch_up "$ref" ;; + esac fi - sha="$(gh api "repos/${repo}/commits/${base}" --jq '.sha')" - grep -l 'googleapis/release-please-action' .github/workflows/*.yml | while read -r file; do - workflow="$(basename "$file")" - runs="$(gh api "repos/${repo}/actions/workflows/${workflow}/runs?head_sha=${sha}&per_page=1" --jq '.total_count')" - if [ "$runs" -eq 0 ]; then - gh workflow run "$workflow" --repo "$repo" --ref "$base" - echo "Started ${workflow} on ${base} (${sha:0:10}): no run of it had started for that commit." - else - echo "${workflow} already ran on ${base} (${sha:0:10})." - fi - done exit 0 ;; *) usage ;; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2784a73..5f3a622 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -240,3 +240,31 @@ jobs: fi done echo "Every CI job passed." + + # A pull request of the automation gets its CI from a manual run (bot-pull-requests.yml), and + # GitHub leaves a manual run's check runs off the pull request. This job sets the result as + # the commit status "CI passed" on the commit, which the pull request shows and main's + # required check reads, then starts the release workflows once auto-merge has merged it. + report: + name: Report CI passed as a commit status + if: always() && github.event_name == 'workflow_dispatch' + needs: ci-passed + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + actions: write # start the release workflows after the merge + contents: read + pull-requests: read + statuses: write # the CI passed commit status + env: + GH_TOKEN: ${{ github.token }} + steps: + - name: Check out the scripts and workflows + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + sparse-checkout: .github + - name: Set the commit status + env: + RESULT: ${{ needs.ci-passed.result }} + run: bash .github/scripts/bot-pull-request.sh report "$RESULT" diff --git a/docs/DESIGN.md b/docs/DESIGN.md index a01c69a..a67fbc8 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -266,7 +266,7 @@ Ruby: `net/http` from the standard library, `jwt` only for Authentication. - Release workflows publish with trusted publishing where the registry supports it (PyPI, npm with provenance, RubyGems and NuGet), Maven Central through the Central Portal with signed artifacts (token and signing key in the `maven-central` environment), Packagist through its GitHub hook, and Go module tags announced to proxy.golang.org. - 1.0.0 shipped on 2026-10-29 with Alex's approval, and the repositories are public. - Every release after that publishes straight from the repository, with no person in the loop for API changes (Alex, 2026-11-07). `.github/workflows/bot-pull-requests.yml` starts CI on the spec sync and release-please pull requests and turns on auto-merge with squash, so each merges once the required `CI passed` check passes. Merging the release pull request creates the vX.Y.Z tag and publishes, and the release workflow then waits until the registry serves the version, or fails. -- A pull request opened with `GITHUB_TOKEN` gets its `pull_request` CI run only after a maintainer approves it (the repositories are public), so that workflow starts `ci.yml` with `workflow_dispatch`, whose checks land on the same head commit. A merge made by that auto-merge starts no push workflow, so it also starts the release workflows on `main` when the newest commit has no run of them. +- A pull request opened with `GITHUB_TOKEN` gets its `pull_request` CI run only after a maintainer approves it (the repositories are public), so that workflow starts `ci.yml` with `workflow_dispatch`. GitHub leaves the check runs of a manual run off the pull request, so that run's `report` job sets its result as the commit status `CI passed` on the head commit, which the pull request shows and the required check reads. A merge made by that auto-merge starts no push workflow, so the `report` job, and `bot-pull-requests.yml` every 30 minutes, start the release workflows on `main` when the newest commit has no run of them. - A person decides, and the pull request gets the `needs-alex` label instead of auto-merge, when a spec sync failed a check, when a pull request asks for a major version (a release pull request for a new major version, or a sync commit with `!` or a `BREAKING CHANGE:` footer), or when it carries commits from people. The `do-not-merge` label, or turning auto-merge off by hand, keeps any of these pull requests from merging. - A release created with `GITHUB_TOKEN` does not trigger other workflows, so a repository that publishes from a separate `release.yml` has release-please start it with `workflow_dispatch` for the new tag. No personal access token is stored.