Suppression mechanisms#
SafeLint offers four ways to silence a rule, ranging from one-line surgical to project-wide. Pick the narrowest scope that matches your intent. Broader scopes are easier to forget about.
| Mechanism | Scope | Where it lives | Best for |
|---|---|---|---|
# nosafe |
One line | The file (trailing comment) | "This specific line is intentional" |
# safelint: ignore |
One file | The file (own-line comment) | "This whole file is intentional", generated code, fixtures, vendor stuff |
[tool.safelint.per_file_ignores] |
A glob pattern | safelint.toml / pyproject.toml |
Project policy: "files under tests/ never need their own tests" |
ignore = [...] |
The whole project | safelint.toml / pyproject.toml |
"We've decided this rule doesn't fit our codebase" |
The narrower-scope forms compose with the broader ones: toml-level ignores still apply on top of file-level directives, and inline # nosafe always wins for the specific line it's on. The two TOML-level mechanisms are documented on the Configuration file page; this page covers the in-source forms (# nosafe and # safelint: ignore) plus the run-summary diagnostics that surface what got suppressed.
Inline # nosafe#
Add a # nosafe comment to the end of any line to suppress violations on that specific line. This is the escape hatch for the rare case where a violation is a deliberate, justified choice.
Suppressed violations do not appear in output and do not count toward the blocking total, but a per-code breakdown of what was suppressed is always reported at the end of the run so suppressions remain auditable.
Syntax#
| Comment | Effect |
|---|---|
# nosafe |
Suppress all violations on this line |
# nosafe: SAFE101 |
Suppress only the rule with code SAFE101 |
# nosafe: function_length |
Suppress only the rule named function_length |
# nosafe: SAFE101, SAFE103 |
Suppress multiple rules (comma-separated codes or names) |
Both rule codes (e.g. SAFE101) and rule names (e.g. function_length) are accepted and can be mixed in the same comment.
Examples#
Suppress all violations on a line. Use when a line genuinely triggers multiple unrelated rules and fixing each would make the code worse:
Suppress a single code. Preferred; makes the intent explicit and leaves other rules active:
while True: # nosafe: SAFE501
item = queue.get() # blocking poll, bounded by the caller's timeout
if item is None:
break
Suppress by rule name. Identical behaviour to suppressing by code; use whichever is more readable in context:
Suppress multiple rules. Keep the list short; a long list is a signal the code needs refactoring:
When to use # nosafe#
Use # nosafe when:
- A violation is correct by design and fixing it would make the code worse (e.g. a deliberate
while Truepolling loop with an external timeout). - A third-party integration forces a pattern safelint flags (e.g. a framework-required function signature with many parameters).
- You are mid-refactor and need to commit a transitional state without breaking CI.
Prefer config changes (adjusting thresholds or disabling rules) over # nosafe when the exception applies to the entire project. For a single file or whole-file scope, see the file-level directive below.
File-level # safelint: ignore#
For cases where a whole file is intentionally violating (auto-generated code, code that has to look the way it does to satisfy a vendor protocol, a fixture file, and similar), put a directive in the file itself rather than in toml:
# safelint: ignore: SAFE101, SAFE304
# Auto-generated by openapi-codegen 4.2.1: do not edit by hand.
import requests # ... 800 lines of generated I/O ...
Forms accepted:
| Directive | Effect |
|---|---|
# safelint: ignore |
Suppress every rule for this file |
# safelint: ignore: SAFE101 |
Suppress only the rule with code SAFE101 for this file |
# safelint: ignore: function_length |
Suppress by rule name (equivalent to the code form) |
# safelint: ignore: SAFE101, SAFE304 |
Multiple codes / names, comma-separated |
Placement: the directive must be a comment alone on its line (no preceding code). Trailing comments after code are scope-local and use # nosafe instead, putting a # safelint: ignore after code on the same line is silently skipped, which prevents accidental file-wide scope creep from what was meant as a per-line suppression.
String-literal safety: the parser uses Tree-sitter, so a literal # safelint: ignore inside a docstring or string is correctly ignored.
Typo guard: unknown codes / rule names trigger a safelint: warning: line on stderr, same as the toml-config typo guard. The run continues; the bogus entry is just noted.
Suppressed violations stay auditable: they go into LintResult.suppressed and surface in the per-code breakdown the CLI prints at the end of the run, so file-level suppressions are visible without grepping the codebase.
End-of-run summary#
When suppressions are active, the summary surfaces a per-code breakdown so it is clear which rules were silenced. Codes are ordered by descending count, ties broken alphabetically.
When there are still active violations, the breakdown rides on the post-summary line. The text varies depending on whether any rule emitted advisory suggestions for those violations (added in 1.8.0):
Found 2 errors, 1 warning. [--fail-on=error].
No suggestions available (safelint does not auto-fix). (2 SAFE501, 1 SAFE304 suppressed)
If at least one violation has a suggestion, the line reads
N advisory suggestion(s) available, view via --format json or --format sarif
(safelint does not auto-apply fixes). followed by the same suppression
breakdown. Suggestions are emitted in both JSON output and the SARIF
fixes[] block (advisory by spec). SafeLint never auto-applies,
the line is informational.
When the run is otherwise clean (no active violations), only the all-clear line is printed, the suggestion / no-suggestion line is omitted:
A fully clean run with no suppressions prints a single line in bold green to
match ruff / ty:
Diagnostic output#
Lint violations and the run summary are written to stdout in the ruff/ty multi-line format. Issues that aren't lint violations, typos in your ignore list, malformed TOML, files skipped because they exceed max_file_size_bytes, etc., are written to stderr as single-line safelint: warning: / safelint: error: messages so they stay out of the violation stream and are captured separately by pre-commit, CI, and editor integrations.
Examples:
safelint: warning: unknown entries in ignore list (typo or stale rule?): SAFFE101
safelint: error: failed to parse /path/to/pyproject.toml: Expected '=' after a key in a key/value pair (at line 5, column 12), skipping file
safelint: warning: skipping /path/to/huge_generated.py (12,345,678 bytes exceeds max_file_size_bytes=5,242,880)
Diagnostics fall into two timing buckets:
- Config-load / engine-construction time: typos in
ignore/per_file_ignores, malformed TOML, themax_file_size_bytes = 0fallback warning. These are emitted once, up front, before any file is linted. None of them fails the run on their own; safelint falls back to defaults and continues. - Per-file runtime,
max_file_size_bytesskip warnings fire fromSafetyEngine.check_file()as the engine walks the file list, so they are interleaved with lint output (one warning per oversize file, just before that file's spot in the run). The skipped file produces no violations and does not affect exit status.