Skip to content

fix(dev-env): resolve ruff from the declared pin instead of PATH - #1871

Merged
kyle-sexton merged 9 commits into
mainfrom
fix/dev-env-ruff-pin-alignment
Aug 8, 2026
Merged

fix(dev-env): resolve ruff from the declared pin instead of PATH#1871
kyle-sexton merged 9 commits into
mainfrom
fix/dev-env-ruff-pin-alignment

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

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 --versionruff 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: chore(deps): bump ruff from 0.15.22 to 0.16.1 in /.github #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/mainEvery 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

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 50s —— View job


Security review

  • Gather context (diff, changed files)
  • Review scripts/run-ruff.sh (new script)
  • Review plugins/source-control/skills/babysit-prs/scripts/engine.test.sh changes
  • Review docs/CI-RUNNER-ROUTING.md and CHANGELOG changes
  • Post findings

No security issues found. This PR is a dev-tooling change (aligns local ruff invocation with the CI pin) with no workflow, permission, or secret-handling changes. Details below.

Scope reviewed: scripts/run-ruff.sh (new), plugins/source-control/skills/babysit-prs/scripts/engine.test.sh, docs/CI-RUNNER-ROUTING.md, plugins/source-control/CHANGELOG.md — the two docs files are prose-only, no contract/security surface.

What I checked in scripts/run-ruff.sh:

  • The pin value is parsed from .github/requirements-ci.txt via awk and used exclusively inside double-quoted expansions ("$got" == "$pin", "ruff==${pin}", exec uvx "ruff==${pin}" "$@"). No unquoted expansion or eval, so there's no shell command-injection path even if that file's content were adversarial —
    if command -v uvx >/dev/null 2>&1; then
    exec uvx "ruff==${pin}" "$@"
    fi
    if command -v ruff >/dev/null 2>&1; then
    got="$(ruff_reported_version ruff)" || got="unknown"
    echo "error: ruff on PATH is ${got}, but CI pins ruff==${pin} (.github/requirements-ci.txt)." >&2
    echo "error: install the pin, or install uv and re-run (uvx ruff==${pin} ...)." >&2
    exit 2
    fi
  • .github/requirements-ci.txt is already the trusted source CI uses for a hash-pinned pip install; this script reads the same file at the same trust level and doesn't widen who can influence the pin.
  • $REPO_ROOT / $RUN_RUFF in engine.test.sh are derived from ${BASH_SOURCE[0]}, not from environment or caller-controlled input, and the script is invoked with an explicit bash "$RUN_RUFF" rather than relying on PATH — no path-hijack surface —
    # Lint through the repo's CI pin (.github/requirements-ci.txt) rather than a
    # bare PATH ruff: a workstation global that auto-upgraded past the pin false-
  • uvx "ruff==${pin}" fetches from PyPI unpinned-by-hash, but this is a local-developer-only fallback path (not exercised by CI, which still uses the hash-locked pip install per docs/CI-RUNNER-ROUTING.md), and it pins to the same version CI already trusts rather than loosening anything — not treating this as a finding.
  • No GitHub Actions workflow files are touched by this PR, so there's no pull_request_target/workflow_run, permissions, or github context injection surface to assess here.

@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: 4bc89095a5

ℹ️ 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".

Comment thread plugins/source-control/CHANGELOG.md
@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 14s —— View job


Code review

  • Gather context (diff, changed files, REVIEW.md lane split)
  • Review scripts/run-ruff.sh
  • Review engine.test.sh changes
  • Review docs/CHANGELOG changes
  • Post findings

Reviewed the single commit 4bc8909 (4 files, +111/-1). Security is out of scope for this lane (per REVIEW.md, this repo has a dedicated security lane, which already posted a clean finding above). Findings below are correctness/consistency/maintainability.


🔴 Important — scripts/run-ruff.sh is committed without the executable bit, breaking its own documented usage

The diff header shows new file mode 100644 for scripts/run-ruff.sh, and every other scripts/*.sh in this repo is 100755. But docs/CI-RUNNER-ROUTING.md tells a human to invoke it directly:

scripts/run-ruff.sh check <paths>

Workstation package managers routinely auto-upgrade past the CI pin; a newer
ruff (for example 0.16.x while CI is on `ruff==0.15.22`) reports dozens of

Without the execute bit, that documented invocation fails with Permission denied (despite the #!/usr/bin/env bash shebang) — it only works if the caller happens to prefix it with bash. The one path that does work in this PR, engine.test.sh, only survives because it explicitly calls bash "$RUN_RUFF" rather than executing the path directly.

This is also why CI didn't catch it: the guard at engine.test.sh line 38 (next finding) accepts a non-executable file. git add --chmod=+x scripts/run-ruff.sh (or chmod +x + re-stage) fixes it.

🟡 Nit — redundant executable check masks the missing +x bit

engine.test.sh#L38:

if [[ -x "$RUN_RUFF" ]] || [[ -f "$RUN_RUFF" ]]; then

Since the file is invoked as bash "$RUN_RUFF" right below (not executed directly), the -x branch is dead weight — any file passing -f also satisfies this condition. That's harmless for the test itself, but it's exactly the reason this test suite is green even though the shipped script isn't executable per the above finding. Suggest simplifying to just [[ -f "$RUN_RUFF" ]] for clarity, now that the underlying mode is fixed separately.

🟡 Suggestion — manifest version left at 0.45.0 while CHANGELOG adds [0.45.1]

Concurring with Codex's comment on CHANGELOG.md#L6: plugins/source-control/.claude-plugin/plugin.json still declares "version": "0.45.0". This repo's own history (e.g. commit 2d4e276, #1853) always pairs a new CHANGELOG entry with a matching plugin.json version bump in the same commit. Leaving it at 0.45.0 here breaks that pairing and desyncs the manifest (the consumer-facing version signal) from the changelog.

Fix this →


Everything else checked out: the CI-pin parsing in run-ruff.sh (awk extraction, quoting) is sound, the REPO_ROOT relative-path math in engine.test.sh resolves correctly (verified with realpath), the resolution order (PATH match → uvx → error) matches the documented behavior, and the docs/CHANGELOG prose is accurate and consistent with the code.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude has reviewed this PR 4 times. The lane skips further automatic reviews after 5; deleting this comment resets the count.

The prior commit documented 0.45.1 and wired engine.test.sh through
scripts/run-ruff.sh, but left the manifest at 0.45.0. Also drop the bare
PATH-ruff fallback so a drifted global cannot false-red the suite when the
wrapper is present, and mark run-ruff.sh executable.
@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 35s —— View job


Code review

  • Gather context (diff since last review, changed files)
  • Review new commit against prior findings (executable bit, manifest version bump)
  • Review full diff for correctness/maintainability
  • Post findings

Reviewed commit 729f3a9, which follows up on the earlier review pass. Both previously raised findings are resolved:

🔴 Executable bit — fixed. scripts/run-ruff.sh is now mode 100755 (confirmed on disk: -rwxr-xr-x), matching every other scripts/*.sh and making the documented scripts/run-ruff.sh check <paths> invocation in docs/CI-RUNNER-ROUTING.md#L48-L49 work as written.

🟡 Manifest/CHANGELOG version desync — fixed. plugin.json now declares 0.45.1, matching the [0.45.1] CHANGELOG entry — this also addresses Codex's comment on the same line.

🟡 Redundant -x check — fixed. engine.test.sh#L38 is simplified to [[ -f "$RUN_RUFF" ]], consistent with the file being invoked via bash "$RUN_RUFF" rather than executed directly.

Also in this commit: the bare PATH-ruff fallback branch (elif command -v ruff) was dropped from engine.test.sh. That branch was only reachable when scripts/run-ruff.sh itself was missing from the checkout — an edge case now that the wrapper is a committed, tracked file — so removing it just tightens the pin guarantee rather than changing normal-path behavior. Reasonable.

No new issues found. This PR is ready from a code-review standpoint; security was already cleared in the earlier pass on this PR (no workflow/permission/secret-handling surface touched).

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 30s —— View job


Security review

  • Gather context (git diff HEAD~2..HEAD, matches PR's 5 changed files)
  • Review scripts/run-ruff.sh (new script)
  • Review plugins/source-control/skills/babysit-prs/scripts/engine.test.sh changes
  • Review docs/CI-RUNNER-ROUTING.md / CHANGELOG.md / plugin.json changes
  • Post findings

No security issues found.

Scope reviewed: the full two-commit diff (4bc8909 + 729f3a9) against origin/mainscripts/run-ruff.sh (new), plugins/source-control/skills/babysit-prs/scripts/engine.test.sh, docs/CI-RUNNER-ROUTING.md, plugins/source-control/CHANGELOG.md, plugins/source-control/.claude-plugin/plugin.json. No GitHub Actions workflow files are touched, so there is no pull_request_target/workflow_run, permissions:, or github-context injection surface here — out of scope per the review brief, and zizmor's lane already covers that category statically for this repo.

What I checked:

  • Injection via the untrusted-looking pin value. pin is parsed from .github/requirements-ci.txt and flows into [[ "$got" == "$pin" ]] (string comparison, not eval) and into exec uvx "ruff==${pin}" "$@"
    if [[ "$got" == "$pin" ]]; then
    and
    exec uvx "ruff==${pin}" "$@"
    . Every expansion is double-quoted, so pin becomes a single literal argument passed to uvx/tested in [[ ]] — no word-splitting, globbing, or shell re-interpretation, and no path to command injection even if the file's content were adversarial. .github/requirements-ci.txt is also already the trust root CI uses for its hash-locked pip install, so this script doesn't widen who can influence the pin.
  • exec ruff "$@" / exec uvx ... "$@" (lines 51, 56). Caller-supplied args are passed through "$@", correctly quoted — no re-splitting of args containing spaces/metacharacters.
  • Path/trust surface in engine.test.sh. REPO_ROOT and RUN_RUFF are derived from ${BASH_SOURCE[0]}, not environment or caller input, and the script is invoked as bash "$RUN_RUFF" (explicit path), not resolved via PATH — no PATH-hijack surface. This also closes the executable-bit gap flagged in the prior review round: scripts/run-ruff.sh is now mode 100755 (fixed in 729f3a9), so it also works when invoked directly per its own docs.
  • uvx "ruff==${pin}" fetches from PyPI unpinned-by-hash (no version-independent supply-chain issue introduced): this is a local-developer-only fallback, not exercised by CI (which still uses the hash-locked pip install per docs/CI-RUNNER-ROUTING.md), and it resolves to the same version CI already trusts rather than loosening the trust boundary. Not treating this as a finding — same conclusion as the prior review round, unchanged by the new commit.
  • Docs/CHANGELOG/manifest changes are prose- and version-only; no contract or security surface.

Confidence: high (both commits fully diffed against origin/main; no new code paths beyond what was assessed in the prior round on this PR).

Invoke ruff_reported_version outside || so set -e stays intact under ShellCheck.
@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude encountered an error after 0s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

kyle-sexton added a commit that referenced this pull request Aug 8, 2026
…#1983)

## Summary

`fleet-state.sh` excludes `$HOME` from project context, because that
directory is
USER scope and reading `$HOME/.claude/settings.json` as the project map
duplicates the user map. The exclusion did not hold.

It compared the native path `pwd -W` reports against `$HOME` exactly as
the
environment carried it — one directory, two spellings. An MSYS mount
alias has no
drive letter for the normalizer to reconcile, so `$HOME=/tmp/x` never
equalled the
`C:/Users/…/Temp/x` reported for the same place. `PROJECT_ROOT` was then
set to
`$HOME` and the duplication happened silently.

Both sides now go through `pwd -W` before normalization. Normalizing
harder could
not have fixed it: the two inputs disagreed before the normalizer saw
them.

## Changes

- `fleet-state.sh`: derive the `$HOME` side of the comparison through
`pwd -W`
(falling back to `pwd`, then to `$HOME`), with the reasoning recorded at
the site.
- `fleet-state.test.sh`: record that the existing case is load-bearing
only on
Git Bash — on a POSIX runner both spellings already agree, so it passes
there
  whether or not the exclusion works.
- `claude-ops` **0.27.1 → 0.27.2** with a matching CHANGELOG entry.

Closes #1982

## Why CI never caught it

`plugin-gate` runs on `ubuntu-24.04`, where `$HOME` and `pwd` agree, so
the case
passes regardless. It was found by a local full-suite sweep and split
out of
#1972, which flagged it as a different root cause from that ticket's two
ruff
failures.

## Related

- #1972 — the sweep that found this; its two remaining failures are
ruff-default
  churn with a separate root cause and are not touched here.
- #1856, #1871, #1953 — the ruff pin divergence behind those two, not
this one.

## Test plan

- [x] Standalone reproduction of the test's exact fixture:
`currentProject` was
      `false`, is now `null`
- [x] `fleet-state.test.sh` — 34 cases, 0 failed (was 34 / 1 failed)
- [x] `shellcheck -x` on the changed script — clean
- [x] `scripts/check-shell-portability.sh` — no unexcused GNU-only
constructs
- [x] `scripts/check-changelog-parity.sh --check-bump` — PASS
- [x] `scripts/validate-plugins.sh` — PASS
- [x] `markdownlint-cli2` on the CHANGELOG — 0 issues
- [ ] CI green on the pushed head

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Aug 8, 2026
…k the repo (#1991)

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`/`F`
rules 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](https://github.com/astral-sh/ruff/releases/tag/0.16.0)).
That single change is behind both failures in #1972: the expansion
brought the
`TRY` category into the default set, and the removals took `E712` out of
it.

## Fix

- **`ruff.toml`** selects `E4`, `E7`, `E9`, and `F` — 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-select` as each category is cleared.
  Registered as `locally-owned` for this repo in
melodic-software/standards#336 (merged), which is what keeps it from
being a
  second unregistered lint policy.
- **Eval fixtures are excluded.** An audit skill's fixture holds the
defect its
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 these
exclusions 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.sh` case 4e selects `E712` in its fixture**
instead of
relying 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.
- **Drops an unused `import stat`** in `python3_alias_probe.py` — a real
`F401`
the pinned set surfaces (line 90 calls `path.stat()`, the method, not
the
  module).

## 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 `isinstance` guard —
`babysit_gh.py:603,699,728` on a GraphQL response,
`babysit_lease.py:114`,
`manage_feedback_ledger.py:176`, and `refresh_pr_branch.py:112` on state
files
this 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 `TypeError` should be raised upon
encountering 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 `RuntimeError` as "an
error
is 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 `RuntimeError` at these sites would change behavior if they
were
  retyped. No `noqa` is needed; the sites stand as written.
- **Separately: `TRY` is not in the selected set**, so they are not
findings
today regardless. Recorded so a future `extend-select = ["TRY"]` knows
the
category was examined and rejected on merits, not merely left
unselected.

## What the prefix selection does not buy

Review raised that `E4`/`E7`/`E9`/`F` are prefixes, so a later release
adding a
rule 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.txt` is held equal to the fleet
inventory
(melodic-software/dotfiles `.chezmoidata/uv-tools.yaml`), so a
newly-added rule
cannot 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
version
pin 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
bare
  0.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
suites
that invoke ruff at all (`grep -rl 'ruff check\|ruff format'
--include=*.sh`
finds `plugins/ruff-format/hooks/ruff-format.sh` and `engine.test.sh`),
so no
  other suite could be affected by a lint config.
- The ruff-format fixtures build their own repos under `mktemp -d`,
which
resolves 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.
- Local ruff is 0.16.0, matching the fleet pin in `dotfiles`
`.chezmoidata/uv-tools.yaml`, so these runs reproduce what a developer
sees.
  CI still installs 0.15.22; #1953 is what closes that half.

## Related

- melodic-software/standards#336 — registers the `ruff` component as
  `locally-owned` for this repo; closes melodic-software/standards#335.
- Refs #1953 — the Dependabot bump to ruff 0.16.0. This makes it safe
against
the failure that happened: with `select` explicit, the bump cannot
change
  which CATEGORIES run.
- Refs #1871 — resolves ruff from the pin file rather than PATH; it
reads the
  pin dynamically, so it needs no value change once #1953 lands.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
kyle-sexton and others added 2 commits August 8, 2026 04:08
Resolves the source-control manifest and changelog against 0.47.1, and
re-frames the entry: the wrapper resolves ruff from the declared pin at run
time, so it follows the repository's version rather than freezing 0.15.22.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMBm1Ae29KhZmjFxw2wdZ7
…s lookup

The wrapper's value was framed as pinning local ruff DOWN to CI's 0.15.22. The
fleet moved to 0.16.0 and CI follows it, so that framing is now backwards: what
the wrapper is for is resolving the tool from the declared pin instead of from
PATH, in whichever direction the pin points. It reads the pin at run time, so no
value in it changes.

Also fixes the wrapper lookup itself. engine.test.sh re-derived the repository
root from BASH_SOURCE AFTER cd-ing to its own directory, so a relative
invocation resolved against the new cwd and landed outside the repo. The lint
pass then printed "SKIP: scripts/run-ruff.sh not found" and the suite passed
without linting anything — running it from the repository root, which is how
scripts/run-plugin-tests.sh runs it, took that path every time. The script
directory is now captured once, before the cd.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMBm1Ae29KhZmjFxw2wdZ7
@cursor

cursor Bot commented Aug 8, 2026

Copy link
Copy Markdown

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

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 58s —— View job


Code review

  • Gather context (diff since last successful review, changed files)
  • Review scripts/run-ruff.sh (SC2310 fix commits 3d34592/eb65ec4)
  • Review engine.test.sh (a4706daREPO_ROOT/SCRIPT_DIR fix)
  • Review docs/CI-RUNNER-ROUTING.md (a4706da — direction-agnostic pin framing)
  • Review CHANGELOG.md / plugin.json state after the origin/main merge
  • Post findings

Note on scope: the two "encountered an error" runs at 22:39/22:40 and the rate-limit run at 23:15 posted no findings (infrastructure failures, not review verdicts, per the automated warning comment). This pass reviews everything pushed since the last completed review (729f3a9, 19:58): commits 3d34592, eb65ec4, the origin/main merge (a4ea6c8), and a4706da.

This environment's sandbox blocked git fetch, running bash scripts/run-ruff.sh, and shellcheck directly (all required approval that wasn't available) — findings below are from static reading of the final file contents and git diff/git show/git log against the locally available history, not from execution. The prior review round's own execution-based claims (e.g. realpath verification) are unverified by me and not restated here.

Two inline findings posted, both non-blocking:

  1. scripts/run-ruff.sh (🟡 nit) — the SC2310 fix duplicates a ~12-line version-probe block across two call sites instead of using this repo's own established # shellcheck disable=SC2310 convention (seen in scripts/check-skill-leaf-names.sh and others), which would have kept the single ruff_reported_version() helper.
  2. plugins/source-control/CHANGELOG.md (🟡 suggestion) — commit a4706da fixes a real bug (the lint pass was silently no-op'ing via SKIP: scripts/run-ruff.sh not found because REPO_ROOT was re-derived from BASH_SOURCE after a cd) and reframes the docs, but ships no changelog entry or version bump for it, breaking the entry-per-fix convention this same PR's earlier review rounds already flagged and had fixed once.

What checked out: engine.test.sh's SCRIPT_DIR fix is correct — capturing it once before the cd and reusing it for REPO_ROOT resolves the exact bug the commit message describes, and the up-5-levels relative path math ($SCRIPT_DIR/../../../../..) still lands on the repo root. run-ruff.sh's resolution order (PATH match → uvx → error) and quoting are unchanged and sound. plugin.json is well-formed and its 0.47.2 matches the CHANGELOG's newest heading (no manifest/changelog desync as of the current head). Security is out of scope for this lane (dedicated security lane already posted clean findings on the earlier commits in this PR; no workflow/permission surface touched since).
· branch fix/dev-env-ruff-pin-alignment

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 6s —— View job


Security review

  • Gather context (gh pr diff, changed files vs origin/main)
  • Review scripts/run-ruff.sh
  • Review plugins/source-control/skills/babysit-prs/scripts/engine.test.sh changes
  • Review docs/CHANGELOG/manifest changes
  • Post findings

No security issues found.

Scope reviewed: the full diff against origin/main at HEAD a4706dadocs/CI-RUNNER-ROUTING.md, plugins/source-control/.claude-plugin/plugin.json (version bump only), plugins/source-control/CHANGELOG.md (prose only), plugins/source-control/skills/babysit-prs/scripts/engine.test.sh, scripts/run-ruff.sh. No GitHub Actions workflow files are touched, so there's no pull_request_target/workflow_run, permissions:, or github-context injection surface here — out of scope per the review brief, and zizmor's lane already covers that category statically.

This PR has been reviewed on this security lane three times already (commits 4bc8909, 729f3a9) with no findings. The only change since then is commit a4706da, which:

What I checked in this incremental change:

  • SCRIPT_DIR and REPO_ROOT are still derived exclusively from ${BASH_SOURCE[0]} (the script's own invocation path), never from environment variables, caller-supplied arguments, or file content — no new attacker-influenceable input enters the path-construction logic.
  • RUN_RUFF is still invoked as bash "$RUN_RUFF" with an absolute, locally-computed path — no PATH-hijack surface, unchanged from the prior review pass.
  • scripts/run-ruff.sh itself is untouched by this commit; the pin-parsing (awk extraction), quoting ("$got" == "$pin", "ruff==${pin}"), and uvx fallback behavior already reviewed clean in the prior two passes are unchanged.
  • The behavioral fix here (script previously silently landed on the wrong directory post-cd, causing the lint pass to silently no-op via the SKIP branch) is a correctness bug, not a security-relevant trust-boundary change — it affects whether linting runs, not what it's allowed to do.

Confidence: high — this is a narrow, mechanical follow-up to a script already fully vetted across three prior review passes on this PR.

@kyle-sexton kyle-sexton changed the title fix(dev-env): align local ruff with CI pin 0.15.22 fix(dev-env): resolve ruff from the declared pin instead of PATH Aug 8, 2026
Comment thread scripts/run-ruff.sh
Comment thread plugins/source-control/CHANGELOG.md
kyle-sexton and others added 2 commits August 8, 2026 04:20
Review found the SC2310 workaround had duplicated a twelve-line probe rather
than using this repo's established disable-comment convention. Restores the
single ruff_reported_version helper with a scoped disable at each call site.

The 0.47.2 changelog entry also described only the resolution order, not the
lookup bug the previous commit fixed — the entry now covers both, since the
feature it describes was inert on a relative invocation until that fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMBm1Ae29KhZmjFxw2wdZ7
@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 10s —— View job


Security review

  • Gather context (git diff origin/main...HEAD, changed files)
  • Review scripts/run-ruff.sh
  • Review plugins/source-control/skills/babysit-prs/scripts/engine.test.sh changes
  • Review docs/CHANGELOG/manifest changes
  • Post findings

No security issues found.

Scope reviewed: the full diff against origin/main at HEAD 748e669scripts/run-ruff.sh, plugins/source-control/skills/babysit-prs/scripts/engine.test.sh, docs/CI-RUNNER-ROUTING.md, plugins/source-control/CHANGELOG.md, plugins/source-control/.claude-plugin/plugin.json (version bump only). No GitHub Actions workflow files are touched by this PR's own diff — the claude-review.yml / claude-security-review.yml SHA bumps and the .github/requirements-ci.txt ruff-hash bump visible in git log came in via merging origin/main and are already identical to origin/main's current state (not part of git diff origin/main...HEAD), so they're out of scope here and were reviewed on their own PRs.

This PR has now been reviewed on this security lane four times across its history (commits 4bc8909, 729f3a9, a4706da) with no findings. The only change since the last completed pass (a4706da) is commit 748e669, which is a pure refactor + doc/changelog follow-up responding to the prior code-review round's nits — not new functional surface:

  • scripts/run-ruff.sh — collapses the two inlined set +e / ruff --version probe blocks back into a single ruff_reported_version() helper, called twice with # shellcheck disable=SC2310
    # `ruff --version` prints "ruff X.Y.Z"; anything else (including a failed probe)
    # is reported as the caller's fallback rather than treated as a version.
    ruff_reported_version() {
    local out
    out="$(ruff --version 2>/dev/null)" || return 1
    printf '%s\n' "${out##* }"
    }
    and
    if command -v ruff >/dev/null 2>&1; then
    # shellcheck disable=SC2310 # same probe, same fallback — reported, never trusted
    got="$(ruff_reported_version)" || got="unknown"
    echo "error: ruff on PATH is ${got}, but CI pins ruff==${pin} (.github/requirements-ci.txt)." >&2
    echo "error: install the pin, or install uv and re-run (uvx ruff==${pin} ...)." >&2
    . The probe still only ever reads ruff --version output (not attacker-controlled) and feeds it through [[ "$got" == "$pin" ]] string comparison — no new input source, no change to the exec ruff "$@" / exec uvx "ruff==${pin}" "$@" call sites, which remain correctly quoted.
  • CHANGELOG.md — adds prose covering the engine.test.sh path-resolution fix; no manifest version bump accompanies it (still 0.47.2), which is a documentation-completeness question, not a security one.
  • docs/CI-RUNNER-ROUTING.md — unchanged in this commit (already covered in the prior pass).

What I re-confirmed holds across the full diff:

  • pin is parsed from .github/requirements-ci.txt via awk and used only inside double-quoted expansions ("$got" == "$pin", "ruff==${pin}") — no eval, no unquoted expansion, no command-injection path even if that file's content were adversarial.
  • .github/requirements-ci.txt is the same trust root CI already uses for its hash-locked pip install; this script doesn't widen who can influence the pin.
  • SCRIPT_DIR / REPO_ROOT / RUN_RUFF in engine.test.sh are derived exclusively from ${BASH_SOURCE[0]}, never from environment or caller input, and RUN_RUFF is invoked as bash "$RUN_RUFF" (explicit path) rather than resolved via PATH — no path-hijack surface.
  • uvx "ruff==${pin}" fetches from PyPI unpinned-by-hash, but this is a local-developer-only fallback (not exercised by CI's hash-locked pip path) and resolves to the same version CI already trusts — not a finding, consistent with every prior pass on this PR.
    · branch fix/dev-env-ruff-pin-alignment

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 32s —— View job


Code review

  • Gather context (diff since last review, changed files)
  • Review commit 748e669 against prior findings (SC2310 duplication, changelog gap)
  • Review full diff for correctness/maintainability
  • Post findings

Reviewed commit 748e669, the only change since the last completed review pass (a4706da, 08:13). It's a direct follow-up to that pass's two inline findings, and both are resolved:

SC2310 duplication — fixed. scripts/run-ruff.sh#L43-L47 restores a single ruff_reported_version() helper, with a # shellcheck disable=SC2310 comment at each of the two call sites (L50, L62) instead of the duplicated 12-line probe. This matches the repo's own convention (scripts/check-skill-leaf-names.sh, scripts/check-cross-plugin-source-drift.sh, scripts/check-hook-userconfig-argv.sh all use the same pattern). The fallback semantics are preserved: the first call site treats a failed probe as got="" (mismatch → falls through to uvx), the second as got="unknown" (used only in the error message) — same behavior as the pre-duplication version and the two inline call sites before this fix.

Changelog gap — fixed. plugins/source-control/CHANGELOG.md#L19-L25 adds a second bullet under the existing [0.47.2] heading covering the BASH_SOURCE/cd lookup bug from a4706da. Folding it into the same (still-unreleased) 0.47.2 entry rather than minting a new version is correct here — unlike the earlier 0.45.0/[0.45.1] desync this PR hit, 0.47.2 hasn't shipped yet within this PR, and plugin.json already declares 0.47.2 (verified: grep '"version"' plugins/source-control/.claude-plugin/plugin.json0.47.2), so no manifest/changelog mismatch is introduced.

Verification performed this pass: read the full current scripts/run-ruff.sh and confirmed the pin-parsing, resolution order, and quoting are otherwise unchanged from the prior clean review; confirmed .github/requirements-ci.txt still parses to a single ruff== pin (0.16.1); diffed origin/main...HEAD and confirmed the 5 changed files match the PR description exactly. engine.test.sh and docs/CI-RUNNER-ROUTING.md are untouched by this commit and unchanged from the previous clean pass. I was not granted approval to execute shellcheck or bash scripts/run-ruff.sh in this sandbox, so shellcheck-clean and runtime behavior are author-claimed/previously-reviewed, not independently re-executed by me this pass.

No new findings. This PR is ready from a code-review standpoint. Security remains out of scope for this lane (dedicated security lane already posted clean findings across all prior commits on this PR, including this one's predecessor).
· branch fix/dev-env-ruff-pin-alignment

@kyle-sexton
kyle-sexton merged commit 7ae25c2 into main Aug 8, 2026
35 checks passed
@kyle-sexton
kyle-sexton deleted the fix/dev-env-ruff-pin-alignment branch August 8, 2026 08:36
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.

dev-env: local ruff auto-upgraded past the CI pin (0.16.0 vs ruff==0.15.22) — 82 findings on clean main, engine.test.sh fails locally

1 participant