fix(ruff): pin the rule set so an upstream default change cannot break the repo - #1991
Conversation
…k the repo The repo had no ruff configuration, so every invocation linted against whatever upstream's default selection happened to be that release. Ruff 0.16.0 moved that default from 59 rules to 413 and dropped 18 E/F rules from it, which turned two contract suites red on an unmodified main. - ruff.toml selects E4, E7, E9, and F, and excludes eval fixtures, which hold the defect their eval asserts on and so fail linting by construction. - ruff-format's case 4e selects E712 in its fixture rather than relying on it being a default rule. A case that asserts on one rule's fix safety must name that rule. - Drops an unused `import stat` the pinned set surfaces as a real F401. The engine suite's TRY004 findings fall outside the selected set, which is the right outcome on their merits: all six sites raise on a malformed remote payload or state file, not on a caller passing the wrong type, and TypeError would misdescribe them. Closes #1972 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KMBm1Ae29KhZmjFxw2wdZ7
…direct paths Ruff lints an explicitly-passed path even when a pattern excludes it, unless the caller also passes --force-exclude. Every invocation in this repo passes directories, so the exclusion holds; the comment now says which mechanism it relies on rather than implying the fixtures are unlintable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KMBm1Ae29KhZmjFxw2wdZ7
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Claude finished @kyle-sexton's task in 2m 47s —— View job Reviewing PR #1991
SummaryThis is a solid, well-reasoned fix. Pinning
One non-blocking note: the No correctness, architecture, or test-coverage issues found. The TRY004 rationale is a documented judgment call rather than a config change, which is appropriately out of scope for this fix and doesn't need to be revisited here. |
|
Claude finished @kyle-sexton's task in 1m 15s —— View job Security Review — complete
No security issues found. This PR is a lint-policy change with no runtime security surface touched:
No workflow files, permissions, secrets/token handling, or trust boundaries are touched by this PR, so there's nothing in scope for the injection/authz/GitHub-Actions checks either. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 60d6b3d731
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Claude has reviewed this PR 2 times. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
Review was right that `select` on category prefixes does not stop a future release adding a rule inside a selected category. It stops default-set churn, which is the failure that happened; the remainder is closed by holding the CI pin equal to the fleet inventory, not by config. Records both halves and why enumerating exact codes was not the answer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KMBm1Ae29KhZmjFxw2wdZ7
|
Claude finished @kyle-sexton's task in 2m 23s —— View job Reviewing PR #1991
SummaryThis is a well-scoped, well-documented fix. I independently verified the specific claims backing that tradeoff and the rest of the PR body:
One documentation-accuracy note (non-blocking): No other correctness, error-handling, or test-coverage issues found. The TRY004 write-up is a documented judgment call correctly kept out of the config (since |
|
Claude finished @kyle-sexton's task in 1m 17s —— View job Security Review — complete
No security issues found. This synchronize event only added two documentation commits (
No workflow files, permissions, secrets/token handling, or trust boundaries are touched by this PR, so there's nothing in scope for the injection/authz/GitHub-Actions checks either. |
Closes #1856 ## Summary A bare `ruff` on `PATH` and the one CI installs can be different versions, and then they disagree about what a finding is. This adds `scripts/run-ruff.sh`, which resolves ruff from the pin declared in `.github/requirements-ci.txt` rather than from `PATH`, and points the `babysit-prs` lint pass at it. **The direction has been re-targeted since this PR was opened, deliberately.** It was written as "align local ruff with CI pin 0.15.22" — pinning workstations *down* to CI. CI has since moved to `ruff==0.16.1` (#1953) and the repo's rule set is pinned explicitly (#1991), so that framing would read as the fleet moving backwards. **No value in this PR changes:** the wrapper parses the pin at run time, so it follows the repository's version wherever it goes and carries no copy of its own. What changed is the title, the docs, and the changelog entry, so a merged commit does not assert the opposite of what the fleet does. ## Fix - **`scripts/run-ruff.sh`** — uses a PATH `ruff` only when it already reports the pinned version (the CI install path), otherwise `uvx ruff==<pin>`; exit 2 when a drifted PATH ruff is all there is and `uvx` is unavailable, exit 127 when neither exists. - **`engine.test.sh`** lints through the wrapper, skipping visibly rather than silently when the pin cannot be resolved. - **`docs/CI-RUNNER-ROUTING.md`** records the local rule, and now also records which way the pin moves: it is held equal to the fleet inventory in melodic-software/dotfiles `.chezmoidata/uv-tools.yaml`, because that is what installs a developer's toolchain. - **`source-control` 0.47.2** with a matching changelog entry (was 0.45.1 against a 0.45.0 baseline; rebased onto 0.47.1). ## A real defect found while re-verifying `engine.test.sh` re-derived the repository root from `BASH_SOURCE` *after* `cd`-ing to its own directory. `BASH_SOURCE` holds the path as invoked, so a relative invocation resolved against the new cwd and landed outside the repository. The lint pass then printed ``` SKIP: scripts/run-ruff.sh not found (lint pass omitted) ``` and the suite exited 0 having linted nothing. Running the suite from the repository root — which is exactly how `scripts/run-plugin-tests.sh` runs it — took that path every time, so the wrapper this PR exists to introduce would have been inert in CI. The script directory is now captured once, before the `cd`. ## Verification - `bash scripts/run-ruff.sh --version` → `ruff 0.15.22` before merging main and `ruff 0.16.1` after, with an unchanged 0.16.0 ruff on `PATH` throughout and no edit to this branch in between. That is the design demonstrated rather than asserted: #1953 landed at 0.16.1 rather than the 0.16.0 its title advertised, and the wrapper followed a version this PR never named. - `bash plugins/source-control/skills/babysit-prs/scripts/engine.test.sh` from the repository root **and** from the script's own directory → exit 0, both printing `== ruff (CI pin via scripts/run-ruff.sh) ==`. Before the lookup fix, the repository-root invocation printed the SKIP line instead. - `shellcheck scripts/run-ruff.sh plugins/source-control/skills/babysit-prs/scripts/engine.test.sh` → clean. - `bash scripts/check-changelog-parity.sh --check-bump origin/main` → `Every plugin whose version changed vs origin/main has a '## [<version>]' CHANGELOG.md entry.` - Merged `origin/main` (100 commits behind at the start); the only conflicts were the `source-control` manifest version and changelog, resolved onto 0.47.1. ## Related - Refs #1991 — pins the repo's rule set so a version bump cannot change which categories run; that and this are the two halves of local-equals-CI. - Refs #1953 — the Dependabot bump, merged at `ruff==0.16.1`, which this wrapper picked up with no change here. - Refs melodic-software/dotfiles#416 — the check that keeps the CI pin and the fleet inventory equal, so the value this wrapper reads stays the one developers run. --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes #1972
Summary
The repo had no ruff configuration, so every ruff invocation in it linted
against whatever upstream's default selection happened to be that release. Ruff
0.16.0 moved that default from 59 rules to 413 and removed eighteen
E/Frules from it — "E401, E402, E701, E702, E703, E711, E712, E713, E714, E721,
E731, E741, E742, E743, F403, F405, F406, and F722"
(0.16.0 release notes).
That single change is behind both failures in #1972: the expansion brought the
TRYcategory into the default set, and the removals tookE712out of it.Fix
ruff.tomlselectsE4,E7,E9, andF— the pre-0.16 default set.The rule set is now a decision recorded in the repo rather than one inherited
from upstream. It is deliberately a floor: raise it toward the managed policy
with
extend-selectas each category is cleared.Registered as
locally-ownedfor this repo inchore(distribution): record claude-code-plugins as a locally-owned ruff consumer standards#336 (merged), which is what keeps it from being a
second unregistered lint policy.
eval asserts on, so linting it fails by construction. The comment records that
exclusion governs traversal: Ruff's settings reference states "Typically,
Ruff will lint any paths passed in directly, even if they would typically be
excluded", and that
force-exclude"will cause Ruff to respect theseexclusions unequivocally". Every invocation here passes directories; the
ruff-format hook passes a file explicitly and already sets
--force-exclude.Verified both ways against the fixture path.
ruff-format.test.shcase 4e selectsE712in its fixture instead ofrelying on it being a default rule. A case asserting on one rule's fix safety
must name that rule; this is version-agnostic rather than re-pinned.
import statinpython3_alias_probe.py— a realF401the pinned set surfaces (line 90 calls
path.stat(), the method, not themodule).
The TRY004 decision, recorded
#1972 asked for this on its merits rather than by config, so both halves:
On merits: the rule does not fit these sites. All six raise on a decoded
payload or a persisted state file failing an
isinstanceguard —babysit_gh.py:603,699,728on a GraphQL response,babysit_lease.py:114,manage_feedback_ledger.py:176, andrefresh_pr_branch.py:112on state filesthis code wrote itself. None is a caller passing the wrong type to a function.
That distinction is the whole argument, so it is worth being explicit that it
is a judgment rather than something upstream states. TRY004's own rationale is
"the Python documentation states that
TypeErrorshould be raised uponencountering an inappropriate type"; the Python docs' operative sentence is
"Passing arguments of the wrong type … should result in a
TypeError",which is a caller-contract rule, and they define
RuntimeErroras "an erroris detected that doesn't fall in any of the other categories." The broader
opening sentence ("an operation or function is applied to an object of
inappropriate type") could be read to cover these; the reading taken here is
that data-integrity validation of a payload is not argument passing. Anything
catching
RuntimeErrorat these sites would change behavior if they wereretyped. No
noqais needed; the sites stand as written.Separately:
TRYis not in the selected set, so they are not findingstoday regardless. Recorded so a future
extend-select = ["TRY"]knows thecategory was examined and rejected on merits, not merely left unselected.
What the prefix selection does not buy
Review raised that
E4/E7/E9/Fare prefixes, so a later release adding arule inside one of those categories enables it here automatically — the original
wording of this PR overstated the invariant, and the file now says so at the
selection.
That exposure is real and is closed by version parity rather than by config. The
pin in
.github/requirements-ci.txtis held equal to the fleet inventory(melodic-software/dotfiles
.chezmoidata/uv-tools.yaml), so a newly-added rulecannot reach CI before a developer or the reverse; melodic-software/dotfiles#416
adds the check that keeps those two equal, and #1953 is the bump that makes them
equal today.
Enumerating the exact pre-0.16 codes was the alternative. It trades a reviewable
statement of intent for a list no reader can evaluate, freezes the policy against
genuinely useful new correctness rules inside
F, and still needs the versionpin to be trustworthy — so it buys the appearance of the invariant rather than
the invariant.
Verification
ruff check --no-cache --statistics plugins/ scripts/→ 0 findings(166 under bare 0.16.0 defaults before this change).
bash plugins/ruff-format/hooks/ruff-format.test.sh→PASS=52 FAIL=0(was
PASS=51 FAIL=1).bash plugins/source-control/skills/babysit-prs/scripts/engine.test.sh→exit 0;
Ran 597 tests ... OK, ruff section clean (83 findings under bare0.16.0 defaults before this change).
bash scripts/run-plugin-tests.sh→ complete sweep of all 170 suites, exit 0,All plugin tests passed or were skipped.Those two are also the only suitesthat invoke ruff at all (
grep -rl 'ruff check\|ruff format' --include=*.shfinds
plugins/ruff-format/hooks/ruff-format.shandengine.test.sh), so noother suite could be affected by a lint config.
mktemp -d, whichresolves outside the worktree, so they do not inherit this config — case 4e's
pass is real evidence about the fixture's own
select, not about this file.dotfiles.chezmoidata/uv-tools.yaml, so these runs reproduce what a developer sees.CI still installs 0.15.22; chore(deps): bump ruff from 0.15.22 to 0.16.1 in /.github #1953 is what closes that half.
Related
ruffcomponent aslocally-ownedfor this repo; closes Adopt the managed ruff component in claude-code-plugins so upstream default churn is not a repo-breaking event standards#335.the failure that happened: with
selectexplicit, the bump cannot changewhich CATEGORIES run.
pin dynamically, so it needs no value change once chore(deps): bump ruff from 0.15.22 to 0.16.1 in /.github #1953 lands.