ci(release): authenticate main pushes with an admin RELEASE_TOKEN (#486)

Point semantic-release's version-bump push and the docs-changelog push at a RELEASE_TOKEN admin PAT so they clear the 16 required checks (default token is rejected). Falls back to the default token, so no change until the secret is set. Documents creating/rotating the token in RELEASE.md.
This commit is contained in:
SnapOtter
2026-07-11 12:49:02 +08:00
committed by GitHub
parent 4a5a6718e6
commit 2e91368816
2 changed files with 31 additions and 4 deletions
+13 -4
View File
@@ -37,7 +37,12 @@ jobs:
- name: Run semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# RELEASE_TOKEN is a fine-grained PAT (repo Contents/Issues/PRs: write)
# owned by an admin, so semantic-release's push of the chore(release)
# commit + tag clears branch protection (enforce_admins is off). Falls
# back to the default token if the secret is unset, so behaviour is
# unchanged until the secret exists.
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
run: npx semantic-release
- name: Check for new release
@@ -72,7 +77,7 @@ jobs:
- name: Update docs changelog
if: steps.notes.outputs.has_notes == 'true' && steps.check.outputs.version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.check.outputs.version }}
run: |
CHANGELOG="apps/docs/changelog.md"
@@ -100,8 +105,12 @@ jobs:
git config user.name "SnapOtter"
git config user.email "snapotter.hq@gmail.com"
git add "$CHANGELOG"
git commit -m "docs: update changelog for v${VERSION}" || true
git push origin HEAD:main || true
git commit -m "docs: update changelog for v${VERSION} [skip ci]" || true
# Authenticate this direct push explicitly: the job's checkout uses
# persist-credentials: false, so there is no ambient credential. The
# PAT's admin identity bypasses branch protection; if the secret is
# unset this no-ops (|| true) exactly as before.
git push "https://x-access-token:${RELEASE_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:main || true
echo "Docs changelog updated for v${VERSION}."
prebuilt:
+18
View File
@@ -15,6 +15,24 @@ SnapOtter ships a multi-arch container image to Docker Hub (`snapotter/snapotter
The `manifest` job targets the `publish-images` GitHub Environment, which requires a maintainer to approve the run before it proceeds. If Trivy fails at step 4, `manifest` never runs and no tags are published.
## One-time setup: `RELEASE_TOKEN`
Step 1 (`release`) pushes a version-bump commit and tag to `main`, which is protected. The default GitHub Actions token cannot push past the required status checks, so the workflow authenticates that push with `RELEASE_TOKEN`: a fine-grained PAT owned by an admin. Because `enforce_admins` is off on `main`, an admin identity bypasses the checks.
Create it once, and rotate it when it expires:
1. GitHub → **Settings → Developer settings → Fine-grained personal access tokens → Generate new token**.
2. Resource owner: `snapotter-hq`. Repository access: **Only select repositories → `snapotter-hq/SnapOtter`**.
3. Repository permissions: **Contents** read+write, **Issues** read+write, **Pull requests** read+write. semantic-release commits and tags (Contents) and comments on released issues/PRs (Issues, Pull requests). Metadata read is added automatically.
4. Expiration: your call, up to a year. Set a reminder to rotate before it lapses.
5. Store it as a repo secret:
```bash
gh secret set RELEASE_TOKEN --repo snapotter-hq/SnapOtter
```
Paste the token when prompted.
If `RELEASE_TOKEN` is missing, the workflow falls back to the default token, the push to `main` is rejected by branch protection, and the `release` job fails at the semantic-release step. So set the secret before cutting a release. A classic PAT with the `repo` scope also works and is simpler to configure, but it can reach every repo you have access to, so the fine-grained token is preferred.
## Cut a release
1. Make sure `main` is green and everything you want in the release is merged.