Skip to content

Count the remaining suppression forms and say the total may not grow (follow-up to #75) - #80

Merged
anth-volk merged 9 commits into
mainfrom
claude/noqa-ratchet-follow-up
Nov 4, 2026
Merged

anth-volk merged 9 commits into
mainfrom
claude/noqa-ratchet-follow-up

Conversation

@anth-volk

@anth-volk anth-volk commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #77

What this does

A follow-up to #75, which merged at 0deef32 before its second review's fixes. 16.1 · 17 in the plan. The noqa-comments guard now counts the suppression forms it still missed, and the rule, README and registry guide say what the check does: the total number of inline suppressions may not grow. The baseline stays at 140 entries in 49 files, because the repository has none of the newly counted forms.

  • Directives beside a trailing file-level noqa (bug 1, medium). Ruff reads each kind of directive in a comment on its own. A trailing ruff: noqa or flake8: noqa that Ruff does not apply after code used to end the parse, so print(1) # noqa: T201 # ruff: noqa: E501 had no entry. Now the other directives in such a comment count, and so does the file-level one when it is on its own line.

  • A leading comma (bug 2, low). Ruff skips a comma before the first code, with a warning, so # noqa: ,T201 counts as T201.

  • The isort comments (bug 3, low; review round 1 of this pull request). These switch off Ruff's import sorting (I001), so they are now entries, labelled by form, read exactly as Ruff 0.16.10's extract_isort_directives reads them:

    • Whole comments: skip_file in its four spellings (# isort: skip_file, # isort:skip_file and the same after # ruff: ), and # isort: off or # ruff: isort: off, with trailing space removed.
    • Skip markers: any other comment containing isort: skip or isort:skip, which counts only when an import statement starts on the comment's line. That covers # keep first, isort: skip_file, # isort:skip_file # keep first, # isort: skipped, ... and # isort:skip_this.
    • Not counted: an own-line skip, a skip after code that isn't an import, #isort: off and # isort:off. Ruff applies none of them. on isn't counted either, and inside an off range Ruff reads no other isort comment.
  • The rule id stays emitter.quality.inline-suppressions-only-shrink. Renaming it now would make the rules tool treat it as a new rule and restart its comparison, the loophole the second review found. Compare a renamed ratcheted rule's baseline with the base branch #79 tracks making the tool follow a renamed ratcheted rule.

  • Wording. The rule statement, README, baseline header, the guard's module docstring and its messages now say:

    • one entry per directive;
    • the total may not grow against the base branch, so a pull request may add a suppression only by removing another;
    • the check counts directives only, so it doesn't stop a swap at the same total, a rename or a code added to an existing comment.

    The README's list of forms includes flake8: noqa and the isort directives.

  • The registry guide now matches its own list of what only a person may do:

    • an agent may remove suppressions and shrink the baseline;
    • an agent adds a baseline entry only as part of a swap at the same total, such as a new suppression in place of one it removes, and that entry needs a person's approval in review. Nothing enforces that approval today: main requires no approving review and there is no code-owners file. Otherwise, an agent that can't fix a finding without a new suppression stops and reports it;
    • raising the total is a person's decision, because the guard refuses a grown count with or without a [rule.downgrade];
    • renaming a ratcheted rule's id restarts its comparison, so review catches an id change (Compare a renamed ratcheted rule's baseline with the base branch #79).
  • Tests. The three same-total tests now model a pull request: the base branch's files and baseline, then the pull request's. They check the guard on both and the total against the base branch's, and they assert the baselines differ. The higher-total test is built the same way and fails as before. The base-branch comparison receives only the two totals, because check_baselines compares counts, so these tests don't show what a comparison of entries would do.

  • One baseline reader. file_length, ci_scripts and pyright now read their baselines with baseline.baseline_lines, as UTF-8, like the guard and validation, so the helper's docstring holds.

Separate issues: #78 for the two timing-dependent macOS tests, and #79 for the rule-rename limit.

Left for later, from this pull request's review:

  • The baseline writers (--write-baseline and the like) don't name an encoding.
  • # isort: split isn't counted, since it divides sorting without switching any of it off, and the statement excludes it.

The [rule.downgrade] section naming #74 and the 140-entry baseline, both from #75, still await Anthony's confirmation; this pull request doesn't change either.

Test-first record

#75's commits, now on main, include 29f5ad6 (red) and a5bdc0c (green), which replaced b9bd74f and 9c76f80 when #75's branch was rebased.

Commit Result
2396b3a Tests for the three bugs Red, on assertions: all 11 fail. Five are directives beside a file-level noqa: the guard returned no entry, or only the file-level one. One is the leading comma, which returned no entry. Five are the isort forms, which returned no entry.
a71ecd8 The fixes Green: all nine test classes pass by name, including this repository's baseline at 140. One code comment that named the isort skip keyword was itself read as a directive, and is reworded.
5585b92 One baseline reader The rules guards pass.
65825b5 Same-total tests modelling pull requests They pass at the same total; the higher-total case fails with "baseline grew from 1 to 2".
fba91c2 The wording and the guide render updated the rules views; all six guards pass with GITHUB_BASE_REF=main, and guard --base-ref origin/main passes. ruff check passes on the changed Python files.

Review round 1 of this pull request:

Commit Result
0ef3d36 Tests of isort as Ruff reads it Red, on assertions: all 8 fail. The four skip samples got no entry, and the four forms Ruff ignores got one.
e13f908 isort read as Ruff 0.16.10 does Green: all ten test classes pass by name, including this repository's baseline at 140.
f01aa5e Test of the "directive" message Red, on its assertion: the leftover-entry message still said "such comment".
d2c392b The guard's docstring and messages, and the guide Green: all ten classes pass. The six guards pass with GITHUB_BASE_REF=main, and ruff check passes on the changed files.

The work is in a fresh clone; the old checkout had lost objects.

Only the test file's classes were run, each by name with python -m unittest; no test suite or directory was run.

CI

No workflow, trigger or script changes, and no environment variable or secret is read differently.

Release

The fragment is changelog.d/noqa-ratchet-follow-up.changed.md, a patch bump: no shipped code changes, so the wheels are unchanged.

🤖 Generated with Claude Code

anth-volk and others added 9 commits November 4, 2026 01:58
… forms

- A trailing ruff: noqa or flake8: noqa, which Ruff does not apply,
  must not hide the comment's other directives: an inline noqa before
  or after it, or a ruff: ignore[...], still counts; on its own line,
  the file-level entry and the inline one both count.
- A code list that starts with a comma still names its codes, as Ruff
  skips the comma.
- The isort directives switch off Ruff's import sorting (I001), so they
  are entries: isort:skip anywhere in a comment, isort: skip_file,
  ruff: isort: skip_file, isort: off (its on is not one) and
  ruff: isort: off.

Red: all 11 fail on their assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A file-level noqa that Ruff does not apply (after code) no longer
  stops the parse: each kind of directive in a comment is read on its
  own, as Ruff does.
- codes_after skips a comma before the first code, as Ruff does.
- The isort directives that switch off import sorting are entries,
  labelled by form with I001: isort:skip anywhere in a comment, and a
  whole comment of isort: skip_file or off, with or without "ruff:".
  isort: on is not one. The repository has none, so the baseline stays
  at 140.

Green: the 11 tests from the previous commit pass, with every other
class in the file, including this repository's baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file_length.baseline_entries, ci_scripts._baseline and
pyright.read_baseline now read their entries with
baseline.baseline_lines, and every read of a baseline file, including
pyright's header, names UTF-8. They read the same entries as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The added-code, swapped-code and renamed-file tests now build the base
branch's files and baseline and the pull request's, check the guard on
both and the total against the base branch's, and assert the baselines
differ. They pass at the same total, and the higher-total test, built
the same way, fails with "baseline grew from 1 to 2". A later move to
comparing entries would fail the first three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The rule statement, README and baseline header say the guard lists
  every suppression directive Ruff applies, one entry per directive,
  including a file-level ruff: noqa or flake8: noqa on its own line and
  the isort directives, and that the total may not grow against the
  base branch. The rule id stays: renaming it would restart its
  comparison (#79).
- The registry guide matches its person-only list: an agent may remove
  suppressions and shrink the baseline; a new suppression, even in
  place of a removed one, adds an entry a person approves in review;
  raising the total is a person's decision; and renaming a ratcheted
  rule's id restarts its comparison, so review catches an id change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ruff's extract_isort_directives takes a whole comment of skip_file, off
or on after "# isort: " or "# ruff: isort: ", and otherwise any comment
containing "isort: skip" or "isort:skip", which leaves the import that
starts on that line out of sorting.

- Counted: skip_file inside a longer comment, skip_file followed by
  more comment, skip followed by letters, and skip followed by an
  underscore, each after an import.
- Not counted: a skip on its own line, a skip after code that is not an
  import, "#isort: off" and "# isort:off", which apply to nothing.

Red: all 8 fail on their assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guard now follows extract_isort_directives: a whole comment of
skip_file (four spellings), off or on, after "# isort: " or
"# ruff: isort: ", with trailing space removed, and otherwise any
comment containing "isort: skip" or "isort:skip", which counts only
when an import statement starts on the comment's line (read with ast).
Inside an off range Ruff reads no other isort comment, and a split is
not a suppression. The baseline still matches the repository at 140.

Green: the 8 tests from the previous commit pass, with every other
class in the file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guard makes one entry per directive, so the message for a leftover
baseline entry should say how many such directives the file has. Red:
test_a_removed_noqa_comment_asks_for_the_baseline_to_shrink fails on
its assertion. The same-total class's docstring now says the
comparison counts directives and receives only the two totals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rule

- The guard's module docstring lists the isort comments with the other
  forms, says the baseline lists one entry per directive and that the
  comparison counts directives, and names what Ruff does not apply.
  compare's docstring and its leftover-entry message speak of
  directives (green for the previous commit's test).
- The registry guide's person-only list now agrees with its next
  sentence: an agent adds a baseline entry only as part of a swap at the
  same total, which needs a person's approval in review, and nothing
  enforces that approval today (main requires no approving review and
  there is no code-owners file). An agent that can't fix a finding
  without a new suppression stops and reports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk marked this pull request as ready for review November 4, 2026 07:59
@anth-volk
anth-volk merged commit 5a8bdb3 into main Nov 4, 2026
22 of 24 checks passed
@anth-volk
anth-volk deleted the claude/noqa-ratchet-follow-up branch November 4, 2026 08:00
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.

Count the remaining suppression forms and correct the inline-suppression rule's wording (follow-up to #75)

1 participant