Skip to content

docs(ruff-format): document TOML inline-table opt-in-gate limitation - #896

Merged
kyle-sexton merged 1 commit into
mainfrom
fix/876-toml-inline-table-opt-in-gate
Jul 21, 2026
Merged

docs(ruff-format): document TOML inline-table opt-in-gate limitation#896
kyle-sexton merged 1 commit into
mainfrom
fix/876-toml-inline-table-opt-in-gate

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Summary

ruff-format's pyproject.toml opt-in-gate check uses a line-anchored grep for a [tool.ruff] section header. TOML also allows the equivalent config as an inline table under a bare [tool] header ([tool] + ruff = { ... }) — Ruff itself honors this form, but the gate's grep does not recognize it, so a repo using that style is treated as "no config" and the hook silently skips (a false-negative opt-in, not an incorrect edit).

Fix

Issue #876's own triage comment already decided the fix direction: "document the inline-table form as a known limitation in BOTH hooks... rather than add a fragile heuristic." A robust detection needs real TOML parsing (inline tables span lines, nest arbitrarily, and the key can appear in any order under [tool]), and a multi-pass grep heuristic risks false positives from an unrelated key or a commented-out line — out of scope for a fast, dependency-free, cross-platform bash hook.

This PR implements that decision for ruff-format:

  • Documented the limitation in the hook's inline comment and the README's opt-in bullet.
  • Added a test case (Case 1d) that locks in the current, intentional (skip) behavior for the inline-table form.
  • No gate logic change.
  • Bumped plugins/ruff-format/.claude-plugin/plugin.json to 0.4.4 + CHANGELOG entry.

This draft is itself the "veto before merge" checkpoint the triage comment asked for — if a maintainer wants a heuristic or a TOML-parse path instead, this is the place to say so before it merges.

Verification

  • bash plugins/ruff-format/hooks/ruff-format.test.sh — 52/52 passing, including the new inline-table case.
  • shellcheck on both modified hook/test scripts — clean.

Related

Closes #876

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o

…(0.4.4)

The pyproject.toml opt-in gate's line-anchored grep only recognizes the
`[tool.ruff]` header form. TOML also allows the equivalent config as an
inline table under a bare `[tool]` header, which Ruff itself honors but
the gate does not detect — a false-negative opt-in, not a wrong edit.

Per the issue's own triage decision, a robust fix needs real TOML parsing
(inline tables span lines, nest arbitrarily, and the key can appear in any
order), and a multi-pass grep heuristic risks false positives from an
unrelated key or a commented-out line — out of scope for a fast,
dependency-free, cross-platform bash hook. Documented the limitation in the
hook, the README, and locked in the current (skip) behavior with a test
case instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
@kyle-sexton kyle-sexton added the automated Opened by automation. label Jul 21, 2026
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Security Review — PR #896

  • Gather PR diff and changed files
  • Review ruff-format.sh for injection surfaces, unsafe shell patterns
  • Review ruff-format.test.sh for unsafe patterns
  • Review plugin.json, README.md, CHANGELOG.md for supply-chain / config risks
  • Post security findings

Findings

No security issues found.

This PR is documentation-only with one new test case. Each changed file is analyzed below.

plugins/ruff-format/hooks/ruff-format.sh (+11 lines)

Lines 132–141 add only comment text above the existing CONFIG_FOUND="" variable. Zero logic changes. The grep pattern on line 149 remains unchanged and is correctly anchored (^[[:space:]]*\[tool\.ruff(\]|[.])). No injection surface, no behavioral change.

plugins/ruff-format/hooks/ruff-format.test.sh (+16 lines)

The new Case 1d block (lines 162–176) follows the exact same structure as the three existing opt-in gate cases (1, 1b, 1c):

  • REPO_PI is rooted under $WORK (mktemp -d), so all paths are system-generated and contain no shell or JSON metacharacters.
  • printf '[project]...' writes a static, hard-coded string to a fixture file — no variable interpolation.
  • run_hook "$REPO_PI/inline.py" passes the path into the existing run_hook function, which uses printf '...%s...' "$file_path" to embed it into a JSON envelope. This is a pre-existing pattern shared by every test case in the file; it is not introduced or widened by this PR. The paths are mktemp-generated and safe.

plugins/ruff-format/.claude-plugin/plugin.json

Version field bumped 0.4.30.4.4. No new permissions, no new MCP endpoints, no dependency changes.

plugins/ruff-format/README.md / plugins/ruff-format/CHANGELOG.md

Prose-only additions. No URLs, no executable content, no config that affects runtime behavior.


Summary: All changes are comments, documentation, and a single test case that exercises a pre-existing skip path. There are no new code paths, no widened shell execution surfaces, no new external calls, and no supply-chain changes. The PR is safe to merge from a security perspective.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


PR Review: docs(ruff-format) — TOML inline-table opt-in-gate limitation

  • Gather context and read changed files
  • Review hook script changes (ruff-format.sh)
  • Review test changes (ruff-format.test.sh)
  • Review documentation changes (README.md, CHANGELOG.md)
  • Review manifest changes (plugin.json)
  • Assess correctness, security, and CLAUDE.md alignment
  • Post final review

Summary

Documentation-only PR: adds a known-limitation notice to the ruff-format hook's inline comment, README opt-in bullet, CHANGELOG, and a test that locks in the current (skip) behavior for pyproject.toml inline-table config. No gate logic changes. Five files changed, no execution-path changes.

Overall verdict: approved. No blocking issues. Two non-blocking observations below.


Correctness

Hook comment (ruff-format.sh L132–141) — the rationale is accurate and well-placed directly above the grep that has the limitation. The explanation covers what the grep misses, why it fails safe, and why a heuristic fix was rejected (real TOML parsing needed). No issues.

Test Case 1d (ruff-format.test.sh L162–176) — the fixture correctly uses [tool] + ruff = { line-length = 88 } (valid TOML inline-table), asserts exit 0, silent output, and file left untouched. The test locks in intentional behavior rather than testing desired behavior — which is the right approach for a documented limitation. Follows the exact same pattern as Cases 1, 1b, and 1c. No issues.

README (README.md L20–24) — the limitation is appended inline in the opt-in bullet, stays in context, and matches the wording in the hook comment. No issues.


Non-blocking observations

1. ### Documented is not a standard Keep-a-Changelog category

The Keep a Changelog 1.1.0 spec defines exactly six section types: Added, Changed, Deprecated, Removed, Fixed, Security. The [0.4.4] entry uses ### Documented, which isn't in that set. It reads clearly and conveys the right intent, but a strict reading of the spec would put this under ### Changed (the documentation changed). This is minor and not worth holding the PR for — just worth knowing for future entries.

2. typos-format half of #876 explicitly deferred

The PR body explains the deferral to #884 clearly. The reasoning is sound (if #884 removes the gate entirely, documenting it here is wasted work). No action needed; noting it for anyone following #876 who wonders why only one plugin was updated.


Security

No new execution paths, no code changes, no new egress, no file writes outside temp dirs in tests. CLAUDE.md security criteria (plugin-form-safe, no PII, deny by default on egress) all pass.

CLAUDE.md alignment

  • Repo-agnostic: ✓ No hardcoded paths or project-specific values
  • Configurable without editing the plugin: ✓ No config changes
  • Plugin-form-safe: ✓ No ${CLAUDE_PLUGIN_ROOT} or path changes
  • Versioned: ✓ 0.4.3 → 0.4.4 patch bump is appropriate for docs-only
  • Security-reviewed: ✓ Documentation-only change; no new code execution

Ready to merge. The CHANGELOG category note is cosmetic and can be addressed in a follow-up or left as-is.

@kyle-sexton
kyle-sexton marked this pull request as ready for review July 21, 2026 19:26
@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Post-green review classification (work lane):

Finding Source Classification Disposition
Everything substantive PR review + security review "Approved. No blocking issues." / "Ready to merge" / "No security issues found" No action needed
### Documented isn't a standard Keep-a-Changelog category PR review (nit) Non-blocking, "not worth holding the PR for" per reviewer Left alone

No DEFER-FORBIDDEN class applies (docs+test-only change, no gate logic modified). No inline review threads to resolve (PR-level comments only).

Flipping to ready, handing off to the babysit lane for ongoing watch.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


PR Review — docs(ruff-format): TOML inline-table opt-in-gate limitation

  • Gather context and read changed files
  • Review hook script changes (ruff-format.sh)
  • Review test changes (ruff-format.test.sh)
  • Review documentation changes (README.md, CHANGELOG.md)
  • Review manifest changes (plugin.json)
  • Post final review

Summary

Documentation-only PR that adds a known-limitation notice to the ruff-format hook comment, README opt-in bullet, CHANGELOG, and a test that locks in the current (skip) behavior for the pyproject.toml inline-table form. No gate logic changes.

Verdict: approved. No blocking issues.


ruff-format.sh — lines 132–141

The comment block is accurate and well-placed — directly above the grep that has the limitation. It covers:

  • What the grep misses (the [tool] + ruff = { ... } inline-table form)
  • Why it fails safe (missed opt-in, never a wrong edit)
  • Why a heuristic fix was rejected (real TOML parsing required; multi-pass grep risks false positives)

The grep pattern on line 149 is unchanged (^[[:space:]]*\[tool\.ruff(\]|[.])). No logic change; no injection surface.

ruff-format.test.sh — lines 162–176

Case 1d is correctly structured:

  • Fixture uses valid TOML inline-table syntax: [tool] + ruff = { line-length = 88 }
  • Asserts exit 0, silent stdout, and file left untouched — the correct assertions for a documented-skip path
  • Follows the identical pattern as Cases 1, 1b, and 1c; nothing novel or risky introduced

This locks in intentional behavior rather than desired behavior, which is exactly right for a known limitation.

README.md — lines 14–24

The limitation notice is embedded inline in the opt-in bullet where users will encounter it in context. Wording is precise: names the form that isn't recognized, explains what happens (hook skips), and characterizes the failure mode correctly (fails safe). Not overstated or buried.

CHANGELOG.md — lines 6–17

Non-blocking nit (already flagged in the prior automated review): ### Documented is not one of the six Keep-a-Changelog 1.1.0 categories (Added, Changed, Deprecated, Removed, Fixed, Security). Strictly speaking this belongs under ### Changed. The intent reads clearly regardless — not worth holding the PR for.

plugin.json

Version bumped 0.4.3 → 0.4.4. Patch bump is appropriate for a docs-only change. No new permissions, no new MCP endpoints, no dependency changes.


CLAUDE.md alignment

Criterion Status
Repo-agnostic ✓ No hardcoded paths or project-specific values
Configurable without editing the plugin ✓ No config changes
Plugin-form-safe ✓ No ${CLAUDE_PLUGIN_ROOT} or path reach-outs
Versioned 0.4.3 → 0.4.4 patch bump
Security-reviewed ✓ No new code paths, no egress, no shell surface widened

Ready to merge.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Security Review — PR #896

  • Gather PR diff and changed files
  • Review ruff-format.sh for injection surfaces, unsafe shell patterns
  • Review ruff-format.test.sh for unsafe patterns
  • Review plugin.json, README.md, CHANGELOG.md for supply-chain / config risks
  • Post security findings

Findings

No security issues found.

This PR is documentation-only with one new test case. Each changed file is analyzed below.

plugins/ruff-format/hooks/ruff-format.sh (+11 lines)

Lines 132–141 add only comment text above the existing CONFIG_FOUND="" variable. Zero logic changes. The grep pattern on line 149 remains unchanged and is correctly anchored (^[[:space:]]*\[tool\.ruff(\]|[.])). No injection surface, no behavioral change.

plugins/ruff-format/hooks/ruff-format.test.sh (+16 lines)

The new Case 1d block (lines 162–176) follows the exact same structure as the three existing opt-in gate cases (1, 1b, 1c):

  • REPO_PI is rooted under $WORK (mktemp -d), so all paths are system-generated and contain no shell or JSON metacharacters.
  • printf '[project]...' writes a static, hard-coded string to a fixture file — no variable interpolation.
  • run_hook "$REPO_PI/inline.py" passes the path into the existing run_hook function, which uses printf '...%s...' "$file_path" to embed it into a JSON envelope. This is a pre-existing pattern shared by every test case in the file; it is not introduced or widened by this PR. The paths are mktemp-generated and safe.

plugins/ruff-format/.claude-plugin/plugin.json

Version field bumped 0.4.30.4.4. No new permissions, no new MCP endpoints, no dependency changes.

plugins/ruff-format/README.md / plugins/ruff-format/CHANGELOG.md

Prose-only additions. No URLs, no executable content, no config that affects runtime behavior.


Summary: All changes are comments, documentation, and a single test case that exercises a pre-existing skip path. There are no new code paths, no widened shell execution surfaces, no new external calls, and no supply-chain changes. The PR is safe to merge from a security perspective.

@kyle-sexton
kyle-sexton merged commit 507261f into main Jul 21, 2026
26 of 27 checks passed
@kyle-sexton
kyle-sexton deleted the fix/876-toml-inline-table-opt-in-gate branch July 21, 2026 19:30
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
## Summary

`typos-format`'s hook only ran `typos --write-changes` when the
consuming repo already had a
`typos.toml`/`_typos.toml`/`.typos.toml`/`Cargo.toml`
(`[*.metadata.typos]`)/`pyproject.toml` (`[tool.typos]`) file present.
`typos` ships a built-in spelling dictionary and runs standalone with
zero configuration — a repo config only widens the allowlist/exclude
list, it is not an activation switch. The gate made the hook a silent
no-op on exactly the zero-config repos it was meant to help, defeating
the plugin's purpose.

## Fix

Removed the opt-in config-gate (and its ancestor-walk grep logic)
entirely. `typos --write-changes --force-exclude` now runs
unconditionally on every edit, matching `markdown-format`'s existing
unconditional pattern. `typos`'s own file-anchored config discovery
still applies automatically when a config IS present (allowlist/exclude,
precedence order) — the hook never re-implemented that discovery and
still doesn't; only the activation gate is gone.

Also updated: plugin manifest description/version, README (top-level +
plugin), CHANGELOG, the `setup` skill's `check` step (config presence is
now reported as informational only, never a gate), and the test suite
(the old "gate OFF -> file left untouched" case is replaced with "runs
unconditionally, fixes a real typo with no config present anywhere").

## Verification

Empirical before/after on an isolated zero-config git repo (`this
document has a recieve typo`, no
`typos.toml`/`_typos.toml`/`.typos.toml`/`Cargo.toml`/`pyproject.toml`
anywhere in the ancestor chain):

- **Before** (`origin/main`'s gated hook): file left untouched —
`recieve` unfixed, hook exits silently.
- **After** (this branch's unconditional hook): file rewritten in place
— `recieve` → `receive`.

Full test suite: `bash plugins/typos-format/hooks/typos-format.test.sh`
— **41/41 passing**, including the new unconditional-fix case,
kill-switch, residual-findings, exclude, and telemetry cases.

Closes #884

## Related

- #872 — the merged PR that shipped the opt-in gate this PR removes.
- #876 — covered a TOML inline-table detection gap in both
`typos-format`'s and `ruff-format`'s opt-in-gate grep. #896 already
closed it for the `ruff-format` half (documented as a known limitation
there). The `typos-format` half is now moot: this PR deletes the opt-in
gate and its grep entirely, so there is no gate left for that detection
gap to affect.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated Opened by automation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

typos-format/ruff-format: opt-in config grep misses TOML inline-table form for [tool.X]

1 participant