feat: gate suppressions mechanically instead of relying on discipline
Some checks are pending
CI / check (push) Waiting to run

This repo has zero `# noqa` and zero `# type: ignore`, and the only reason it
stays that way is a gate. Nothing was enforcing it: testsAndMisc has a
no-noqa pre-commit hook and signal-bot did not, so the rule lived entirely in
whoever was editing. During this rewrite a suppression was reflexively typed
three times before being caught by hand, which is exactly the failure a hook
makes impossible.

Blocked in both places: a pygrep pre-commit hook, and a CI step mirroring it
so a commit made without hooks installed cannot land one on main. Verified
by canary: a file containing `# noqa: F401` exits 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yaBT45KhHqxTKVCTA87iy
This commit is contained in:
Krzysztof Rudnicki 2026-08-29 13:09:16 +02:00
parent 749ad28f4b
commit 9193360461
2 changed files with 25 additions and 0 deletions

View File

@ -26,6 +26,16 @@ jobs:
uv venv .venv
uv pip install --python .venv/bin/python -e '.[dev]'
# Mirrors the no-noqa pre-commit hook. A suppression must not be able
# to reach main just because someone committed without hooks installed.
- name: block suppressions
run: |
if grep -rnEi '#\s*(noqa|type:\s*ignore|ruff:\s*noqa)' \
--include='*.py' signal_bot fake_signal tests; then
echo '::error::suppressions are not allowed; fix the finding or ask first'
exit 1
fi
- name: ruff format
run: .venv/bin/ruff format --check .

View File

@ -30,6 +30,21 @@ repos:
- repo: local
hooks:
# Suppressions are blocked mechanically, not by discipline. This repo
# has zero of them and the only way it stays that way is a gate: the
# rule is "fix the finding, or ask first", and a habit is not a gate.
- id: no-noqa
name: block noqa / type:ignore comments
entry: '(?i)#\s*(noqa|type:\s*ignore)'
language: pygrep
types: [python]
- id: no-ruff-noqa
name: block file-level ruff noqa
entry: '(?i)#\s*ruff:\s*noqa'
language: pygrep
types: [python]
# The shared 250-line cap. Checks only the files in the commit, so an
# unrelated commit never breaks. No baseline, no allowlist -- those are
# suppressions. The exemption list lives with the checker in ~/utils so