Skip to content

CI ruff check is red on every branch: unpinned uvx ruff picked up 0.16's expanded default rule set #201

Description

@db-tycoon-stephen

Summary

The ruff job in ci.yml fails on every PR and on main. No code change caused
it. CI runs ruff unpinned, ruff 0.16.0 expanded its default rule set, and the
repo declares no explicit select — so CI silently adopted hundreds of new rules
that the codebase was never linted against.

The code is clean under every ruff version the repo actually pins.

Reproduction

Run against the same source tree at different ruff versions:

# main / PR #200 branch
uvx ruff@0.15.20 check src tests   # All checks passed!
uvx ruff@0.16.2  check src tests   # Found 271 errors. (166 fixable)

# v0.1.12 branch
uvx ruff@0.14.0  check src tests   # clean
uvx ruff@0.15.20 check src tests   # clean
uvx ruff@0.16.2  check src tests   # Found 106 errors.

The 271/166 figures reproduce CI exactly (run 31203974464). The two branches
differ in count only because their source trees differ post-M2 — the cause is
identical.

Root cause

Three ruff versions are declared across the repo, and CI honours none of them:

Where Version Honoured by CI?
.pre-commit-config.yaml (ruff-pre-commit rev) v0.14.0 no
pyproject.toml [dependency-groups] dev ruff==0.15.20 no
ci.ymluvx ruff check src tests resolves to latest (0.16.2) this one runs

uvx ruff ignores the project's own pin entirely, so ruff==0.15.20 in the dev
group is dead weight today.

Compounding it: pyproject.toml has [tool.ruff] (line-length, target-version)
and [tool.ruff.lint.per-file-ignores], but no [tool.ruff.lint] select. With
no explicit selection the project inherits whatever upstream calls "default" — so
an upstream release can change our lint gate without a commit on our side. That's
the latent defect; the 0.16 bump is just the first time it fired.

Rule breakdown (main @ 0.16.2, 271 total)

71  UP045    non-pep604-annotation-optional     (Optional[X] -> X | None)
48  UP017    datetime-timezone-utc              (timezone.utc -> datetime.UTC)
38  I001     unsorted-imports                   [fixable]
37  BLE001   blind-except                       (not autofixable)
 9  B008     function-call-in-default-argument  (Typer's Option()/Argument() idiom)
 5  SIM117   multiple-with-statements
 5  E402     module-import-not-at-top
 4  RUF100   unused-noqa
 3  UP035 / 1 UP037 / 1 SIM102 / 1 C408

Two of these need judgement, not autofix:

  • B008 flags typer.Option(...) / typer.Argument(...) in defaults. That is
    the required Typer idiom — this rule should be ignored project-wide, not "fixed".
  • BLE001 (37 hits) is a real design question about our best-effort except Exception blocks. Note bandit in pre-commit already skips B110/B112 for
    the same blocks, so there's precedent for accepting them deliberately.
  • E402/RUF100 interact with the existing per-file-ignores for cli.py and
    init.py; 0.16 reports the noqa: E402 there as unused. Fixing needs care so
    the two aren't fought over.

Proposed fix

Two parts — the first stops the bleeding, the second prevents recurrence.

1. Make CI honour the existing pin. In ci.yml, change:

-        run: uvx ruff check src tests
+        run: uv run ruff check src tests

uv run uses the ruff==0.15.20 already in the dev group, so the pin becomes
authoritative instead of decorative and CI goes green immediately. Also bump the
ruff-pre-commit rev from v0.14.0 to match, so all three agree on one version.

2. Declare an explicit rule set. Add [tool.ruff.lint] select = [...] to
pyproject.toml pinning the rules we intend to enforce. Until this exists, any
ruff upgrade is an unreviewed lint-policy change.

Then adopt 0.16 deliberately as its own change: --fix the mechanical 166,
ignore B008, and make a real call on BLE001.

Notes

  • docs, pytest (3.12 + 3.13), build, gitleaks, and both template smokes
    are green throughout. Only ruff is affected.
  • CONTRIBUTING.md tells contributors to run uvx ruff check src tests, which
    reproduces the CI failure on a clean checkout. Update it alongside the fix.
  • Surfaced while reviewing Contributor templates: issue forms, PR template, Code of Conduct, README #200, which is unaffected (adds no Python).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtech-debtTest infrastructure, refactors, quality work that isn't a bug or feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions