From cff32160c721fcfb180c314962b2dce48cdb71a4 Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Thu, 17 Sep 2026 16:08:04 +0200 Subject: [PATCH 1/3] ci: skip the test suite on release-please Release PRs A Release PR bumps the version and rewrites the changelog. Every source commit in it already passed the suite on the PR it came from, so running it again only delays the release. The guard sits on each job rather than on the calling job in ci.yml: a job skipped by `if:` reports success and satisfies a required status check, while a reusable workflow that is never called produces no check at all and would leave a required one pending forever. The pre-tag gate is unaffected. release.yml triggers on push, where github.head_ref is empty, so the suite still runs before a tag lands. --- .github/workflows/run_tests.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index fcf5760..75e9be9 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -17,8 +17,12 @@ concurrency: permissions: contents: read +# Release PRs only bump the version and rewrite the changelog, and every commit in one +# already passed this suite on its own PR. The guard sits on each job, not on the caller, +# so a skipped job still reports success to a required status check. jobs: unit: + if: ${{ !startsWith(github.head_ref, 'release-please--') }} name: Unit Tests & Code Quality runs-on: ubuntu-latest timeout-minutes: 20 @@ -44,6 +48,7 @@ jobs: make security integration-chat: + if: ${{ !startsWith(github.head_ref, 'release-please--') }} name: Chat Integration Tests runs-on: ubuntu-latest timeout-minutes: 30 @@ -74,7 +79,7 @@ jobs: # the other. !cancelled() keeps the ordering without inheriting the implicit # success(), so a red chat no longer hides whether feed passes. needs: integration-chat - if: '!cancelled()' + if: ${{ !cancelled() && !startsWith(github.head_ref, 'release-please--') }} runs-on: ubuntu-latest timeout-minutes: 30 environment: ci @@ -98,6 +103,7 @@ jobs: run: make test-integration-feed integration-video: + if: ${{ !startsWith(github.head_ref, 'release-please--') }} name: Video Integration Tests runs-on: ubuntu-latest timeout-minutes: 30 @@ -122,6 +128,7 @@ jobs: run: make test-integration-video integration-gcp-lb: + if: ${{ !startsWith(github.head_ref, 'release-please--') }} name: GCP load balancer keep-alive runs-on: ubuntu-latest timeout-minutes: 30 From a22276c681616f664739668e66ddbb231f1ac404 Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Thu, 17 Sep 2026 16:35:06 +0200 Subject: [PATCH 2/3] ci: verify release-please authorship before skipping checks The branch-name guard was spoofable: any PR, a fork's included, could name its head branch release-please--x and skip every required check, which branch protection then counts as satisfied. The guard now also requires the PR to be opened by github-actions[bot] from a branch in this repository. integration-gcp-lb stays unguarded: it is a keep-alive probe running beside the other integration jobs, so skipping it shortens nothing and costs one exercise of the load balancer path it exists to keep warm. --- .github/workflows/run_tests.yml | 35 +++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 75e9be9..13ad849 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -17,12 +17,19 @@ concurrency: permissions: contents: read -# Release PRs only bump the version and rewrite the changelog, and every commit in one -# already passed this suite on its own PR. The guard sits on each job, not on the caller, -# so a skipped job still reports success to a required status check. +# Every job below skips on a Release PR. release-please only bumps the version and rewrites +# the changelog, and every source commit in one already passed this suite on the PR it came +# from. The guard sits on each job rather than on the calling job in ci.yml, because a job +# skipped by `if:` reports success and satisfies a required status check, while a reusable +# workflow that is never called produces no check at all. It tests the author as well as the +# branch name: on its own, the name would let any PR, a fork's included, call its branch +# release-please--x and skip every required check, which branch protection counts as met. jobs: unit: - if: ${{ !startsWith(github.head_ref, 'release-please--') }} + if: >- + ${{ !(github.event.pull_request.user.login == 'github-actions[bot]' + && github.event.pull_request.head.repo.full_name == github.repository + && startsWith(github.head_ref, 'release-please--')) }} name: Unit Tests & Code Quality runs-on: ubuntu-latest timeout-minutes: 20 @@ -48,7 +55,10 @@ jobs: make security integration-chat: - if: ${{ !startsWith(github.head_ref, 'release-please--') }} + if: >- + ${{ !(github.event.pull_request.user.login == 'github-actions[bot]' + && github.event.pull_request.head.repo.full_name == github.repository + && startsWith(github.head_ref, 'release-please--')) }} name: Chat Integration Tests runs-on: ubuntu-latest timeout-minutes: 30 @@ -79,7 +89,11 @@ jobs: # the other. !cancelled() keeps the ordering without inheriting the implicit # success(), so a red chat no longer hides whether feed passes. needs: integration-chat - if: ${{ !cancelled() && !startsWith(github.head_ref, 'release-please--') }} + if: >- + ${{ !cancelled() + && !(github.event.pull_request.user.login == 'github-actions[bot]' + && github.event.pull_request.head.repo.full_name == github.repository + && startsWith(github.head_ref, 'release-please--')) }} runs-on: ubuntu-latest timeout-minutes: 30 environment: ci @@ -103,7 +117,10 @@ jobs: run: make test-integration-feed integration-video: - if: ${{ !startsWith(github.head_ref, 'release-please--') }} + if: >- + ${{ !(github.event.pull_request.user.login == 'github-actions[bot]' + && github.event.pull_request.head.repo.full_name == github.repository + && startsWith(github.head_ref, 'release-please--')) }} name: Video Integration Tests runs-on: ubuntu-latest timeout-minutes: 30 @@ -128,7 +145,9 @@ jobs: run: make test-integration-video integration-gcp-lb: - if: ${{ !startsWith(github.head_ref, 'release-please--') }} + # Not guarded below: this is a keep-alive probe, not a gate. It runs beside the other + # integration jobs rather than after them, so skipping it shortens nothing, and each + # skipped run is one fewer exercise of the GCP load balancer path it exists to keep warm. name: GCP load balancer keep-alive runs-on: ubuntu-latest timeout-minutes: 30 From 60e396836852b74a3495f3f7e8ccdef84f8e76de Mon Sep 17 00:00:00 2001 From: Yun Wang Date: Thu, 17 Sep 2026 16:36:17 +0200 Subject: [PATCH 3/3] ci: test a human commit pushed onto a Release PR The guard keyed on who opened the PR, so a commit pushed by hand onto the release-please branch, to fix a conflict or a changelog entry, inherited the skip and reached the default branch having run nothing. github.actor is the pusher rather than the PR author, so that commit is now tested like any other and only release-please's own pushes skip. --- .github/workflows/run_tests.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 13ad849..eb14b3f 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -24,10 +24,14 @@ permissions: # workflow that is never called produces no check at all. It tests the author as well as the # branch name: on its own, the name would let any PR, a fork's included, call its branch # release-please--x and skip every required check, which branch protection counts as met. +# github.actor is the third clause, and it is the pusher rather than the PR author, so a +# human commit pushed onto the Release PR to fix a conflict or a changelog entry is tested +# like any other commit instead of riding the skip into main untested. jobs: unit: if: >- - ${{ !(github.event.pull_request.user.login == 'github-actions[bot]' + ${{ !(github.actor == 'github-actions[bot]' + && github.event.pull_request.user.login == 'github-actions[bot]' && github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'release-please--')) }} name: Unit Tests & Code Quality @@ -56,7 +60,8 @@ jobs: integration-chat: if: >- - ${{ !(github.event.pull_request.user.login == 'github-actions[bot]' + ${{ !(github.actor == 'github-actions[bot]' + && github.event.pull_request.user.login == 'github-actions[bot]' && github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'release-please--')) }} name: Chat Integration Tests @@ -91,7 +96,8 @@ jobs: needs: integration-chat if: >- ${{ !cancelled() - && !(github.event.pull_request.user.login == 'github-actions[bot]' + && !(github.actor == 'github-actions[bot]' + && github.event.pull_request.user.login == 'github-actions[bot]' && github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'release-please--')) }} runs-on: ubuntu-latest @@ -118,7 +124,8 @@ jobs: integration-video: if: >- - ${{ !(github.event.pull_request.user.login == 'github-actions[bot]' + ${{ !(github.actor == 'github-actions[bot]' + && github.event.pull_request.user.login == 'github-actions[bot]' && github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'release-please--')) }} name: Video Integration Tests