ADR-0893: Pre-commit config audit — 2026-05-30¶
- Status: Accepted
- Date: 2026-05-30
- Deciders: lusoris
- Tags: ci, lint, hygiene, pre-commit
Context¶
.pre-commit-config.yaml had not been audited since the last formatter / linter bumps several months ago. Three drift classes had accumulated:
- Stale tool revisions —
isortwas pinned at5.13.2(released 2023-12-13, ~17 months old at audit time),ruff-pre-commitatv0.15.13(two patch versions behindv0.15.15). - Missing belt-and-suspenders hooks — the audit walked the
pre-commit-hooksrepo's hygiene catalogue and confirmed five of the requested six guards (check-yaml,end-of-file-fixer,mixed-line-ending,check-merge-conflict,detect-private-key) were already present, butforbid-new-submoduleswas missing — a supply-chain gap, since the fork pulls upstream viasubprojects/(Meson wraps) andffmpeg-patches/(out-of-tree patches), never via.gitmodules. A stray submodule entry would bypass the wrap pinning machinery entirely. - Autoupdate noise —
pre-commit autoupdateproposed downgradinggitleaksfromv8.30.1(current) tov8.30.0. Verified viagit ls-remote --tagsthatv8.30.1is the latest upstream tag; the autoupdate heuristic mis-ranks tag sort order on releases that land out of branch order. Skipped.
pre-commit run semgrep-local --all-files was failing with exit code 2 (io_uring / RLIMIT_MEMLOCK exhaustion under pre-commit's per-CPU fan-out). That failure is pre-existing on master and is the subject of the in-flight PR #340 (ADR-0867, adds require_serial: true to the semgrep-local hook). Outside this audit's scope; this PR does not touch the semgrep-local block to avoid a merge conflict with #340.
PR #342 (ADR-0866) is in flight and inserts a new markdownlint-cli2 repo block between shellcheck-py and gitleaks. This audit intentionally does not touch that file region either.
Decision¶
Apply the following deltas to .pre-commit-config.yaml:
- Add
forbid-new-submodulesunder thepre-commit/pre-commit-hooksblock, with a comment explaining the fork's wraps-and-patches dependency posture. - Bump
isortfrom5.13.2to6.0.1(latest stable;profile = "black"semantics unchanged in 6.x; one latent import-grouping fix surfaced intools/vmaf-tune/tests/test_codec_adapter_av1_videotoolbox.pyand is applied in the same PR per the touched-file rule). - Bump
ruff-pre-commitfromv0.15.13tov0.15.15(two patch versions; no rule changes affect the fork's[tool.ruff]selection set). - Keep
gitleaksatv8.30.1— autoupdate's suggested downgrade tov8.30.0was rejected after verification against the upstream tag list.
Other hooks (clang-format v22.1.5, black 26.5.1, shfmt v3.13.1-1, shellcheck v0.11.0.1, conventional-pre-commit v4.4.0) are already on the latest upstream revisions and untouched.
Larger-jump options (e.g. isort 7.x / 8.x, ruff 0.16.x betas) were deferred — the touched-file lint-clean rule (CLAUDE.md §12 r12) makes the gate increasingly strict over time, so each bump needs its own diff-with-blame audit. The 6.0.1 step is the safe one.
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
Apply pre-commit autoupdate verbatim | One-command, zero thought. | Would downgrade gitleaks (v8.30.1 → v8.30.0); would jump isort to 9.0.0a3 (alpha pre-release); skips the missing-hook audit entirely. | Autoupdate's tag-sort heuristic produces wrong answers on repos that release point releases out of branch order; alpha pre-releases violate the project's "stable upstream pins only" hygiene. |
| Bump isort to 8.0.1 (latest stable) | Latest features, Python 3.13 support. | 8.x dropped Python 3.8 (we're on 3.14 so OK), but 7.x→8.x changelog calls out several profile = "black" interaction tweaks; needs a wider audit than this audit is sized for. | Conservatism — 6.0.1 has been the de facto stable line for ~18 months, ships the Python 3.13 fix we care about, and keeps the diff small. |
Add markdownlint-cli2 here too | Single audit PR covers all hygiene. | Direct conflict with PR #342 (already in flight, ADR-0866). | Coordination — one PR per moving config block. |
Add require_serial: true to semgrep-local here | Single audit PR closes the io_uring bug. | Direct conflict with PR #340 (already in flight, ADR-0867). | Same coordination argument. |
Skip forbid-new-submodules (the fork has no submodules today) | One fewer hook to maintain. | Defense-in-depth: an accidental git submodule add (e.g. an agent following stale instructions) would bypass the subprojects/ wrap pinning and the SBOM machinery entirely. The hook costs ~0 ms at commit time. | Cheap insurance against a real failure mode. |
Consequences¶
- Positive:
- Submodule additions are now caught at commit time, not at code review or post-merge supply-chain scan.
- isort 6.0.1 + ruff 0.15.15 close two minor drift windows against the upstream ecosystem.
- Documents the audit cadence — next audit on a similar drift threshold (~6 months or when CI starts surfacing deprecation warnings on any pinned hook).
- Negative:
- One latent isort fix lands in this PR's diff (
tools/vmaf-tune/tests/test_codec_adapter_av1_videotoolbox.py). Acceptable per the touched-file rule. - Neutral / follow-ups:
- Re-audit after PR #340 + PR #342 land — the in-flight rebase deltas should be trivial (different file regions).
- Future bumps to isort 7.x / 8.x deserve their own ADR with a diff-with-blame audit of the changed default rules.
References¶
- ADR-0867 — in-flight, fixes the
semgrep-localio_uring failure; this audit defers to it. - ADR-0866 — in-flight, wires
markdownlint-cli2into pre-commit; this audit does not touch that file region. - ADR-0278 — touched-file lint-clean rule (CLAUDE.md §12 r12) makes each gate strictness ratchet meaningful.
- Source:
req(user prompt 2026-05-30 — "Audit.pre-commit-config.yamlfor missing hooks, stale versions, broken hooks").