Skip to content

build: upgrade ruff to 0.16.0 - #69

Merged
dariero merged 3 commits into
mainfrom
codex/bump-ruff
Jul 27, 2026
Merged

build: upgrade ruff to 0.16.0#69
dariero merged 3 commits into
mainfrom
codex/bump-ruff

Conversation

@dariero

@dariero dariero commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Upgrades ruff from 0.15.21 to 0.16.0. This is a major bump under Ruff's own versioning scheme, not a minor one, and it changes what ruff format discovers.

Version delta

Package Before After Delta Constraint
ruff 0.15.21 0.16.0 major under Ruff's scheme >=0.15.21,<0.16 to >=0.16.0,<0.17 (pyproject.toml:24)

Intervening stable releases: 0.15.22 and 0.16.0.

Why "major" for a numerically-minor bump. Ruff's docs/versioning.md at tag 0.16.0, verbatim:

Ruff uses a custom versioning scheme that uses the minor version number for breaking changes and the patch version number for bug fixes. Ruff does not yet have a stable API; once Ruff's API is stable, the major version number and semantic versioning will be used.

Under "Minor version increases will occur when:", three bullets fire for this exact upgrade: "Support for a new file type is promoted to stable", "Stable rules are added to the default set", and under Formatter, "The stable style changed". The contrast is decisive because the patch list contains "Support for a new file type is added in preview" - which is precisely where Markdown formatting sat at 0.15.x.

$ git diff pyproject.toml
-    "ruff>=0.15.21,<0.16",
+    "ruff>=0.16.0,<0.17",

Lock regenerated with the second form of .agents/skills/upgrade-dependencies/SKILL.md step 5, not --upgrade:

$ uvx --from uv==0.11.30 uv lock --python 3.14 --prerelease disallow
Resolved 56 packages in 174ms
Updated ruff v0.15.21 -> v0.16.0

No transitive churn - ruff ships as a self-contained binary wheel:

added:   []
removed: []
moved:   {'ruff': ('0.15.21', '0.16.0')}
total packages: 56 -> 56

Breaking changes, and which touch this repository

Change Touches this repository
Formatter now formats Python code blocks in Markdown files, by default Yes. This is the whole blast radius.
Default rule set grew from 59 rules to 413 No. [tool.ruff.lint] select replaces the default set rather than extending it, so the expansion cannot reach here. Without that explicit select, CPY001 (missing-copyright-notice) alone would fire on every file.
12 rules promoted to stable (AIR303, CPY001, FURB164, FURB192, ISC004, LOG004, PLE0304, PLR0917, PLR1708, RUF036, RUF063, RUF068) No. Not one belongs to the selected E, W, F, I, B, C4, UP sets.
UP019 now recognises typing_extensions.Text No. It is in the selected UP set - the only stabilised behaviour that is - but the repository contains no typing.Text and does not import typing_extensions at all.
PLW2901 changed in 0.15.22 No. A prefix trap worth recording: this repository selects W (pycodestyle warnings), which is a different linter from PLW (pylint warnings). A substring reading would wrongly flag it.
ruff: ignore comments now honoured at end-of-line No. No such comment exists here.
check and format --check now render the proposed fix as a diff No. Nothing in this repository captures or parses Ruff output, and no committed documentation reproduces it.
--add-ignore promoted out of preview No. Announced in neither release body - visible only in the docs/configuration.md CLI-help diff between the tags. Inert here: nothing invokes --add-ignore or --add-noqa.

On the Markdown change. The release note reads:

Ruff can now format Python code blocks in Markdown files and will do this by default. See the documentation for more details.

It is stable, not preview. The strongest evidence is not the release note but a documentation diff across the two tags: at ref=0.15.21, docs/formatter.md line 230 carries "This feature is currently only available in preview mode."; at ref=0.16.0 that single sentence is deleted and the rest of the section is byte-identical. That is the preview-to-stable transition captured directly, and it also explains the 0.15.21 baseline: this repository sets neither preview = true nor --preview, so the feature was simply unreachable.

The file-count arithmetic closes exactly, with no residual:

$ git ls-files '*.py' | wc -l   ->  10
$ git ls-files '*.md' | wc -l   ->   7

10 + 7 = 17. Only README.md contains Python fences, which is why the other six .md files land in the "already formatted" bucket.

Prediction recorded before running anything

Recorded from a dry-run through uvx against the unmodified tree, before any file was edited:

$ uvx ruff@0.16.0 format --check .
1 file would be reformatted, 16 files already formatted    (exit 1)
$ uvx ruff@0.16.0 check .
All checks passed!                                          (exit 0)

The prediction, in full:

  1. The <0.16 ceiling blocks the upgrade first. Since CI runs uv lock --check and the byte-exact cmp pylock.toml before any hook, raising the ceiling without regenerating both locks fails at that gate and never reaches the formatter - masking the real finding.
  2. Once the locks are consistent, ruff format --check . fails, exit 1, on exactly one file: README.md. The ask(...) call split across lines 105-107 collapses to a single line, because it fits under line-length = 100.
  3. ruff check . passes, and expectedly rather than luckily, for the two independent reasons in the table above.
  4. pre-commit run --all-files passes, locally and in CI, because both Ruff hooks pin types_or: [python, pyi, jupyter] with no markdown. No .md file is ever handed to Ruff.
  5. Free suite, coverage gate, and the 13-deselected assertion are all unaffected. Ruff has no import-time role.
  6. Therefore the expected outcome is a silent documentation/CI divergence, not a code break: CI green while the documented command fails.

What actually happened

Every point above, exactly. The complete formatter diff was one hunk:

$ .venv/bin/ruff format --diff .
--- README.md
+++ README.md
@@ -102,9 +102,7 @@
 ```python
 from pipeline import ask

-record = ask(
-    "What quantity of pearl salt and which vapor are specified for ground moonpetal?"
-)
+record = ask("What quantity of pearl salt and which vapor are specified for ground moonpetal?")

 print(record.retrieved_ids)
 print(record.context_payload)

1 file would be reformatted, 16 files already formatted

The joined line is 95 characters, inside line-length = 100.

The divergence, demonstrated rather than argued

With the locks upgraded to Ruff 0.16.0 and README.md still at its origin/main content:

--- what CI runs: pre-commit run --all-files ---
pre-commit run --all-files exit=0   <-- CI would be GREEN
--- what the README documents at line 423 ---
ruff format --check . exit=1         <-- documented command FAILS

All twelve hooks pass on a tree the documented command rejects. That is the finding.

Resolution chosen, and the two alternatives rejected

Applied the remediation the README itself documents at lines 437-438:

$ .venv/bin/ruff check --fix .
All checks passed!                          (exit 0)
$ .venv/bin/ruff format .
1 file reformatted, 16 files left unchanged (exit 0)
$ .venv/bin/ruff format --check .
17 files already formatted                  (exit 0)

The reformat is semantically inert, verified by AST comparison rather than by reading:

$ .venv/bin/python -c '<parse the README python fence, compare ASTs>'
parses: True
AST dump identical to the pre-format form: True
question string preserved verbatim: True

The golden question string is unchanged and still matches corpus.py.

Two alternatives were considered and not applied:

Alternative Why not
Add markdown to both Ruff hooks' types_or so the hooks cover what the README claims to check This is the right long-term fix and it widens coverage rather than suppressing anything - but it is a tooling-scope change beyond this upgrade, and changing a gate's scope inside a version bump confounds the bump's own evidence. Raised here as a separate decision for a maintainer.
Add *.md to extend-exclude to opt out of Markdown formatting This is a suppression added to make an upgrade pass. Rejected on principle. For completeness: extend-exclude is the only documented control - Ruff ships no dedicated Markdown-formatting toggle, and extension merely maps additional file extensions to Markdown. That negative is part of the answer.

Gate results

Gate Before (main, ruff 0.15.21) After (ruff 0.16.0)
uv lock --check Resolved 56 packages Resolved 56 packages
Frozen export vs pylock.toml identical identical
uv pip check 54 packages compatible 54 packages compatible
pytest eval/ -q 335 passed, 13 deselected 335 passed, 13 deselected
Coverage gate 96.05% 96.05%
pre-commit validate-config exit 0 exit 0
pre-commit run --all-files 12 hooks passed 12 hooks passed
ruff format --check . 10 files already formatted 17 files already formatted
ruff check . All checks passed All checks passed
mypy strict scope Success, 5 source files Success, 5 source files
agent-policy-symbols exit 0 exit 0
Default selection 13 deselected 13 deselected

The 10-to-17 file count is the one number in this table that moved, and it is the whole upgrade.

Free validation command and result:

$ .venv/bin/python -m pytest -m "not openai and not rag_test" --cov --cov-report=term-missing eval/ -q
TOTAL                       642     18    218     16  96.05%
Required test coverage of 95.0% reached. Total coverage: 96.05%
335 passed, 13 deselected in 0.95s
$ .venv/bin/ruff --version
ruff 0.16.0

The mutation question

Which behaviours of this dependency does the suite exercise? None, in the pytest sense - Ruff is dev tooling with no import-time role, appears in no [tool.coverage.run] source entry, and is imported by no test. Its behaviour is exercised by two surfaces with different scopes, which is the crux:

Surface What it hands to Ruff Runs in CI
ruff-check / ruff-format hooks types_or: [python, pyi, jupyter] - 10 files Yes, via pre-commit run --all-files
ruff format --check . / ruff check . (README:423-424) the whole tree - 17 files under 0.16.0 No

For each breaking change that touches a real call site, which test would have failed?

Change Test or gate that would have caught it
Markdown formatting promoted to stable No test, and no CI step. Only the README-documented ruff format --check ., which CI does not run. Demonstrated above: all twelve hooks green, documented command exit 1.
A stable Python formatter style change (had one occurred) The ruff-format hook would have caught it, since .py files are in types_or. Positively confirmed absent: 16 of 17 files were already formatted, and 16 = the 6 Python-free .md files + all 10 .py files.
A newly stabilised rule inside E/W/F/I/B/C4/UP The ruff-check hook would have caught it. None occurred.
Ruff output-format changes (inline fixes, new --output-format values, nullable JSON fields) Nothing would have caught them, and nothing needs to - no gate parses Ruff output.

The finding, stated plainly. Ruff is pinned once and invoked from one hash-locked venv, which eliminates the classic version-skew failure mode - that part of the design holds. But the hook and the documented CLI command disagree about scope, and CI validates only the narrower of the two. A Ruff release that changes anything outside python/pyi/jupyter is invisible to every gate this repository runs automatically. This upgrade is exactly that release, and only the manual documented command surfaced it. Per the audit brief, no test was added and no hook scope was widened in this pass; the fix is named above as a maintainer decision.

Lock byte-comparison

$ uvx --from uv==0.11.30 uv lock --check --python 3.14 --prerelease disallow
Resolved 56 packages in 5ms
$ uvx --from uv==0.11.30 uv export --frozen --format pylock.toml --all-groups \
    --no-emit-project --python 3.14 --prerelease disallow --no-header --quiet \
    -o "$generated_dir/pylock.generated.toml"
$ cmp pylock.toml "$generated_dir/pylock.generated.toml"
cmp: IDENTICAL (exit 0)
$ shasum -a 256 pylock.toml "$generated_dir/pylock.generated.toml"
2823be5b94eaf8e88e4944f4e6b8cea41a36af4ebdffded9cbaa279127e29872  pylock.toml
2823be5b94eaf8e88e4944f4e6b8cea41a36af4ebdffded9cbaa279127e29872  .../pylock.generated.toml

pylock.toml invariants re-checked after the export:

requires-python = "==3.14.*"
sha256 count: 343
local paths / editable / file:// / git+ : 0
non-PyPI index entries: index = "https://pypi.org/simple"

Clean-clone transcript

Isolated temp directory, both provider key variables unset and confirmed absent by presence check only, no sibling ../RagaliQ reachable, README install commands verbatim with bare uv as documented.

=== provider key presence check (names only, never values) ===
OPENAI_API_KEY: absent
ANTHROPIC_API_KEY: absent
=== clone root: /private/tmp/verdigrise-cleanclone.1SqODq ===
=== sibling RagaliQ reachable from clone parent? ===
not reachable
cloned HEAD: 604d5554254ea67f5c9fb3b012d85eacfa6e416f  branch: codex/bump-ruff
worktree clean: yes
sibling RagaliQ reachable from repo root? not reachable
uv 0.11.32 (Homebrew 2026-07-23 aarch64-apple-darwin)

$ uv venv --python 3.14
Using CPython 3.14.6 interpreter at: /opt/homebrew/opt/python@3.14/bin/python3.14
$ uv pip sync --preview-features pylock --require-hashes pylock.toml
Installed 54 packages in 118ms
$ uv pip check
Checked 54 packages in 1ms
All installed packages are compatible

$ .venv/bin/python -m pytest eval/ -q
335 passed, 13 deselected in 1.58s

=== clone-to-green wall time: 4s (uv cache WARM: 17G) ===

$ .venv/bin/python -m pytest --cov --cov-report=term-missing eval/ -q
TOTAL                       642     18    218     16  96.05%
Required test coverage of 95.0% reached. Total coverage: 96.05%
335 passed, 13 deselected in 1.18s

$ .venv/bin/ruff format --check .   -> 17 files already formatted (exit 0)
$ .venv/bin/ruff check .            -> All checks passed! (exit 0)
$ .venv/bin/python -m mypy config.py corpus.py models.py pipeline.py eval/ragaliq_adapter.py
Success: no issues found in 5 source files (exit 0)
$ .venv/bin/pre-commit validate-config   -> exit 0
$ .venv/bin/pre-commit run --all-files   -> 12 hooks Passed (exit 0)
$ .venv/bin/python -m pytest eval/ --collect-only -q | tail -1
335/348 tests collected (13 deselected) in 0.51s
$ .venv/bin/python -m eval.check_agent_policy_symbols   -> exit 0

ragaliq 0.2.0 from .../.venv/lib/python3.14/site-packages/ragaliq/__init__.py

The 17 files already formatted line in a clean clone is the durable proof that the new discovery scope is satisfied from a fresh checkout, not only in a developer working tree.

The clone-to-green figure is a warm-cache number: the uv cache was already populated (17G), so 4s measures command execution, not first contact. A cold-cache clone would additionally download 54 wheels.

Hidden costs

  • This upgrade makes ruff format . write to README.md. A contributor following the documented remediation path now produces an edit to the file that documents the path. That is new behaviour, not a one-off: any future Markdown-embedded Python that does not match Ruff's style will be rewritten by the documented command.
  • Markdown-embedded Python is now formatter-governed. Deliberately wrapped examples chosen for readability will be joined whenever they fit inside line-length = 100. This is the first such case; it will not be the last.
  • The hook/README scope gap named in the mutation section is now a live divergence rather than a latent one. It is not closed by this pull request.
  • The new ceiling <0.17 means 0.17.0 will again require a source edit. Given that Ruff's minor number is its breaking-change vehicle, that is the correct shape for the pin, not an inconvenience.
  • No provider calls were made at any point in producing this change.

@dariero

dariero commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

@codex review for deterministic/RagaliQ ownership, dependency reproducibility, Python 3.14 compatibility, public-clone portability, paid-call safety, golden-fixture integrity, marker correctness, public API compatibility, and unintended behaviour changes

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃挕 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 604d555425

鈩癸笍 About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 馃憤.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread pyproject.toml
@dariero
dariero merged commit f74e0a8 into main Jul 27, 2026
4 checks passed
@dariero
dariero deleted the codex/bump-ruff branch July 27, 2026 02:19
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