Skip to content

Required checks lint, build and test are satisfied by a skip when dependency-locks fails #3755

Description

@aviseth

The required checks lint, build, test (HTTPX2), test (Python 3.10) and test (Python 3.14) all come from jobs that declare needs: dependency-locks, and dependency-locks is not itself a required check. Because GitHub treats a skipped required check as satisfied, a failure in dependency-locks skips all five and the branch rule is met with none of them having run.

From About protected branches:

Required status checks must have a successful, skipped, or neutral status before collaborators can make changes to a protected branch.

The state in .github/workflows/ci.yml on main today:

job check name required needs
dependency-locks dependency lock freshness no
lint lint yes dependency-locks
build build yes dependency-locks
test test (Python 3.10 / 3.14) yes dependency-locks
test-httpx2 test (HTTPX2) yes dependency-locks

None of the five carries an always() or !cancelled() guard, so the default skip-on-upstream-failure behaviour applies. dependency-locks failing is not hypothetical — it is the job that fails when pyproject.toml and uv.lock disagree, which is exactly the situation where you would most want lint and the test suite to run.

The narrowest fix is to make the dependency it gates on a gate itself, by adding dependency lock freshness to the required checks in the ruleset. Nothing in the workflow changes, and a lock failure then blocks on its own terms rather than by silently withdrawing four other checks.

The alternative, if you would rather not grow the required list, is the aggregate-gate shape you already have elsewhere in the ecosystem: one job with if: always() that inspects needs.*.result and exits non-zero, required in place of the individual checks.

Two caveats on scope. Those five jobs also carry if: github.event_name == 'push' || github.event_name == 'merge_group' || github.event.pull_request.head.repo.fork, so on a same-repo pull request they are skipped by design and the real run happens in the merge queue; the exposure I am describing is inside the queue, where a dependency-locks failure would skip the rest of the run and the queue would see satisfied checks. And I can only read your rulesets, not classic branch protection, so if additional enforcement exists that I cannot see, this may already be covered.

Found with greenwash, a tool I wrote for auditing this specific failure mode; greenwash audit --repo openai/openai-python reproduces it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions