Skip to content

What Happens When a Team Starts Suppressing Lint Rules

An AI assistant's fastest fix for a lint error is often silencing it, not solving it. Here's how to measure suppression creep before it spreads.

· · 7 min read
Rows of red error messages filling a dark terminal screen

The first eslint-disable is never the problem

Nobody notices the first one. A rule fires, the fix is not obvious, someone drops // eslint-disable-next-line above the offending line, and the build goes green again. That’s a completely normal thing for a human to do under deadline pressure, and reviewers wave it through because, in isolation, it’s a defensible call.

The trouble starts when an AI coding assistant becomes the one making that call, over and over, at a pace no human reviewer can match. What happens when the fix-it loop runs a hundred times a week instead of once a month?

Here’s the thing about a model asked to “fix the lint errors”: it doesn’t actually know what the rule is protecting against. It knows the fastest path from red to green, and silencing the rule at the exact line it fired on is almost always shorter than understanding why the rule exists and rewriting the code to satisfy it. Ask an assistant to clear forty no-unused-vars warnings and you’ll get forty variables prefixed with underscores or forty disable comments, not forty small refactors. Ask it to fix @typescript-eslint/no-explicit-any and you get // @ts-ignore more often than you get an actual type. The measurement gets quieter. The thing it was measuring doesn’t improve.

I’ve watched this happen more than once, and it’s never a dramatic moment. It’s a slow accumulation that nobody notices until someone finally greps for it.

Why code review misses the pattern

A single suppression comment reads fine on its own. “This third-party type is genuinely wrong, ignore it here” is a legitimate sentence, and a reviewer skimming a fifteen-line diff has no reason to push back on one line out of fifteen. That’s exactly why the failure mode survives review so well: the unit of judgment (one line, one comment) is never where the problem lives. The problem lives in the aggregate, across weeks, across dozens of pull requests, most of which never touch the same file twice.

Is that a review problem or a tooling problem? Honestly, it’s both, but you can’t fix it by asking reviewers to try harder. Nobody holds the whole file’s suppression history in their head while reviewing a five-line diff, and expecting them to is unrealistic. What you actually need is something that counts.

Yellow and black hazard tape stretched across a warning barrier
Photo by Ash Amplifies on Unsplash

What a real gate measures

A green CI run tells you the linter didn’t fail. It says nothing about how it didn’t fail, and that gap is exactly where suppression creep lives. So what does a gate need to look at instead of the pass/fail flag? If a team wants to gate on code quality rather than vibes, three things actually work, in roughly this order of effort:

Count disable comments over time. This is the cheapest possible metric and also the one most teams skip. A single grep, run on a schedule, pointed at eslint-disable, @ts-ignore, # noqa, # type: ignore, whatever your stack uses:

grep -rn "eslint-disable\|@ts-ignore\|# noqa\|# type: ignore" src/ | wc -l

Run it weekly, log the number, plot it. A number that only goes up is not a stable codebase quietly maturing; it’s a codebase quietly losing the argument with its own linter. What surprised me the first time I set this up on a real project was how obviously the line spiked right where an AI-assisted refactor had gone through, weeks before anyone flagged the files by hand.

Track technical debt ratio, not the disable count alone. SonarQube’s technical debt ratio (remediation cost divided by the cost of rewriting the codebase from scratch) is a different signal from raw suppression count, and it’s worth having both. A file can carry zero disable comments and still be a mess of duplicated logic and unreachable branches; a file can carry three disable comments and be otherwise clean. Sonar’s own documentation on the metric is the reference worth bookmarking if you’re setting this up for the first time. Neither metric alone tells the whole story, which is precisely why relying on “the build is green” tells you close to nothing.

Require a justification next to every suppression. ESLint’s own configuration docs are blunt about this: disabling a rule inline should be restricted to cases with a clear, valid reason, and that reason belongs in the comment, after the trailing description. That’s a low bar, and it’s still worth enforcing with a lint rule of your own (yes, a lint rule that lints your lint suppressions is exactly as recursive as it sounds, and yes, it works).

That’s the opinion, stated plainly: a suppression with no linked ticket and no stated reason should block the merge, the same as a failing test would. I know teams that would call that too strict, and I get the objection. It slows people down on the exact days they can least afford it. My answer is that a team allergic to slowing down for a one-line justification has already told you what it thinks the linter is worth.

The case against banning eslint-disable entirely

Some teams, once they see the trend line, swing hard the other way and ban eslint-disable outright, no comment allowed anywhere in the repo. I think that’s a mistake, and it’s the kind of overcorrection I’d push back on even from a client I like. Some rules genuinely produce false positives against certain patterns, and a codebase with zero escape hatch just pushes people toward worse workarounds: renaming variables to dodge no-unused-vars, wrapping perfectly good code in unnecessary functions to dodge a complexity rule, or reaching for a broader /* eslint-disable */ block that silences everything in a file instead of one targeted line. Rigid zero-tolerance rules tend to produce more creative rule-breaking, not less.

The goal was never “no suppressions.” It’s “no suppressions nobody can account for.” A team that tracks the count, reviews the trend monthly, and requires a reason on each one gets almost all the benefit of a ban with none of the perverse incentives. This ties directly back to the L1 row of the quality-gates table I keep coming back to on this site: what the gate has to measure is the number of suppressions over time and the debt ratio a tool like Sonar reports, not whether the build happened to come back green today.

If you’re building out a CI pipeline and wondering where a check like this slots in relative to everything else you could be running, that’s worth its own breakdown. See Twelve CI Gates: Which I Put First, and Why in That Order for how lint gates rank against the other eleven.

Code editor window showing a JavaScript source file on screen
Photo by Ferenc Almasi on Unsplash

What I’d actually set up this week

So where do you actually start? If I were walking into a codebase today and wanted a suppression gate running by Friday, I’d start small. Grep for the disable patterns, dump the count into a CSV alongside the date, and commit it. That takes maybe twenty minutes and gives you a trend line within a month. Layer in Sonar’s technical debt ratio once you’ve got a SonarQube instance running (Google’s engineering review guide is a good companion read here, since a suppression-justification requirement is really just a code review policy wearing a lint hat). Then, and only then, start actually blocking merges on unjustified suppressions, because blocking before you have a baseline just teaches people to write vaguer justifications faster.

None of this is exotic. It’s a grep command, a spreadsheet, and a review policy someone has to actually enforce. What actually surprised me was how few teams have any of the three, given how cheap each one is on its own. The codebases I’ve reviewed that skipped this step didn’t fail loudly. They just got quieter and quieter, one silenced warning at a time, until the day someone asked why the type checker hadn’t caught an obvious bug and the answer turned out to be three hundred @ts-ignore comments deep in a file nobody had opened in months.