Skip to content

fix(compliance): stop the gate job's name from satisfying its own required check - #73

Merged
aabusair merged 1 commit into
mainfrom
fix/decouple-gate-job-name-from-status-context
Sep 15, 2026
Merged

aabusair merged 1 commit into
mainfrom
fix/decouple-gate-job-name-from-status-context

Conversation

@aabusair

Copy link
Copy Markdown
Collaborator

The org ruleset requires a status check named Check CLA/DCO. Two different things carried that exact name:

  1. The commit status policy_selector.py posts after actually verifying the contributor
  2. This workflow's job, which carried it merely by finishing

A 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:

CheckRun  ZZZ-Probe-Context -> SUCCESS
commit statuses: []  total_count=0
required context: ZZZ-Probe-Context

mergeable=MERGEABLE   mergeStateStatus=CLEAN

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_status ignores github_api's return value, and github_api returns None on 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

Check Result
Open PRs (305 governed) passing via check run alone 0 — 99 have both, 2 status-only, 204 neither
Repo-level rulesets on the 84 gated repos requiring this context none
Classic branch protections requiring it none
Ruleset's workflows rule Pins the file path, not the job name — required-workflow enforcement unaffected
References to the string anywhere in the repo Only STATUS_CONTEXT and this job name; nothing queries the check by name

No 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

  • Semantics proven experimentally in isolation (above), then cleaned up
  • Blast radius measured against every gated repo and all 305 governed open PRs
  • 57 tests still green; workflow YAML validated
  • Functional change is one line — the scripts/policy_selector.py diff is comment-only
  • Cannot be tested before merge. See below.

Honest 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/DCO check run; new ones get Compliance 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_CONTEXT is the other half of the guard: collapsing these two names back together, from either side, reopens the hole.

🤖 Generated with Claude Code

…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>
@aabusair
aabusair merged commit e8fcb16 into main Sep 15, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant