Skip to content

fix(ruff): stop ruff check from mutating the tree - #4

Merged
Jamie-BitFlight merged 1 commit into
mainfrom
fix/ruff-no-fix-in-ci
Aug 31, 2026
Merged

Jamie-BitFlight merged 1 commit into
mainfrom
fix/ruff-no-fix-in-ci

Conversation

@Jamie-BitFlight

Copy link
Copy Markdown
Contributor

The defect

pyproject.toml set fix = true under [tool.ruff]. That is a global default: every ruff check in this repo autofixes, including CI's lint job.

Reproduced on origin/main (b801d7b), clean checkout, uv sync, ruff 0.14.8 from uv.lock:

$ git status --porcelain           # clean
$ uv run ruff check --output-format=github packages/
EXIT=0
$ git status --porcelain
 M packages/ensure_uv/__init__.py

The lint job inserted the from __future__ import annotations that lint.isort.required-imports demands, reported success, and discarded the fix when the runner was torn down. The committed source was never lint-clean.

It is worse than a no-op: the file ruff check writes is not what ruff format wants — the inserted import gets no blank line after the module docstring, so ruff format --check exits 1 on it. CI only stayed green because the format and lint jobs run in separate checkouts.

$ uv run ruff format --check packages/     # on the ruff-check-rewritten file
unformatted: File would be reformatted
1 file would be reformatted, 2 files already formatted
EXIT=1

The change

  1. packages/ensure_uv/__init__.py — add the missing import and format it, so the committed tree is genuinely clean under both ruff check and ruff format --check. Applied with the repo's own pinned ruff (0.14.8, per uv.lock), not floating latest.

  2. Drop fix = true rather than only patching the CI call site. fix = true in shared config makes every consumer of that config a writer — CI, hooks, editors, ad-hoc invocations — so patching one caller leaves the rest broken. Removing it costs the local developer nothing: autofix is still one flag away (ruff check --fix), and requesting it at the call site is already this repo's convention — .pre-commit-config.yaml passes args: [--fix] to the ruff hook explicitly rather than relying on the config default.

  3. Pass --no-fix in the CI lint job as well. "This job must never mutate its checkout and report success" is a hard requirement of that job specifically, and stating it where it is required makes the job correct on its own terms instead of dependent on a config line a future edit could put back.

  4. unsafe-fixes = true kept. It is not part of this defect. It only widens which fixes apply once fixing has been requested, and with fix = true gone, fixing is always an explicit opt-in. Removing it would change what the existing pre-commit ruff hook fixes — a behaviour change unrelated to the problem here.

Proof that CI now fails on unclean input

Violation reintroduced (import deleted again), exact CI command run:

$ uv run ruff check --no-fix --output-format=github packages/
::error title=Ruff (I002),file=.../packages/ensure_uv/__init__.py,line=1,col=1::
  packages/ensure_uv/__init__.py:1:1: I002 Missing required import: `from __future__ import annotations`
EXIT=1
$ head -3 packages/ensure_uv/__init__.py    # file untouched
"""Pre-commit hook to ensure uv is installed and available."""

from .main import main

The config change alone is sufficient — the old command (no --no-fix) also now exits 1 and leaves the file untouched under the new config. The flag is the belt to that pair of braces.

Gate

All test.yml job commands pass on this branch: ruff format --check, ruff check --no-fix, ty check, ensure-uv, the import smoke test. prek run --all-files with no SKIP.

Pre-existing failure, not introduced here

prek run --all-files fails on origin/main and still fails on this branch, in packages/ensure_uv/version.py only:

baseline (origin/main) this branch
ruff hook Failed — 17 errors (14 fixed, 3 remaining) Failed — 16 errors (13 fixed, 3 remaining)
ruff-format hook Failed — 1 file reformatted Passed

Cause: version skew. .pre-commit-config.yaml pins ruff-pre-commit at v0.16.5 while uv.lock resolves ruff to 0.14.8, so the hook and CI run different linters. 0.16.5 rewrites # noqa: PLC0415 into the new # ruff: ignore[...] spelling and then still reports import-outside-top-level three times in version.py. Fixing that means bumping the locked ruff (uv remove / uv add) and migrating those suppressions — a separate change with its own behaviour implications, deliberately not folded into this PR.

This PR strictly reduces that baseline failure: the ruff-format hook goes Failed → Passed and one lint error is gone.

`fix = true` under `[tool.ruff]` applies to every `ruff check` invocation in
the repo, including CI. The lint job (`uv run ruff check --output-format=github
packages/`) was therefore rewriting its own checkout and exiting 0: it inserted
the `required-imports` entry missing from `packages/ensure_uv/__init__.py`,
reported success, and threw the fix away. The committed source was never
lint-clean, and the file ruff-check produced would itself have failed
`ruff format --check` (no blank line after the module docstring) had the two
jobs shared a checkout.

- Add the missing `from __future__ import annotations` so the committed tree is
  genuinely clean under both `ruff check` and `ruff format --check`.
- Drop `fix = true`. Autofix is now requested at the call site, which is already
  this repo's convention: the pre-commit ruff hook passes `args: [--fix]`
  explicitly, and a developer wanting autofix runs `ruff check --fix`.
- Pass `--no-fix` in the CI lint job so the "must never mutate" requirement is
  stated where it is required, not only inherited from shared config.

`unsafe-fixes = true` is kept: it only widens which fixes apply once fixing has
been explicitly requested, so it is no longer reachable from a plain
`ruff check`.

Verified: with the violation reintroduced, the CI lint command exits 1 and
leaves the file untouched.
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ed13b7a7-d9fb-4a79-b9d3-70b7f0bce34a


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-08-31T07:45:59.070383Z 271decd PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

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