feat: one required status check that fails closed - #66
Merged
Conversation
Branch protection is about to go on across the org, and it needs a check name that is stable and that actually gates. Naming the real jobs gives neither. Names are not stable. Matrix legs are named after their inputs, so a required context can stop reporting because a component was deleted — and a required context that never reports leaves every pull request Pending forever, from a change that looks unrelated to CI. Naming jobs also cannot keep up: a job added to a workflow is not automatically required, so the branch-protection list is a hand-kept copy of the CI graph that drifts in the direction of less coverage. This adds one gate job per PR-triggered workflow, depending on every other job in it, delegating to the shared action in nanohype/.github. That single name becomes the only required check. Two details carry the whole thing: `if: always()` — without it a failed dependency SKIPS the gate, and GitHub counts a skipped check as passing for branch protection, so the gate would report green in exactly the case it exists to catch. Restricted to pull_request — the gate treats a skipped dependency as a failure, and a job carrying `if: github.event_name == 'pull_request'` is legitimately skipped on a push to main. The gate exists to gate merges, and merges come from pull requests. The shared action refuses to pass on a dependency that is not success (skipped and cancelled included), on an empty needs list, and on any job in the workflow that the gate does not watch. That last check is what keeps the hand-written needs list honest as jobs are added. No repository settings change here. The gate blocks nothing until it is named as a required check.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Groundwork for turning branch protection on across the org. No repository settings change here — the gate blocks nothing until it is named as a required check.
Why one gate instead of naming the real jobs
Names are not stable. Matrix legs are named after their inputs.
landing-zonepublishes 144 check names, ~120 of them likeValidate (components/aws/tenant-substrate). Delete a component and any required context naming it never reports again — and a required context that never reports leaves every PR Pending forever, from a change that looks unrelated to CI.Naming jobs cannot keep up. A job added to a workflow is not automatically required. The branch-protection list is a hand-maintained copy of the CI graph, kept in a second place, drifting toward less coverage.
One gate per workflow gives one stable required name, with coverage checked by the gate rather than by whoever last edited settings.
Two details carry it
if: always()— without it a failed dependency skips the gate, and GitHub counts a skipped check as passing for branch protection. From the docs: "A job that is skipped will report its status as 'Success'. It will not prevent a pull request from merging, even if it is a required check." So the gate would report green in exactly the case it exists to catch.incident-responsehad this bug for real (incident-response#76).Restricted to
pull_request— the gate treats a skipped dependency as a failure, and a job carryingif: github.event_name == 'pull_request'is legitimately skipped on a push to main. Three repos have such jobs, including landing-zone's entireplanmatrix. The gate exists to gate merges, and merges come from PRs; on push it is skipped and gates nothing.What the shared action refuses to pass on
nanohype/.github#26, pinned by SHA:
successskipped/cancelled/neutralare green to GitHub; here they are notneeds:emptyneeds:The third keeps the hand-written
needs:list honest as jobs are added — it is what caughtincident-response's gate silently missing itsdocsjob.Coverage was verified mechanically before commit: the gate parses, watches every other job in its workflow, carries
always(), and destroys no existing job. Plan:~/.claude/plans/repo-settings-asserter.md.