From 21fd4b775b5b033eba3cd122248e35b179d99cf5 Mon Sep 17 00:00:00 2001 From: Maxwell Elliott Date: Thu, 11 Jun 2026 11:54:27 -0400 Subject: [PATCH 1/2] ci: move coverage badge updates to a daily PR-based cron The CI job pushed a regenerated coverage.json directly to master on every push, which force-pushed/raced against concurrent merges and failed CI. Remove that merge-time publish step (and the job's contents:write permission), and add a daily scheduled workflow that regenerates the badge and opens an auto-merging PR only when the coverage value changes. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yaml | 25 ---------- .github/workflows/coverage_badge.yaml | 70 +++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/coverage_badge.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 16f9ee9..482a2fc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,12 +9,6 @@ on: jobs: test-jre21: runs-on: ${{ matrix.os }} - # contents:write is needed by the "Publish coverage badge" step below, which - # commits the regenerated coverage.json back to master on push events. The - # step itself is gated on push + canonical matrix entry, but the permission - # has to be declared at the job level. - permissions: - contents: write strategy: fail-fast: false matrix: @@ -61,25 +55,6 @@ jobs: USE_BAZEL_VERSION: ${{ matrix.bazel }} COVERAGE_THRESHOLD: '90' run: ~/go/bin/bazelisk run //tools:coverage-check -- --badge-json coverage.json bazel-out/_coverage/_coverage_report.dat - - name: Publish coverage badge to master - # Only the canonical Linux + Bazel 9.x runner pushes the badge so the - # other matrix entries don't race to commit the same file. Skipped on - # pull_request because forks lack write access and the badge should - # only ever reflect master. - if: github.event_name == 'push' && github.ref == 'refs/heads/master' && matrix.os == 'ubuntu-latest' && matrix.bazel == '9.x' - run: | - if [ -n "$(git status --porcelain coverage.json)" ]; then - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add coverage.json - # [skip ci] is belt-and-suspenders — GITHUB_TOKEN pushes already do - # not retrigger workflows, but the marker makes the intent explicit - # in the log. - git commit -m "ci: update coverage badge [skip ci]" - git push origin HEAD:master - else - echo "coverage.json unchanged; nothing to publish." - fi - name: Upload test logs uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/coverage_badge.yaml b/.github/workflows/coverage_badge.yaml new file mode 100644 index 0000000..d8c2524 --- /dev/null +++ b/.github/workflows/coverage_badge.yaml @@ -0,0 +1,70 @@ +name: Coverage badge + +# Regenerates the coverage badge (coverage.json) on a daily schedule and opens +# a PR if the value has changed. This replaces the previous approach of pushing +# the badge directly to master from CI, which raced/force-pushed and failed CI. +on: + schedule: + # 13:00 UTC daily. + - cron: '0 13 * * *' + workflow_dispatch: + +jobs: + update-coverage-badge: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Setup Java JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + - name: Setup Go environment + uses: actions/setup-go@v5 + with: + go-version: ^1.17 + id: go + - name: Setup Bazelisk + run: go install github.com/bazelbuild/bazelisk@latest && export PATH=$PATH:$(go env GOPATH)/bin + - uses: actions/checkout@v4 + - name: Run bazel-diff tests with coverage + env: + USE_BAZEL_VERSION: '9.x' + run: ~/go/bin/bazelisk coverage --combined_report=lcov //cli/... //tools:coverage_check_test --enable_bzlmod=true --enable_workspace=false + - name: Regenerate coverage badge + env: + USE_BAZEL_VERSION: '9.x' + COVERAGE_THRESHOLD: '90' + run: ~/go/bin/bazelisk run //tools:coverage-check -- --badge-json coverage.json bazel-out/_coverage/_coverage_report.dat + - name: Open PR if coverage badge changed + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [ -z "$(git status --porcelain coverage.json)" ]; then + echo "coverage.json unchanged; nothing to do." + exit 0 + fi + NEW_COVERAGE="$(python3 -c 'import json,sys; print(json.load(open("coverage.json"))["message"])')" + BRANCH="ci/coverage-badge-update" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -B "$BRANCH" + git add coverage.json + git commit -m "ci: update coverage badge to ${NEW_COVERAGE}" + # Force-push the dedicated, bot-owned branch so re-runs reuse one PR + # instead of accumulating stale branches. This never touches master. + git push --force origin "$BRANCH" + if gh pr view "$BRANCH" --json state --jq '.state' 2>/dev/null | grep -q OPEN; then + echo "PR already open for $BRANCH; it now points at the latest badge." + else + gh pr create \ + --base master \ + --head "$BRANCH" \ + --title "ci: update coverage badge to ${NEW_COVERAGE}" \ + --body "Automated coverage badge refresh. Main-source line coverage is now **${NEW_COVERAGE}**." + fi + # Queue the PR to merge automatically once required checks pass. This + # is idempotent, so it is safe to re-run against an already-queued PR. + gh pr merge "$BRANCH" --auto --squash --delete-branch From 84763b9f3260547a6f957adec0a0c3e2855863b3 Mon Sep 17 00:00:00 2001 From: Maxwell Elliott Date: Thu, 11 Jun 2026 11:55:09 -0400 Subject: [PATCH 2/2] ci: drop auto-merge from coverage badge cron Open the badge PR for manual review instead of queuing it to auto-merge. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/coverage_badge.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/coverage_badge.yaml b/.github/workflows/coverage_badge.yaml index d8c2524..dbd3418 100644 --- a/.github/workflows/coverage_badge.yaml +++ b/.github/workflows/coverage_badge.yaml @@ -65,6 +65,3 @@ jobs: --title "ci: update coverage badge to ${NEW_COVERAGE}" \ --body "Automated coverage badge refresh. Main-source line coverage is now **${NEW_COVERAGE}**." fi - # Queue the PR to merge automatically once required checks pass. This - # is idempotent, so it is safe to re-run against an already-queued PR. - gh pr merge "$BRANCH" --auto --squash --delete-branch