Skip to content

INW009 suppression-comment

What it does

Checks the inline suppressions, # inwards: ignore[INW001] reason="...", and reports one that doesn't do its job:

  • error, and the comment hides nothing: it is malformed, has no reason or an empty one, names a code that is unknown or can't be suppressed (INW000, INW004, INW007, INW008, INW009), or is a second directive in the same comment;
  • warning: a valid code that matches no finding on the comment's line, since it would silently hide a new finding there later. A rule that is off in [tool.inwards.rules] isn't reported as unused.

Suppressions are read from the syntax tree, so a string that looks like one (x = "# inwards: ignore[...]") doesn't count.

Why is this bad

A comment is the cheapest way for an agent to turn a check green. Requiring a code and a reason keeps every exception specific and reviewable, and INW009 makes sure a suppression that doesn't meet that bar fails loudly instead of being ignored in silence, where the agent would think the finding is handled. An unused suppression is a trap: the next import on that line would be hidden without anyone looking at it.

Example

With the layers from INW001, a suppression without a reason:

shop/domain/order.py
import shop.infrastructure.db  # inwards: ignore[INW001]
inwards check
shop/domain/order.py:1:32: INW009 This suppression comment hides nothing. It has no reason: say why in `reason="..."`.
  fix: Fix the comment, or remove it and fix the finding it was meant to hide.
    1. Write it as `# inwards: ignore[INW001] reason="why this import is allowed"`, with codes of rules that can be suppressed and a reason a reviewer can check.
    2. Better, fix the import it covers, so no suppression is needed.
  docs: https://sircypkowskyy.github.io/inwards/rules/INW009/

The INW001 finding on the same line is reported too, since the comment hides nothing. Fixed: the suppression says why.

shop/domain/order.py
import shop.infrastructure.db  # inwards: ignore[INW001] reason="legacy adapter, removed in #210"
inwards check
1 finding suppressed by inline comments.

A suppression on a line with no matching finding is a warning:

shop/application/place.py
import shop.domain.order  # inwards: ignore[INW001] reason="legacy"
inwards check
shop/application/place.py:1:27: INW009 (warning) This suppression of INW001 matches no finding on its line, so it would silently hide a new one.
  fix: Remove the unused suppression.
    1. Remove INW001 from the comment, or the whole comment if no code is left.
  docs: https://sircypkowskyy.github.io/inwards/rules/INW009/

How to fix

  1. Best, fix the finding the comment covers, so no suppression is needed.
  2. Otherwise write the comment as # inwards: ignore[CODE] reason="why", on the line the finding points at: for a parenthesised import, the imported name's line; for a dynamic import spread over several lines, the call's first line. Several codes go in one directive, ignore[INW001,INW005].
  3. Remove a suppression that matches no finding, or the code in it that doesn't.

Fix safety

No automatic fix (autofix: false). Only a person can write the reason, and removing an unused suppression is a one-line edit the reviewer should see.

Configuration

Severity error, with unused suppressions as warnings. [tool.inwards.rules] can turn INW009 off or change its level like any rule (Configure rules). It can't suppress itself.

agent-suppressions in [tool.inwards] decides what the Claude Code hooks do with suppressions the agent writes (ADR-028):

  • "deny" (default): a suppression counts only when it was in the file at session start. One the agent adds, copies, moves or widens with another code is ignored, so its finding blocks the edit and the Stop gate like any other, with a note saying why. inwards stats counts these rejected suppressions.
  • "allow": every suppression counts.

inwards check, CI and the language server always honour valid suppressions. Every output format counts suppressed findings, and SARIF lists them as suppressed results with the reason as the justification.

Known limitations

  • A reason can't contain ", and nothing checks that it says anything useful. Review does.
  • Under "deny", a suppression in a file that was uncommitted or untracked at session start, or in a project outside git, and too large for the hooks to keep a copy of (over 512 KiB, or past 4 MiB of such files), counts as new once the agent edits the file (#134, #157). Commit a suppression before handing such a file to an agent.
  • Renaming or moving a file makes its suppressions new, and so does reaching it through a symlink.
  • There is no blanket, next-line or file-level form, by design.

References