From 68c987fd3c047b31265969b73bb3bf1f45ab44cb Mon Sep 17 00:00:00 2001 From: Steven Gates Date: Mon, 7 Sep 2026 15:48:51 -0500 Subject: [PATCH 1/2] fix(release): fall back to GITHUB_TOKEN, and fail loudly when the PAT is missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A dry run only reads the repo, so requiring RELEASE_TOKEN for it was wrong — an unset secret made actions/checkout fail with "Input required and not supplied: token" before anything useful ran. Checkout and gh now fall back to the job's own GITHUB_TOKEN. A real run still needs the PAT to push and merge, so it is checked explicitly up front with a message that names the secret, rather than failing later mid-push. --- .github/workflows/release.yml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2dc036..c42f51f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,8 +10,10 @@ name: Release · build signed AAB # RELEASE variant — R8, resource shrinking, the baseline profile, #178's Firebase guard, signing — # so `bundleRelease` succeeding and `jarsigner -verify` passing is the gate this flow adds. # -# Secrets required: RELEASE_TOKEN, KEYSTORE_BASE64, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD, -# GOOGLE_SERVICES_JSON. +# Secrets: KEYSTORE_BASE64, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD, GOOGLE_SERVICES_JSON are +# needed by every run. RELEASE_TOKEN (a fine-grained PAT with contents + pull-requests write) is +# needed only by a real run, which pushes a branch and merges its own PR; a dry run falls back to +# the job's GITHUB_TOKEN because it only ever reads. on: workflow_dispatch: @@ -33,15 +35,23 @@ jobs: name: Bump · build · sign runs-on: ubuntu-latest env: - GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} + GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} steps: + # A real run pushes a branch and merges a PR, which github-actions[bot] cannot do here. + # Say so now, by name, instead of dying halfway through with a 403. + - name: Require the PAT for a real run + if: ${{ !inputs.dry_run && secrets.RELEASE_TOKEN == '' }} + run: | + echo "::error::RELEASE_TOKEN is not set. A real release run pushes the bump branch and merges its own PR, which needs a fine-grained PAT with contents and pull-requests write. Add it as a repository secret, or re-run with dry_run to build only." + exit 1 + - name: Checkout main uses: actions/checkout@v6 with: ref: main fetch-depth: 0 - token: ${{ secrets.RELEASE_TOKEN }} + token: ${{ secrets.RELEASE_TOKEN || github.token }} # Read the version rather than accepting it as input — the owner should never type a # versionCode, and a typo here ships the wrong number to Play. @@ -160,6 +170,12 @@ jobs: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} run: | set -euo pipefail + for required in KEYSTORE_BASE64 KEYSTORE_PASSWORD KEY_ALIAS KEY_PASSWORD GOOGLE_SERVICES_JSON; do + if [[ -z "${!required}" ]]; then + echo "::error::Secret $required is not set. Every run needs the signing material." + exit 1 + fi + done printf '%s' "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/upload.jks" printf '%s' "$GOOGLE_SERVICES_JSON" > app/google-services.json { From a669945be2b7909a0bd22b584d1c3a46ceee0657 Mon Sep 17 00:00:00 2001 From: Steven Gates Date: Mon, 7 Sep 2026 15:49:56 -0500 Subject: [PATCH 2/2] fix(release): check the PAT inside the step, not in its if condition `secrets` is not an allowed context in a step-level `if:`, so the guard I just added would not have evaluated as intended. Gate the step on dry_run alone and test the secret in the shell, where it is a normal env var. --- .github/workflows/release.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c42f51f..396db4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,12 +39,18 @@ jobs: steps: # A real run pushes a branch and merges a PR, which github-actions[bot] cannot do here. - # Say so now, by name, instead of dying halfway through with a 403. + # Say so now, by name, instead of dying halfway through with a 403. The check lives in the + # shell rather than in `if:` because `secrets` is not an allowed context in a step condition. - name: Require the PAT for a real run - if: ${{ !inputs.dry_run && secrets.RELEASE_TOKEN == '' }} + if: ${{ !inputs.dry_run }} + env: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | - echo "::error::RELEASE_TOKEN is not set. A real release run pushes the bump branch and merges its own PR, which needs a fine-grained PAT with contents and pull-requests write. Add it as a repository secret, or re-run with dry_run to build only." - exit 1 + if [[ -z "$RELEASE_TOKEN" ]]; then + echo "::error::RELEASE_TOKEN is not set. A real release run pushes the bump branch and merges its own PR, which needs a fine-grained PAT with contents and pull-requests write. Add it as a repository secret, or re-run with dry_run to build only." + exit 1 + fi + echo "RELEASE_TOKEN is present." - name: Checkout main uses: actions/checkout@v6