From 79649737a1bdeed713aeb371346de2eeef16bfc5 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Sat, 29 Aug 2026 13:09:16 +0200 Subject: [PATCH] feat: gate suppressions mechanically instead of relying on discipline 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) Claude-Session: https://claude.ai/code/session_013yaBT45KhHqxTKVCTA87iy --- .github/workflows/ci.yml | 10 ++++++++++ .pre-commit-config.yaml | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2371f37..2adf001 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c8aea1a..b9d3eb7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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