From 2e16a3ba92b90192f48c92c3f42522cc41d29242 Mon Sep 17 00:00:00 2001 From: Troublor <14846504+Troublor@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:13:33 +0800 Subject: [PATCH 1/5] ci(release): adopt the gutenberg release pipeline --- .github/workflows/app-token-lint.yml | 32 +++++ .github/workflows/on-release.yml | 2 +- .github/workflows/release-candidate.yml | 97 +++++++++++---- .github/workflows/release-publish.yml | 56 --------- .github/workflows/release-settle.yml | 158 +++++++++++++++++++----- 5 files changed, 231 insertions(+), 114 deletions(-) create mode 100644 .github/workflows/app-token-lint.yml delete mode 100644 .github/workflows/release-publish.yml diff --git a/.github/workflows/app-token-lint.yml b/.github/workflows/app-token-lint.yml new file mode 100644 index 00000000..c3f6caa0 --- /dev/null +++ b/.github/workflows/app-token-lint.yml @@ -0,0 +1,32 @@ +name: App Token Lint + +# Fails a pull request that mints a GitHub App token the wrong way: a CI-app +# token with write permissions it must not have, the CI app in a release +# workflow, or a release-app (mega-gutenberg) token outside the `release` +# environment. Read-only; no secrets. The rules are listed in the guide. +# +# Guide: https://github.com/megaeth-labs/.github/blob/main/.github/actions/app-token-lint/README.md + +on: + pull_request: + paths: [".github/workflows/**"] + push: + branches: [main] + +permissions: + contents: read + +jobs: + app-token-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + persist-credentials: false + + # claude.yml mints a CI-app (Maxwell) token without `permission-*`, so + # lint only the release workflows. + - uses: megaeth-labs/.github/.github/actions/app-token-lint@main + with: + paths: .github/workflows/release-candidate.yml .github/workflows/release-settle.yml .github/workflows/on-release.yml .github/workflows/app-token-lint.yml diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml index 46b89b60..3b3a3b96 100644 --- a/.github/workflows/on-release.yml +++ b/.github/workflows/on-release.yml @@ -131,7 +131,7 @@ jobs: needs: build runs-on: ubuntu-24.04 timeout-minutes: 15 - environment: publish + environment: release steps: - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index 5ae78f2f..40fbde88 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -2,14 +2,27 @@ name: Release Candidate # Trunk-first release start. Dispatch with a version: the shared action bumps # the version file on the default branch, drafts the changelog entry, and -# opens a candidate PR. When that -# PR merges, the `cut` job creates `release-vX.Y.Z` at the merge commit. No -# tag is created here — see release-settle / release-publish. +# opens a candidate PR. When that PR merges, the push to the default branch +# starts this workflow again: `route` recognises the merged candidate PR and +# `cut` creates `release-vX.Y.Z` at the merge commit. No tag is created here; +# see release-settle. # # To release a commit that is not the tip of the default branch, dispatch with # `base_commit` as well: `propose` creates `release-vX.Y.Z` at that commit and # aims the candidate PR at it, so that PR never touches the default branch and -# the `cut` job below does not run. +# `route` never selects `cut` for it. +# +# Every write is made by the release GitHub App (mega-gutenberg), which reads +# MEGA_GUTENBERG_CLIENT_ID and MEGA_GUTENBERG_PK from repository scope, so no +# job here declares an environment. The decision to release is made by merging +# the candidate PR, not by a click on the run. `route` is read-only and uses +# the job token. +# +# The candidate PR carries no `compat:` label: the compatibility level is a +# per-release judgement, so the reviewer adds it, and the required +# Compatibility Label Check re-runs on `labeled`. +# +# Guide: https://github.com/megaeth-labs/.github/blob/main/.github/actions/RELEASE.md on: workflow_dispatch: @@ -23,8 +36,7 @@ on: required: false type: string default: "" - pull_request: - types: [closed] + push: branches: [main] permissions: @@ -34,9 +46,9 @@ jobs: propose: if: github.event_name == 'workflow_dispatch' # One proposal at a time: two dispatches would race on the candidate - # branch. Job-level, not workflow-level: every PR closing on the default - # branch also starts this workflow, and a workflow-wide group would let - # that noise evict a queued release run (one pending run per group). + # branch. Job-level, not workflow-level: every push to the default branch + # also starts this workflow, and a workflow-wide group would let that + # noise evict a queued release run (one pending run per group). # A skipped job holds no slot in a job-level group. concurrency: group: ${{ github.workflow }}-propose @@ -44,16 +56,22 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 - id: app-token + id: release-token with: - client-id: ${{ vars.MEGA_MAXWELL_CLIENT_ID }} - private-key: ${{ secrets.MEGA_MAXWELL_PK }} + client-id: ${{ vars.MEGA_GUTENBERG_CLIENT_ID }} + private-key: ${{ secrets.MEGA_GUTENBERG_PK }} + owner: megaeth-labs + repositories: stateless-validator + permission-contents: write + permission-pull-requests: write + permission-workflows: write - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 persist-credentials: false + # For `bump_command`; picks up rust-toolchain.toml. - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1 with: cache: false @@ -61,7 +79,7 @@ jobs: - uses: megaeth-labs/.github/.github/actions/release-candidate@main with: stage: propose - token: ${{ steps.app-token.outputs.token }} + token: ${{ steps.release-token.outputs.token }} version: ${{ inputs.version }} base_commit: ${{ inputs.base_commit }} version_file: Cargo.toml @@ -69,14 +87,40 @@ jobs: changelog_file: CHANGELOG.md # Cargo.lock carries the workspace crate versions too. bump_command: cargo update --workspace + # Every megaeth-labs git dependency (algebra, salt, mega-evm) is public, + # so the read-only job token resolves them. + dependency_token: ${{ github.token }} + + # Read-only: decides from the pushed commit whether it is a merged candidate + # PR that targeted the default branch. One proposed with `base_commit` merges + # into the release branch propose already cut, and routes to nothing here. + route: + if: github.event_name == 'push' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + outputs: + action: ${{ steps.route.outputs.action }} + commit: ${{ steps.route.outputs.commit }} + pr_number: ${{ steps.route.outputs.pr_number }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + persist-credentials: false + + - id: route + uses: megaeth-labs/.github/.github/actions/release-route@main + with: + kind: candidate + version_file: Cargo.toml + version_pattern: toml cut: - if: >- - github.event_name == 'pull_request' && - github.event.pull_request.merged == true && - startsWith(github.event.pull_request.head.ref, 'chore/release-candidate-') && - github.event.pull_request.user.login == 'mega-maxwell[bot]' - # One cut at a time, never cancelled: pull_request.closed does not refire. + needs: route + if: needs.route.outputs.action == 'cut' + # One cut at a time, never cancelled: a push does not refire. # Job-level for the reason given on `propose`. concurrency: group: ${{ github.workflow }}-cut @@ -84,10 +128,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 - id: app-token + id: release-token with: - client-id: ${{ vars.MEGA_MAXWELL_CLIENT_ID }} - private-key: ${{ secrets.MEGA_MAXWELL_PK }} + client-id: ${{ vars.MEGA_GUTENBERG_CLIENT_ID }} + private-key: ${{ secrets.MEGA_GUTENBERG_PK }} + owner: megaeth-labs + repositories: stateless-validator + permission-contents: write + permission-pull-requests: read + permission-workflows: write - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: @@ -97,6 +146,8 @@ jobs: - uses: megaeth-labs/.github/.github/actions/release-candidate@main with: stage: cut - token: ${{ steps.app-token.outputs.token }} + token: ${{ steps.release-token.outputs.token }} + commit: ${{ needs.route.outputs.commit }} + pr_number: ${{ needs.route.outputs.pr_number }} version_file: Cargo.toml version_pattern: toml diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml deleted file mode 100644 index b5ad3e1d..00000000 --- a/.github/workflows/release-publish.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Release Publish - -# Publishes a settled release when its settle PR merges into the release -# branch: annotated tag at the merge commit (once) and a GitHub Release with -# the changelog section as notes. Merging the settle PR is the release approval (the -# `release-*` branch ruleset requires a reviewed PR), so no environment gate -# is needed; add the CI app as a bypass actor on the `v*` tag ruleset. -# -# This file must exist on the release branch (it does, when the branch is cut -# from a default branch that has it). -# -# Only settlement by PR reaches this workflow. release-settle.yml settles -# directly (`settle_mode: direct`, gated by the `release` environment) and -# publishes from its own job, so nothing here fires unless this repo goes -# back to PR mode; the job gate rejects any settle PR the app did not open. - -on: - pull_request: - types: [closed] - branches: ["release-v*"] - -permissions: - contents: read - -jobs: - publish: - if: >- - github.event.pull_request.merged == true && - startsWith(github.event.pull_request.head.ref, 'chore/release-settle-') && - github.event.pull_request.user.login == 'mega-maxwell[bot]' - # One publish per release branch: the tag guard and the tag push are two - # steps, so a re-run must queue behind a run in flight, never overlap it. - # Job-level: other PRs closing on the release branch also start this - # workflow, and must not evict a queued publish (one pending per group). - concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.base.ref }} - cancel-in-progress: false - runs-on: ubuntu-latest - steps: - - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 - id: app-token - with: - client-id: ${{ vars.MEGA_MAXWELL_CLIENT_ID }} - private-key: ${{ secrets.MEGA_MAXWELL_PK }} - - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - persist-credentials: false - - - uses: megaeth-labs/.github/.github/actions/release-publish@main - with: - token: ${{ steps.app-token.outputs.token }} - version_file: Cargo.toml - version_pattern: toml - changelog_file: CHANGELOG.md diff --git a/.github/workflows/release-settle.yml b/.github/workflows/release-settle.yml index 846af37a..743b8380 100644 --- a/.github/workflows/release-settle.yml +++ b/.github/workflows/release-settle.yml @@ -1,51 +1,103 @@ name: Release Settle -# Settle a release candidate. Dispatch from the default branch with the -# version and the full SHA of the release-branch tip; the shared action -# verifies it, generates release notes from the commits since the previous -# tag, commits them to the changelog on the release branch, and publishes: -# annotated tag at that commit, GitHub Release with the notes (on-release.yml -# then attaches the binaries; release.yaml / release-tracing.yaml archive to -# Artifact Registry on the tag push). +# Settle a release candidate by pull request. A push to a `release-v*` branch +# starts this workflow and `route` decides what the push means: +# refresh the candidate has landed on the release branch: generate release +# notes from the commits since the previous tag and open or update +# a reviewed `chore/release-settle-vX.Y.Z` PR onto the release +# branch with the changelog entry; +# publish that settle PR was just merged: create the annotated tag at the +# merge commit and the GitHub Release with the changelog section +# as notes. +# Merging the settle PR is the release decision on a push. Dispatch with +# `version` to publish a clean release branch (no settle PR needed), or to +# refresh it when settlement is needed; a blank version refreshes every +# eligible release branch. # -# The dispatch is the settlement decision, gated by the `release` environment -# on the job: its required reviewers approve the run before it starts, and -# its deployment branch policy allows only the default branch. The app is a -# bypass actor on the release-branch and `v*` tag rulesets, which is what -# lets the settle commit and the tag land. `settlers` (default `any`) is left -# unset: the environment's reviewers are the only gate. +# Every write is made by the release GitHub App (mega-gutenberg), which reads +# MEGA_GUTENBERG_CLIENT_ID and MEGA_GUTENBERG_PK from repository scope, so +# the PR-creating jobs declare no environment. Only the publish job waits on +# the `release` environment, whose required reviewers authorise the tag and the +# GitHub Release. The app is a bypass actor on the `v*` tag ruleset only; the +# `release-*` branch ruleset still requires the reviewed settle PR. `route` is +# read-only and uses the job token. +# +# The settle PR carries no labels: the `compat:*` label required by the +# Compatibility Label Check is a per-release judgement, so the reviewer adds +# it, and the check re-runs on `labeled`. +# +# This file must exist on the release branch (it does, when the branch is cut +# from `main`); a push to the branch runs the copy of the workflow on that +# branch. +# +# Guide: https://github.com/megaeth-labs/.github/blob/main/.github/actions/RELEASE.md on: + push: + branches: ["release-v*"] workflow_dispatch: inputs: version: - description: "Version to settle (X.Y.Z)" - required: true + description: "Version to refresh (X.Y.Z); blank: every eligible release branch" + required: false type: string - commit: - description: "Full SHA of the release-branch tip being settled" - required: true - type: string - -# One settle per version: two dispatches for the same version would race on -# the release branch. Queue, never cancel. -concurrency: - group: ${{ github.workflow }}-${{ inputs.version }} - cancel-in-progress: false + default: "" permissions: contents: read jobs: - settle: + # Read-only: classifies the push (or the dispatch) as refresh, publish or skip. + route: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + outputs: + action: ${{ steps.route.outputs.action }} + settle_source: ${{ steps.route.outputs.settle_source }} + targets: ${{ steps.route.outputs.targets }} + release_branch: ${{ steps.route.outputs.release_branch }} + commit: ${{ steps.route.outputs.commit }} + pr_number: ${{ steps.route.outputs.pr_number }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + persist-credentials: false + + - id: route + uses: megaeth-labs/.github/.github/actions/release-route@main + with: + kind: settle + version: ${{ inputs.version }} + version_file: Cargo.toml + version_pattern: toml + + refresh: + needs: route + if: needs.route.outputs.action == 'refresh' + strategy: + fail-fast: false + matrix: + target: ${{ fromJSON(needs.route.outputs.targets) }} + # One refresh per release branch, queued and never cancelled: two runs + # would race on the settle PR branch. + concurrency: + group: release-settle-refresh-${{ matrix.target.release_branch }} + cancel-in-progress: false runs-on: ubuntu-latest - environment: release steps: - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 - id: app-token + id: release-token with: - client-id: ${{ vars.MEGA_MAXWELL_CLIENT_ID }} - private-key: ${{ secrets.MEGA_MAXWELL_PK }} + client-id: ${{ vars.MEGA_GUTENBERG_CLIENT_ID }} + private-key: ${{ secrets.MEGA_GUTENBERG_PK }} + owner: megaeth-labs + repositories: stateless-validator + permission-contents: write + permission-pull-requests: write + permission-workflows: write - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: @@ -54,10 +106,48 @@ jobs: - uses: megaeth-labs/.github/.github/actions/release-settle@main with: - token: ${{ steps.app-token.outputs.token }} - version: ${{ inputs.version }} - commit: ${{ inputs.commit }} + token: ${{ steps.release-token.outputs.token }} + version: ${{ matrix.target.version }} + commit: ${{ matrix.target.commit }} + release_branch: ${{ matrix.target.release_branch }} + version_file: Cargo.toml + version_pattern: toml + changelog_file: CHANGELOG.md + + publish: + needs: route + if: needs.route.outputs.action == 'publish' + # One publish per release branch: the tag guard and the tag push are two + # steps, so a re-run must queue behind a run in flight, never overlap it. + concurrency: + group: release-settle-publish-${{ needs.route.outputs.release_branch }} + cancel-in-progress: false + runs-on: ubuntu-latest + environment: release + steps: + - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 + id: release-token + with: + client-id: ${{ vars.MEGA_GUTENBERG_CLIENT_ID }} + private-key: ${{ secrets.MEGA_GUTENBERG_PK }} + owner: megaeth-labs + repositories: stateless-validator + permission-contents: write + permission-pull-requests: read + permission-workflows: write + + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: megaeth-labs/.github/.github/actions/release-publish@main + with: + token: ${{ steps.release-token.outputs.token }} + commit: ${{ needs.route.outputs.commit }} + pr_number: ${{ needs.route.outputs.pr_number }} + settle_source: ${{ needs.route.outputs.settle_source || 'pr' }} + release_branch: ${{ needs.route.outputs.release_branch }} version_file: Cargo.toml version_pattern: toml changelog_file: CHANGELOG.md - settle_mode: direct From a8ecd64adaf3c80b356898573053181e746aa0cd Mon Sep 17 00:00:00 2001 From: Troublor <14846504+Troublor@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:45:20 +0800 Subject: [PATCH 2/5] ci(release): scope the app-token-lint triggers to the release workflows --- .github/workflows/app-token-lint.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/app-token-lint.yml b/.github/workflows/app-token-lint.yml index c3f6caa0..87584681 100644 --- a/.github/workflows/app-token-lint.yml +++ b/.github/workflows/app-token-lint.yml @@ -9,9 +9,18 @@ name: App Token Lint on: pull_request: - paths: [".github/workflows/**"] + paths: + - .github/workflows/release-candidate.yml + - .github/workflows/release-settle.yml + - .github/workflows/on-release.yml + - .github/workflows/app-token-lint.yml push: branches: [main] + paths: + - .github/workflows/release-candidate.yml + - .github/workflows/release-settle.yml + - .github/workflows/on-release.yml + - .github/workflows/app-token-lint.yml permissions: contents: read From 9373258e89bda17a611cf42625a44b0755cc51f9 Mon Sep 17 00:00:00 2001 From: Troublor <14846504+Troublor@users.noreply.github.com> Date: Thu, 29 Oct 2026 13:58:25 +0800 Subject: [PATCH 3/5] ci(release): refresh open candidates when main advances --- .github/workflows/release-candidate.yml | 77 +++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index 40fbde88..41ba3480 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -91,6 +91,83 @@ jobs: # so the read-only job token resolves them. dependency_token: ${{ github.token }} + # Only trunk candidates follow trunk pushes; base_commit candidates belong + # to their release branch and must never pick up changes from trunk. + refresh-candidates: + if: github.event_name == 'push' && github.ref_name == github.event.repository.default_branch + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + prs: ${{ steps.candidates.outputs.prs }} + steps: + - id: candidates + env: + GH_TOKEN: ${{ github.token }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + run: | + prs="$(gh api --method GET --paginate --slurp \ + "repos/$GITHUB_REPOSITORY/pulls" \ + -f state=open -f base="$DEFAULT_BRANCH" -f per_page=100 \ + --jq '[.[][] | select(.head.ref | startswith("chore/release-candidate-")) | select(.head.repo.full_name == .base.repo.full_name) | .number]')" + echo "prs=$(jq -c . <<< "$prs")" >> "$GITHUB_OUTPUT" + + refresh: + needs: refresh-candidates + if: needs.refresh-candidates.outputs.prs != '[]' + strategy: + fail-fast: false + matrix: + pr: ${{ fromJSON(needs.refresh-candidates.outputs.prs) }} + concurrency: + group: ${{ github.workflow }}-refresh-${{ matrix.pr }} + cancel-in-progress: false + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + steps: + # Recheck after queueing: a candidate may have closed or changed base + # while another refresh held its concurrency slot. + - id: candidate + env: + GH_TOKEN: ${{ github.token }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + PR_NUMBER: ${{ matrix.pr }} + run: | + version="$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" \ + --jq 'select(.state == "open") | select(.base.ref == env.DEFAULT_BRANCH) | select(.head.repo.full_name == .base.repo.full_name) | .head.ref | select(startswith("chore/release-candidate-")) | ltrimstr("chore/release-candidate-")')" + echo "version=$version" >> "$GITHUB_OUTPUT" + + - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 + if: steps.candidate.outputs.version != '' + id: release-token + with: + client-id: ${{ vars.MEGA_GUTENBERG_CLIENT_ID }} + private-key: ${{ secrets.MEGA_GUTENBERG_PK }} + owner: megaeth-labs + repositories: stateless-validator + permission-contents: write + permission-pull-requests: write + permission-workflows: write + + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + if: steps.candidate.outputs.version != '' + with: + fetch-depth: 0 + persist-credentials: false + + - uses: megaeth-labs/.github/.github/actions/release-candidate@main + if: steps.candidate.outputs.version != '' + with: + stage: refresh + token: ${{ steps.release-token.outputs.token }} + version: ${{ steps.candidate.outputs.version }} + pr_number: ${{ matrix.pr }} + version_file: Cargo.toml + version_pattern: toml + changelog_file: CHANGELOG.md + # Read-only: decides from the pushed commit whether it is a merged candidate # PR that targeted the default branch. One proposed with `base_commit` merges # into the release branch propose already cut, and routes to nothing here. From 8b78c051a653118aae2e1ea98cbe43449bafa5c6 Mon Sep 17 00:00:00 2001 From: Troublor <14846504+Troublor@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:29:02 +0800 Subject: [PATCH 4/5] ci(release): page open candidates without --slurp/--jq --- .github/workflows/on-release.yml | 6 +++--- .github/workflows/release-candidate.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml index 3b3a3b96..503f1a76 100644 --- a/.github/workflows/on-release.yml +++ b/.github/workflows/on-release.yml @@ -27,9 +27,9 @@ name: On Release # version is the commit SHA and the file sits under # release/vX.Y.Z/ — //release/vX.Y.Z/. # -# GCP_AUTH_KEY lives in the `publish` environment, whose deployment policy -# allows only v* tag refs: a job can read it only when the run's ref is a -# release tag, never from a branch build. +# GCP_AUTH_KEY lives in the `release` environment, which admits main, +# release-v* branches and v* tags with required reviewer approval. +# The registry job reads it when publishing artifacts for a release tag. on: release: diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index 41ba3480..cea3c0e3 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -106,10 +106,10 @@ jobs: GH_TOKEN: ${{ github.token }} DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} run: | - prs="$(gh api --method GET --paginate --slurp \ + prs="$(gh api --method GET --paginate \ "repos/$GITHUB_REPOSITORY/pulls" \ -f state=open -f base="$DEFAULT_BRANCH" -f per_page=100 \ - --jq '[.[][] | select(.head.ref | startswith("chore/release-candidate-")) | select(.head.repo.full_name == .base.repo.full_name) | .number]')" + | jq -cs '[.[][] | select(.head.ref | startswith("chore/release-candidate-")) | select(.head.repo.full_name == .base.repo.full_name) | .number]')" echo "prs=$(jq -c . <<< "$prs")" >> "$GITHUB_OUTPUT" refresh: From f69efb567a74156b09739b6b8cc53d6cd993df27 Mon Sep 17 00:00:00 2001 From: Troublor <14846504+Troublor@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:32:48 +0800 Subject: [PATCH 5/5] ci(release): clarify organization credential scope --- .github/workflows/release-candidate.yml | 2 +- .github/workflows/release-settle.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index cea3c0e3..48e1326b 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -13,7 +13,7 @@ name: Release Candidate # `route` never selects `cut` for it. # # Every write is made by the release GitHub App (mega-gutenberg), which reads -# MEGA_GUTENBERG_CLIENT_ID and MEGA_GUTENBERG_PK from repository scope, so no +# MEGA_GUTENBERG_CLIENT_ID and MEGA_GUTENBERG_PK from organization scope, so no # job here declares an environment. The decision to release is made by merging # the candidate PR, not by a click on the run. `route` is read-only and uses # the job token. diff --git a/.github/workflows/release-settle.yml b/.github/workflows/release-settle.yml index 743b8380..8813b0db 100644 --- a/.github/workflows/release-settle.yml +++ b/.github/workflows/release-settle.yml @@ -15,7 +15,7 @@ name: Release Settle # eligible release branch. # # Every write is made by the release GitHub App (mega-gutenberg), which reads -# MEGA_GUTENBERG_CLIENT_ID and MEGA_GUTENBERG_PK from repository scope, so +# MEGA_GUTENBERG_CLIENT_ID and MEGA_GUTENBERG_PK from organization scope, so # the PR-creating jobs declare no environment. Only the publish job waits on # the `release` environment, whose required reviewers authorise the tag and the # GitHub Release. The app is a bypass actor on the `v*` tag ruleset only; the