Skip to content

Why Do Production Codebases Have 400+ eslint-disable Comments (And Is That Bad?)

Problem

When I looked at Claude Code’s leaked source code, I saw 460 eslint-disable comments. My first reaction was: is this a code quality problem?

A Reddit thread exploded with debate. Some developers said this proves “code quality doesn’t matter.” Others argued it shows “pragmatic engineering.”

So I asked myself: when does suppressing lint rules make sense, and when is it just lazy?

What I Found

I started analyzing when eslint-disable appears in production code. Here are the patterns I saw:

Pattern 1: Third-Party API Weirdness

api-client.ts
// Third-party SDK returns `any` for error responses
// Tracking issue: https://github.com/vendor/sdk/issues/123
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const errorResponse: any = sdk.parseError(rawError);

This is pragmatic. The vendor’s types are wrong. I can either:

  1. Wait for them to fix it (maybe never)
  2. Cast to unknown and add runtime checks (adds complexity)
  3. Suppress the rule with documentation (what I did)

I chose option 3 because the fix is tracked, and the scope is limited.

Pattern 2: Gradual Migration

legacy-handler.ts
// TODO: Remove when migration to strict mode completes
// Tracked in JIRA-4521
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const legacyData = oldSystem.getData();

This is also valid. I’m migrating 50,000 lines of code. I can’t do it all at once. Each disable has:

  • A TODO comment explaining why
  • A tracking ticket
  • A plan to remove it

Pattern 3: Framework Requirements

next-page.ts
// Next.js requires getServerSideProps to be exported
// eslint-disable-next-line import/no-default-export
export default function Page() {
return <div>Content</div>;
}
export async function getServerSideProps() {
// ...
}

The framework demands this pattern. I can’t change Next.js. The disable is intentional.

Pattern 4: Intentional Edge Cases

error-handler.ts
// Intentionally catching and ignoring specific errors
// eslint-disable-next-line no-empty
catch (e) {
// Network timeouts are expected and don't need logging
}

This one required a comment explaining why the rule is disabled. Without the comment, it looks lazy.

When It’s Lazy

Now let me show the patterns that ARE problematic:

Anti-Pattern 1: No Explanation

utils.ts
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function process(data: any) {
return data.value;
}

Why is any needed here? No comment. No tracking. No plan. This is lazy.

Anti-Pattern 2: File-Level Disables

legacy-file.ts
/* eslint-disable */
// Entire 500-line file with no lint checking

This is the worst. One disable for an entire file? I’ve seen files with this at the top and no one knows why. The original author left years ago.

Anti-Pattern 3: Copy-Paste Without Understanding

component.tsx
// Copied from Stack Overflow
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
fetchData();
}, []);

I copied this. I don’t know why the dependency array is empty. I just wanted the warning to go away.

Anti-Pattern 4: Disabling Instead of Fixing

handler.ts
// Too lazy to refactor
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function oldFunction(notUsed: string, alsoNotUsed: number) {
// ...
}

The function has unused parameters. Instead of fixing the signature, I suppressed the warning.

What ESLint Recommends

ESLint’s official guidance says disables should be:

  1. Restricted - Use noInlineConfig: false in production
  2. Documented - Always add a comment explaining why
  3. Temporary - Link to an issue or TODO for removal
  4. Reviewed - Require approval in code review

Here’s how I configure this:

.eslintrc.json
{
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unused-vars": ["error", {
"argsIgnorePattern": "^_"
}]
},
"overrides": [
{
"files": ["**/*.ts"],
"rules": {
"no-restricted-syntax": [
"error",
{
"selector": "CallExpression[callee.name='disableEsLint']",
"message": "Use eslint-disable comments with caution and always add an explanation"
}
]
}
}
]
}

Metrics That Matter

I started tracking eslint-disable usage across projects:

Count per 10K LOCAssessmentAction
< 50NormalMonitor
50-100Review neededAudit for documentation
> 100ConcernRequire justification per disable

But count alone isn’t enough. I also track:

eslint-disable-audit.sh
#!/bin/bash
# Count total disables
TOTAL=$(grep -r "eslint-disable" src/ | wc -l)
# Count those with explanations (preceding comment line)
DOCUMENTED=$(grep -B1 "eslint-disable" src/ | grep "// " | wc -l)
# Calculate percentage undocumented
UNDOCUMENTED=$((TOTAL - DOCUMENTED))
PERCENTAGE=$((UNDOCUMENTED * 100 / TOTAL))
echo "Total: $TOTAL"
echo "Documented: $DOCUMENTED"
echo "Undocumented: $UNDOCUMENTED ($PERCENTAGE%)"

Running this on a recent project:

Terminal window
$ ./eslint-disable-audit.sh
Total: 87
Documented: 52
Undocumented: 35 (40%)

40% undocumented is too high. I set a threshold: if > 30% are undocumented, the PR gets blocked.

Common Rules That Get Disabled

I analyzed which rules I disable most:

RuleValid Reason?Example Justification
@typescript-eslint/no-explicit-anyOften yesThird-party SDK lacks types
@typescript-eslint/no-unused-varsRarelyUsually means dead code or wrong signature
no-consoleSometimesCLI tools need console output
@typescript-eslint/naming-conventionSometimesAPI requirements from external system
react-hooks/exhaustive-depsSometimesIntentional empty deps for mount-only effect

For no-explicit-any, I created a pattern:

types/branded.ts
// Instead of disabling, create a branded type
type VendorResponse = unknown & { __brand: "VendorResponse" };
// Force explicit handling
function parseVendorResponse(data: unknown): VendorResponse {
if (typeof data !== "object" || data === null) {
throw new Error("Invalid response");
}
return data as VendorResponse;
}

This removes the need for eslint-disable entirely.

The Reddit Debate

The Reddit thread on Claude Code’s 460 disables revealed two camps:

Camp 1: “This is fine”

“There’s only one thing better than pristine code: code in production. Ship it.” - Senior engineer with 15 years experience

“That’s what new starters don’t get. They see these and think it’s bad code. They don’t see the trade-offs we made.” - Tech lead at Series B startup

Camp 2: “This is tech debt”

“Every disable is a compromise. If you have 460 compromises, maybe your standards are too high or your team is too rushed.” - Staff engineer at Big Tech

“Blanket disables without explanation are problematic. It’s not the count, it’s the lack of documentation.” - Open source maintainer

I think both are right. The count doesn’t matter. The intention does.

What I Do Now

After this analysis, I updated my team’s workflow:

  1. Every disable needs a comment - Not just the rule name, but why
  2. Link to an issue - For non-trivial disables, create a tracking ticket
  3. Review in PRs - Disables get extra scrutiny in code review
  4. Monthly audits - Run the audit script and address undocumented disables

Here’s a template I use:

template.ts
// REASON: [Why this rule is disabled]
// TRACKING: [Issue/PR URL or "permanent" if intentional]
// ALTERNATIVES CONSIDERED: [What else we tried]
// eslint-disable-next-line @typescript-eslint/no-explicit-any

Example:

api-adapter.ts
// REASON: Vendor SDK v2.x returns untyped error objects
// TRACKING: https://github.com/vendor/sdk/issues/1234
// ALTERNATIVES CONSIDERED: Casting to unknown + runtime checks (too verbose)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function handleVendorError(error: any): ProcessedError {
return {
code: error.code ?? "UNKNOWN",
message: error.message ?? "An error occurred"
};
}

Summary

In this post, I analyzed when eslint-disable comments are pragmatic vs. lazy. The key point is not how many disables exist, but whether each one is intentional, documented, and tracked.

The 460 disables in Claude Code’s source don’t automatically mean bad code. What matters is:

  • Is there a reason documented?
  • Is there a plan to fix (if applicable)?
  • Is the scope limited (line-level vs file-level)?

If I can answer “yes” to those questions, the disable is acceptable. If not, it’s technical debt.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments