Skip build-gate on successful compile-only runs so they show pending (not red) while staying merge-blocked

A successful compile-only run (fork pull_request, *-reviewed-for-compile tag,
or workflow_dispatch with skip_tests=true) previously made build-gate exit 1:
the required check went red AND the whole Actions run showed as failed, even
though compilation succeeded. That reads as breakage when nothing broke.

Fork pull_request runs get a read-only GITHUB_TOKEN (permissions: is capped to
read-only regardless of what the workflow requests), so the Commit Statuses and
Checks APIs both 403 there — a posted 'neutral'/status cannot mark the check.
And neutral would not block merge anyway (GitHub treats neutral/skipped/success
as passing for required checks). The only state that is both non-red and still
merge-blocking is a required check that is never reported: it sits 'Expected —
waiting' (pending).

So build-gate is now SKIPPED via a job-level if: for exactly the successful
compile-only case. A required check whose job never starts stays pending, so the
PR remains un-mergeable without a red X and the run is not marked failed. This
must be a job-level skip (job never starts): a job that runs and reports
conclusion 'skipped' counts as PASSED and would let the PR merge.

The guard is deliberately narrow — !(build.result == success && skip_tests ==
true) — so a compile-only build that FAILED still runs the gate and goes red,
and the trivial-change skip still runs and goes green (docs-only PRs are not
left pending forever). The now-unreachable compile-only branch in the gate body
is removed. Merge qualification for fork code continues to come from a
maintainer pushing a *-reviewed-for-build tag (full trusted build with tests).
compile-gate (advisory) still runs and goes green, giving a clear positive
compile signal beside the pending build-gate.

Assisted-By: Claude Opus 4.8
This commit is contained in:
Axel Uhl committed 2026-08-28 12:08:59 +02:00
1 parent e0679de32d
commit 51403fbe1f
1 file changed
+19 -7
+19 -7
View File
@@ -307,13 +307,28 @@ jobs:
# build-gate is the REQUIRED status check in branch protection. It must go
# green ONLY for a genuine full build (tests actually ran) or a legitimate
# trivial-change skip. A compile-only run (skip_tests true, e.g. a
# *-reviewed-for-compile tag or a workflow_dispatch with skip_tests=true) must
# NOT satisfy it — so those cannot qualify a PR for merge.
# trivial-change skip.
#
# A SUCCESSFUL compile-only run (build succeeded AND skip_tests true — e.g. a
# fork pull_request, a *-reviewed-for-compile tag, or a workflow_dispatch with
# skip_tests=true) must NOT qualify a PR for merge, but should also NOT show as
# a failed/red run. So for that one case this job is SKIPPED via its if:. A
# required check whose job never starts is left "Expected — waiting" (pending):
# the PR stays un-mergeable WITHOUT a red X, and the workflow run is not marked
# failed. Merge qualification for fork code then comes only from a maintainer
# pushing a *-reviewed-for-build tag (full trusted build with tests).
#
# IMPORTANT: this must be a job-level skip (job never starts), NOT run-then-
# report-skipped — a job that runs and reports conclusion "skipped" counts as
# PASSED for branch protection and would let the PR merge. Also note the guard
# is deliberately narrow: it fires ONLY for a *successful* compile-only build.
# A compile-only build that FAILED (build.result != success) still runs the
# gate below and goes red, and the trivial-change skip still runs and goes
# green — so docs-only PRs are not left pending forever.
build-gate:
permissions: {}
needs: [changes, build]
if: always()
if: ${{ !(needs.build.result == 'success' && needs.build.outputs.skip_tests == 'true') }}
runs-on: ubuntu-latest
steps:
- run: |
@@ -321,9 +336,6 @@ jobs:
echo "Full build with tests passed."
elif [[ "${{ needs.build.result }}" == "skipped" && "${{ needs.changes.outputs.should_run }}" == "false" ]]; then
echo "No relevant changes — skipping is OK."
elif [[ "${{ needs.build.result }}" == "success" && "${{ needs.build.outputs.skip_tests }}" == "true" ]]; then
echo "Compile-only build (tests skipped) does not satisfy the required check."
exit 1
else
echo "Build failed or was cancelled."
exit 1