From 806b1ac921ceb8af344b5c7c3198f7aa39a75004 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Mon, 29 Jun 2026 23:13:09 +0200 Subject: [PATCH] =?UTF-8?q?ci:=20bypass=20size=20gate=20=E2=80=94=20cap=20?= =?UTF-8?q?self-merge=20PR=20size=20for=20review-bypass=20users?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @Luca-Timo is on main's review-bypass list so he can self-merge small bugfixes without waiting for a maintainer review. The bypass list alone is binary (he can merge anything), so this adds a complementary required status check that fails when a bypass user's PR exceeds a configured line-count threshold — blocking merge for genuine features while leaving small bugfixes flowing. How it works: - Trigger: pull_request_target (so the workflow runs in the base repo's context with permissions to write a check status — script never executes PR code, so fork-PR-attack-safe). - For PRs authored by a bypass user (default: @Luca-Timo): - linesChanged = additions + deletions - If ≤ LINE_LIMIT (300): check = success → bypass works → self-merge OK - If > LINE_LIMIT: check = failure → required-check gate blocks merge regardless of bypass; needs a maintainer review. - For everyone else: check = success ("not applicable"). They go through the normal review path and are unaffected. Both constants (LINE_LIMIT, BYPASS_USERS) are at the top of the workflow for easy tuning. After this lands on main, a separate API step adds 'bypass-size-gate' to the main branch's required_status_checks list so the gate is actually enforced. Until that's in place the check runs but doesn't block. --- .github/workflows/bypass-size-gate.yml | 70 ++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .github/workflows/bypass-size-gate.yml diff --git a/.github/workflows/bypass-size-gate.yml b/.github/workflows/bypass-size-gate.yml new file mode 100644 index 00000000..a4e742c2 --- /dev/null +++ b/.github/workflows/bypass-size-gate.yml @@ -0,0 +1,70 @@ +name: Bypass size gate + +# Caps how large a PR a "review-bypass" collaborator (e.g. @Luca-Timo) can +# self-merge without a maintainer review. The branch-protection bypass list +# alone is binary — once a user is on it they can merge anything without +# review. This workflow reports a REQUIRED status check that fails when a +# bypass user's PR exceeds the configured size threshold, which blocks the +# merge even with bypass enabled. Other contributors are unaffected (the +# check reports success for them so the required-check gate doesn't trip). +# +# To tune: edit LINE_LIMIT or BYPASS_USERS below. +# +# Trigger note: uses `pull_request_target` so the workflow has the elevated +# permissions of the base repo's GITHUB_TOKEN (read PR metadata, write +# checks). The script never executes code FROM the PR — it only reads +# metadata via the API — so this is safe against fork-PR attacks. + +on: + pull_request_target: + types: [opened, reopened, synchronize, ready_for_review] + +permissions: + pull-requests: read + checks: write + +jobs: + size-gate: + runs-on: ubuntu-latest + steps: + - name: Compute PR size and report check status + uses: actions/github-script@v7 + with: + script: | + // Tune these two constants if the policy shifts. + const LINE_LIMIT = 300; + const BYPASS_USERS = ['Luca-Timo']; + + const pr = context.payload.pull_request; + const author = pr.user.login; + const linesChanged = pr.additions + pr.deletions; + const filesChanged = pr.changed_files; + + let conclusion, title, summary; + + if (!BYPASS_USERS.includes(author)) { + // Not a bypass user — this gate doesn't apply to them. They + // go through normal review. Report success so the required + // check doesn't block their merge. + conclusion = 'success'; + title = 'Not applicable'; + summary = `This gate only restricts review-bypass for: ${BYPASS_USERS.join(', ')}. PRs from other authors (${author} here) go through the normal review path and are unaffected.`; + } else if (linesChanged <= LINE_LIMIT) { + conclusion = 'success'; + title = `OK — within bypass limit (${linesChanged} lines)`; + summary = `Small PR: ${linesChanged} lines changed across ${filesChanged} file(s). Within the ${LINE_LIMIT}-line self-merge limit for @${author}. Can be merged without a maintainer review.`; + } else { + conclusion = 'failure'; + title = `Too large for bypass (${linesChanged} lines)`; + summary = `Large PR: ${linesChanged} lines changed across ${filesChanged} file(s). Exceeds the ${LINE_LIMIT}-line self-merge limit for @${author} — needs an approving review from a maintainer before merge. Split into smaller PRs or wait for review.`; + } + + await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: 'bypass-size-gate', + head_sha: pr.head.sha, + status: 'completed', + conclusion, + output: { title, summary } + });