From ca3159b484792c0d9d05070da96b4ec9a55648ed Mon Sep 17 00:00:00 2001 From: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:13:55 +0200 Subject: [PATCH] fix(impl-merge): use ADMIN_TOKEN PAT so --admin can bypass main ruleset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to c662f83 (added --admin to gh pr merge). That alone is not enough: GitHub's main-branch ruleset bypass list contains only RepositoryRole=admin (mode: pull_request). The default GITHUB_TOKEN runs as github-actions[bot] with `write` role — not admin — so the merge API call still returns: GraphQL: Repository rule violations found 3 of 3 required status checks are expected. To bypass, the merge must be authenticated as a user/identity that holds the admin role. Solution: route only the merge step through a repository-admin PAT stored as `ADMIN_TOKEN`. All other steps in impl-merge.yml (and other impl-* workflows) keep using GITHUB_TOKEN. Behavior: - If ADMIN_TOKEN is set: `gh pr merge --admin` runs as the PAT owner (admin), the bypass actor matches, ruleset is bypassed, merge succeeds. - If ADMIN_TOKEN is unset: falls back to GITHUB_TOKEN. Workflow emits a warning at the top of the merge step and the merge still fails with the same ruleset error as before — no regression, just clearer signal in the run log. Setup required after merging this PR: 1. Create a fine-grained PAT (Settings → Developer settings → Personal access tokens → Fine-grained), repository = anyplot, permissions: Contents: Read+Write, Pull requests: Read+Write, Administration: Read+Write, Metadata: Read. 2. Settings → Secrets and variables → Actions → New repository secret named `ADMIN_TOKEN`, value = the PAT. Considered alternatives: - Add github-actions[bot] as bypass actor on the ruleset (would let *any* workflow run bypass main protection — broader blast radius) - Remove the 3 required checks from the ruleset (weakens protection for human PRs as well) - Push from impl-generate via PAT so CI triggers naturally (cleanest semantically but requires changes in 3 workflows + same PAT management overhead) The PAT-only-for-merge approach scopes bypass to one workflow step while leaving everything else under GITHUB_TOKEN. --- .github/workflows/impl-merge.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/impl-merge.yml b/.github/workflows/impl-merge.yml index 5fdd92d802..1007ae099d 100644 --- a/.github/workflows/impl-merge.yml +++ b/.github/workflows/impl-merge.yml @@ -178,10 +178,20 @@ jobs: - name: Merge PR to main (with retry) if: steps.check.outputs.should_run == 'true' env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # ADMIN_TOKEN: PAT with admin scope from a repo-admin user, used so + # that `gh pr merge --admin` can bypass the main-branch ruleset + # (required-status-checks). Falls back to GITHUB_TOKEN if not set so + # the workflow still runs and fails with a clear ruleset error + # instead of an opaque auth error. + GH_TOKEN: ${{ secrets.ADMIN_TOKEN || secrets.GITHUB_TOKEN }} PR_NUM: ${{ steps.check.outputs.pr_number }} REPOSITORY: ${{ github.repository }} + HAS_ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN != '' }} run: | + if [ "$HAS_ADMIN_TOKEN" != "true" ]; then + echo "::warning::ADMIN_TOKEN secret is not set — merge will fail if main ruleset enforces required status checks. Add a fine-grained PAT with Contents:Write + Pull requests:Write + Administration:Read+Write as repo secret ADMIN_TOKEN." + fi + MAX_ATTEMPTS=5 for attempt in $(seq 1 $MAX_ATTEMPTS); do @@ -197,6 +207,9 @@ jobs: # downstream CI workflows (Run Linting / Run Tests / Run Frontend # Tests), so impl PRs never get those checks. The pipeline already # gates merge behind the AI quality review threshold. + # + # Bypass only works if the token has admin role. GITHUB_TOKEN is + # only `write`, so a repo-admin PAT is required (ADMIN_TOKEN). if gh pr merge "$PR_NUM" \ --repo "$REPOSITORY" \ --squash \