Spaces:
Runtime error
Runtime error
| name: Lock released branch | |
| # Two responsibilities (defense in depth β Hard Rule #18 enforcement): | |
| # | |
| # 1. `on: release: published` β when a GitHub Release publishes tag v3.X.Y, | |
| # apply branch protection (lock_branch + enforce_admins) to release/v3.X.Y | |
| # so no further commits can land on a shipped version. To reopen later: | |
| # gh api -X DELETE repos/<owner>/<repo>/branches/release/<tag>/protection | |
| # | |
| # 2. `on: push: branches: ['release/v*']` β verify that no push lands on a | |
| # release/* branch whose matching tag already exists. This is the preventive | |
| # guard: if the lock didn't apply (workflow bug, missing PAT, race), this | |
| # job FAILS the push run so the operator gets paged immediately. | |
| # | |
| # `permissions:` cannot grant the `Administration` scope to GITHUB_TOKEN β that | |
| # scope only exists on PATs. Set BRANCH_LOCK_TOKEN as a repo secret pointing to | |
| # a PAT/fine-grained token with `Administration: read & write`. Without it, the | |
| # lock step will fail loudly (which is what we want β silent failure caused the | |
| # v3.8.3 incident on 2026-05-26 where 6 commits landed post-release). | |
| on: | |
| release: | |
| types: [published] | |
| push: | |
| branches: | |
| - "release/v*" | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Tag of the released version (e.g. v3.8.2)" | |
| required: true | |
| type: string | |
| permissions: | |
| contents: read | |
| jobs: | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Job 1 β Lock the release branch when a Release is published. | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| lock-branch: | |
| if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Lock release/<tag> branch | |
| env: | |
| # Administration scope is required to PUT branch protection. Default | |
| # GITHUB_TOKEN cannot do this β operator must provision BRANCH_LOCK_TOKEN. | |
| GH_TOKEN: ${{ secrets.BRANCH_LOCK_TOKEN }} | |
| TAG: ${{ github.event.release.tag_name || inputs.tag }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${GH_TOKEN}" ]; then | |
| echo "::error::BRANCH_LOCK_TOKEN secret is not set. Create a PAT with Administration:write and add it as repo secret." | |
| exit 1 | |
| fi | |
| if [ -z "${TAG}" ]; then | |
| echo "::error::No tag provided; cannot determine release branch." | |
| exit 1 | |
| fi | |
| BRANCH="release/${TAG}" | |
| echo "Target branch: ${BRANCH} (repo: ${REPO})" | |
| if ! gh api "repos/${REPO}/branches/${BRANCH}" >/dev/null 2>&1; then | |
| echo "::warning::Branch ${BRANCH} not found β nothing to lock." | |
| exit 0 | |
| fi | |
| echo "Applying lock_branch protection to ${BRANCH}..." | |
| gh api -X PUT "repos/${REPO}/branches/${BRANCH}/protection" --input - <<'JSON' | |
| { | |
| "required_status_checks": null, | |
| "enforce_admins": true, | |
| "required_pull_request_reviews": null, | |
| "restrictions": null, | |
| "lock_branch": true, | |
| "allow_force_pushes": false, | |
| "allow_deletions": false | |
| } | |
| JSON | |
| LOCKED=$(gh api "repos/${REPO}/branches/${BRANCH}/protection" \ | |
| --jq '.lock_branch.enabled') | |
| if [ "${LOCKED}" != "true" ]; then | |
| echo "::error::Failed to confirm lock on ${BRANCH} (lock_branch=${LOCKED})." | |
| exit 1 | |
| fi | |
| echo "β ${BRANCH} is now locked (read-only)." | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Job 2 β Preventive guard: fail if a push lands on release/vX.Y.Z whose | |
| # tag already exists. This catches the case where the lock didn't apply | |
| # (PAT missing, race window, workflow bug) and pages the operator. | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| guard-no-push-after-release: | |
| if: github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Reject push if matching release tag exists | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| REF: ${{ github.ref_name }} | |
| run: | | |
| set -euo pipefail | |
| # Extract version from ref: release/v3.8.3 -> v3.8.3 | |
| if [[ ! "${REF}" =~ ^release/(v[0-9]+\.[0-9]+\.[0-9]+)$ ]]; then | |
| echo "Ref ${REF} does not match release/vX.Y.Z β nothing to guard." | |
| exit 0 | |
| fi | |
| TAG="${BASH_REMATCH[1]}" | |
| echo "Checking if tag ${TAG} already exists on ${REPO}..." | |
| if gh api "repos/${REPO}/git/refs/tags/${TAG}" >/dev/null 2>&1; then | |
| echo "::error::Hard Rule #18 violation β push to ${REF} but tag ${TAG} is already released." | |
| echo "::error::Hotfixes for a released version must go on a NEW branch: release/v$(echo "${TAG#v}" | awk -F. '{$3=$3+1; print $1"."$2"."$3}' OFS=.)" | |
| echo "::error::To undo this push: revert the offending commits, or contact an admin to lock the branch if it wasn't already." | |
| exit 1 | |
| fi | |
| echo "β No release tag for ${TAG} yet β push is OK." | |