Skip to content

ci: gate Ruff with a baseline of the existing findings - #281

Merged
mkb79 merged 1 commit into
masterfrom
ci/enable-ruff
Aug 7, 2026
Merged

ci: gate Ruff with a baseline of the existing findings#281
mkb79 merged 1 commit into
masterfrom
ci/enable-ruff

Conversation

@mkb79

@mkb79 mkb79 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Ruff has been configured in pyproject.toml for a while but never ran anywhere. Turning it on as-is fails immediately: 442 findings across 29 files, and 29 files the formatter has never touched.

Not a blanket ignore

A global ignore list would be an amnesty — a new violation of the same rule would then pass anywhere, including in files written from scratch, and among the 48 rules involved are members of B, S, ASYNC and RUF where a single hit can be a real defect. Instead each file's existing findings are pinned in per-file-ignores.

What that catches and what it does not:

New file with violations ✅ fails
A rule a file does not already break ✅ fails
A file becoming unformatted that was formatted ✅ fails
Another violation of a rule already listed for that same file ❌ passes

That last row is inherent: per-file-ignores keys on file and rule, not on individual findings, and Ruff has no native baseline yet (astral-sh/ruff#1149). The alternative that closes it is ruff check --add-noqa, which writes 442 # noqa comments into the source. That seemed the worse trade here, but it is a one-command switch if you disagree. The limitation is spelled out in the config comment rather than left for someone to discover.

Format without the churn

[tool.ruff.format].exclude lists the 29 files the formatter has never seen. ruff format --check is therefore blocking from day one, every new file is checked, and the legacy files leave the list one at a time. This avoids a ~1400-line reformat that would collide with the eight external pull requests still open.

The three test files from #280 were only unformatted for a day, so they are formatted here rather than added to the list.

Scope

Both jobs run over the whole repository rather than a list of directories, so pyi_entrypoint.py — which the release build uses — plus utils/ and the docs config are covered, and a new top-level file cannot slip past. Ruff skips .venv and .nox on its own.

audible.spec stays out: it is Python but not .py, so it would need an extension mapping. That is a separate decision.

Pinned on purpose

ruff==0.16.2 in the dev group. The enabled rule families pull in new stable rules on an upgrade, which would turn the gate red without anyone touching the code. Dependabot will propose bumps; a red bump PR is then the signal to deal with the new rules deliberately.

Verified

  • ruff check . and ruff format --check . both pass, 56 tests still green
  • ratchet probes: a new file with violations is caught by both jobs, and a new S101 in config.py — where S101 is not listed — fails as it should
  • the documented gap reproduced deliberately: an added G004 passes in models.py, where G004 is listed, and fails in tests/, where it is not

Next

197 of the findings are safe autofixes. Applying them, then dropping the corresponding codes from the baseline, is the obvious follow-up — deliberately not in this PR, so the gate lands first and the mechanical churn stays reviewable on its own.

Ruff has been configured in pyproject.toml for a while but never ran
anywhere. Turning it on as-is would fail immediately: 442 findings in 29
files, and 29 files the formatter has never touched.

Both jobs run over the whole repository rather than a list of directories,
so `pyi_entrypoint.py`, which the release build uses, `utils/` and the docs
config are covered too, and a new top-level file cannot slip past.

Rather than switch the rules off globally, pin what each file already breaks.
A global ignore list would be an amnesty: a new violation of the same rule
would then pass anywhere, including in files written from scratch. With
`per-file-ignores` a new file, and a rule a file does not already break, both
still fail.

The one thing this does not catch is another violation of a rule already
listed for that same file — `per-file-ignores` keys on file and rule, not on
individual findings. Ruff has no native baseline for that yet.

`ruff format` is gated the same way, through `[tool.ruff.format].exclude`.
That leaves the 25 legacy files alone while every new file is checked from
the start, and avoids a 1400-line reformat that would collide with the eight
external pull requests still open. The three test files added in #280 were
not in that state for long, so they are formatted here instead of listed.

Ruff itself is pinned. The enabled rule families pull in new stable rules on
an upgrade, which would turn the gate red without anyone touching the code.

Both run as separate jobs so a failure names which one, without the Python
matrix the tests need.

Shrinking this is the point: fix a rule in a file, drop its code from the
list, or run `ruff format` on one file and remove it from the exclude list.
197 of the findings are safe autofixes, which is the obvious next step.
@mkb79
mkb79 merged commit 08d8f91 into master Aug 7, 2026
7 checks passed
@mkb79
mkb79 deleted the ci/enable-ruff branch August 7, 2026 18:16
mkb79 added a commit that referenced this pull request Aug 7, 2026
The baseline added with #281 recorded 442 findings. 219 of them Ruff can fix
on its own without changing behaviour, so this applies them and shrinks the
baseline accordingly.

Fully gone, 23 rules: the type annotation modernization the project's own
target-version already implies (UP006/UP007/UP045, 98 findings), import
sorting (I001), docstring formatting (D2xx/D4xx), quote style (Q000),
redundant f-strings and conversions (F541, RUF010), and a handful of one-off
simplifications (UP009, UP015, UP025, UP032, UP034, UP035, PLR5501, RUF021,
RUF022, RUF100).

  442 findings  ->  230
  168 file/rule pairs in the baseline  ->  75
  29 files with findings  ->  23

Nothing was fixed with --unsafe-fixes; those 109 remain untouched because
they can change behaviour and deserve to be read one by one.

`ruff check --fix` reorders imports, which left `src/audible_cli/__init__.py`
unformatted even though it had been clean. It is formatted here and dropped
from the formatter's exclude list.

The remainder is dominated by G004 (78 f-strings in logging calls), D415 (60
docstring endings) and W291 (24 trailing whitespace), none of which Ruff will
fix on its own.
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