Repository navigation
fix(compliance): stop the gate job's name from satisfying its own required check - #73
Merged
Merged
Conversation
…uired check
The org ruleset requires a status check named "Check CLA/DCO". Two different
things carried that exact name: the commit status policy_selector.py posts
after actually verifying the contributor, and this workflow's job, which
carries it merely by finishing.
A required status check is satisfied by EITHER a commit status or a check run
of that name. Verified experimentally rather than assumed — a repo-level
ruleset on a throwaway branch requiring a context that only a job produced
gave mergeStateStatus=CLEAN with zero commit statuses on the head SHA.
So "the workflow ran" was indistinguishable from "the CLA was verified". That
was survivable only because the script always posts a status before exiting.
It does not always succeed at it: set_commit_status ignores github_api's return
value, and github_api returns None on every failure, so a failed status POST
leaves the job green, the check run green, and the PR mergeable having verified
nothing. Fail-open, silently.
Renaming the job means only a real commit status can satisfy the gate. No
verification, no pass.
Blast radius measured, not estimated:
* Of 305 open PRs on gated default branches, 99 are satisfied by both a
status and the check run, 2 by a status alone, and ZERO by the check run
alone — so no open PR loses its passing check. (204 have neither and are
already blocked, unaffected.)
* No repo-level ruleset and no classic branch protection on any of the 84
gated repos requires this context; the org ruleset is the sole enforcer.
* The ruleset's workflows rule pins the workflow FILE path, not the job name,
so required-workflow enforcement is unaffected.
* The only references to the string anywhere are STATUS_CONTEXT and this job
name; nothing queries the check by name.
Known cost: this cannot be tested before merging. The ruleset pins this
workflow at refs/heads/main, so the usual trick of pointing a branch's sweeper
checkout at itself does not reach the gate path. First exercise is a live PR
event. Rollback is this one line reverted, effective on the next PR event
rather than the next cron.
The comment added at STATUS_CONTEXT is the other half of the guard: collapsing
these two names back together — from either side — reopens the hole.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
The org ruleset requires a status check named
Check CLA/DCO. Two different things carried that exact name:policy_selector.pyposts after actually verifying the contributorA required status check is satisfied by either a commit status or a check run of that name. So "the workflow ran" was indistinguishable from "the CLA was verified."
Verified, not assumed
I didn't want to act on inference, so I tested the semantics in isolation — a repo-level ruleset on a throwaway branch of a disposable fixture repo, requiring a context that only a job produced, no commit status ever posted:
A green check run alone satisfies the requirement. Probe fully cleaned up afterwards.
Why it matters today
This was survivable only because the script always posts a status before exiting. It does not always succeed at it:
set_commit_statusignoresgithub_api's return value, andgithub_apireturnsNoneon every failure mode. So a failed status POST leaves the job green, the check run green, and the PR mergeable having verified nothing. Fail-open, silently, with no signal.Renaming the job means only a real commit status can satisfy the gate. No verification, no pass.
Blast radius — measured
workflowsruleSTATUS_CONTEXTand this job name; nothing queries the check by nameNo open PR loses a passing check. Merged PRs are never re-evaluated. Existing check runs are immutable, so nothing already green turns red.
Test plan
scripts/policy_selector.pydiff is comment-onlyHonest caveats
No pre-merge verification path. The ruleset pins this workflow at
refs/heads/main, so the usual trick — pointing a branch's sweeper checkout at itself and dispatching — does not reach the gate path. First real exercise is a live PR event. This is the only change in this series without a pre-merge test, and that's a genuine cost rather than a formality.Rollback is this one line reverted, effective on the next PR event rather than the next cron, so exposure is minutes.
Cosmetic churn. Old head SHAs keep their
Check CLA/DCOcheck run; new ones getCompliance Gate. Both appear in the checks list for a while. Harmless, untidy.What this does not fix. The gate still exits 0 on any path that writes no status — the PR is now correctly blocked rather than wrongly mergeable, but nobody is notified. That's tracked separately.
The comment added at
STATUS_CONTEXTis the other half of the guard: collapsing these two names back together, from either side, reopens the hole.🤖 Generated with Claude Code