You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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.yml → uvx 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.
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:
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, ignoreB008, 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.
Summary
The
ruffjob inci.ymlfails on every PR and onmain. No code change causedit. 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 rulesthat 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:
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:
.pre-commit-config.yaml(ruff-pre-commitrev)v0.14.0pyproject.toml[dependency-groups] devruff==0.15.20ci.yml→uvx ruff check src testsuvx ruffignores the project's own pin entirely, soruff==0.15.20in the devgroup is dead weight today.
Compounding it:
pyproject.tomlhas[tool.ruff](line-length, target-version)and
[tool.ruff.lint.per-file-ignores], but no[tool.ruff.lint] select. Withno 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)
Two of these need judgement, not autofix:
B008flagstyper.Option(...)/typer.Argument(...)in defaults. That isthe required Typer idiom — this rule should be ignored project-wide, not "fixed".
BLE001(37 hits) is a real design question about our best-effortexcept Exceptionblocks. Notebanditin pre-commit already skipsB110/B112forthe same blocks, so there's precedent for accepting them deliberately.
E402/RUF100interact with the existingper-file-ignoresforcli.pyandinit.py; 0.16 reports thenoqa: E402there as unused. Fixing needs care sothe 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:uv runuses theruff==0.15.20already in the dev group, so the pin becomesauthoritative instead of decorative and CI goes green immediately. Also bump the
ruff-pre-commitrev fromv0.14.0to match, so all three agree on one version.2. Declare an explicit rule set. Add
[tool.ruff.lint] select = [...]topyproject.tomlpinning the rules we intend to enforce. Until this exists, anyruff upgrade is an unreviewed lint-policy change.
Then adopt 0.16 deliberately as its own change:
--fixthe mechanical 166,ignoreB008, and make a real call onBLE001.Notes
docs,pytest(3.12 + 3.13),build,gitleaks, and both template smokesare green throughout. Only
ruffis affected.CONTRIBUTING.mdtells contributors to runuvx ruff check src tests, whichreproduces the CI failure on a clean checkout. Update it alongside the fix.