Skip to content

fix(toolchain): lint --fix ships code-changing defaults behind a single flag, no dry-run, no scope fence #2649

Description

@kyle-sexton

Problem

/toolchain:lint --fix is the weakest-gated tree-mutating surface in the fleet. The flag is the only gate: no confirmation step in the skill body, no dry-run mode, no LOC budget, and no file cap.

That would be defensible if the fix commands were formatting-only. They are not — the bundled defaults change code:

  • plugins/toolchain/reference/ecosystems/python.yaml:9uv run ruff check . --fix && uv run ruff format .
  • plugins/toolchain/reference/ecosystems/go.yaml:37golangci-lint run --fix ./...
  • plugins/toolchain/reference/ecosystems/typescript.yaml:9npx biome check --write .

The sharpest instance

The Python fix-cmd targets . — the whole tree, not the changed files — and carries no --unfixable F401, so it deletes unused imports.

The fleet already knows this is hazardous. plugins/ruff-format/hooks/ruff-format.sh:238 runs the guarded form, and its header (:19-23) explains exactly why:

--unfixable F401 protects just-added imports: during iterative editing an import often lands one edit before the code that uses it […] F401 still surfaces as an advisory finding — only the auto-deletion is suppressed.

The hook has a regression test for the behavior (ruff-format.test.sh:255 "unused import preserved (--unfixable F401) but reported"). Two surfaces in the same repo take opposite positions on the same fix, and the unguarded one is the one with no confirmation gate.

Note the asymmetry is deliberate on the check side — python.yaml:8 uses --no-fix for check-cmd. Only the fix path is unguarded.

Why it matters now

Unrelated adoption research surveyed every findings→apply relay in the fleet. Every other mutator has a gate of some kind: review:fanout fix has a confirmation step plus a surface-instead-of-applying carve-out, claude-memory:audit fix is documented as never batch-applying without approval. This one has the flag and nothing else — and it is reachable non-interactively.

Suggested direction

Not prescribing the fix, but the options that keep the skill useful:

  1. Add --unfixable F401 to the Python fix-cmd, matching the hook's already-tested position.
  2. Scope fix-cmd to the detected changed files rather than ., consistent with the skill's own ecosystem-detection step.
  3. Add a dry-run and/or a confirmation gate for the fix path, matching the pattern the other mutating surfaces use.

(1) is the smallest change that closes the sharpest edge; (2) and (3) are the general fix.

Verification

All paths and line numbers verified against 065877c8.

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: mediumReal value, no hard deadline; normal backlog flow.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions