Comparisons

AI Rules vs ESLint Config: Complementary Tools

ESLint configs and AI rule files both enforce coding standards but at different stages. Guide to aligning CLAUDE.md with eslint.config.js, preventing contradictions, leveraging each tool's strengths, and the complementary enforcement model.

6 min read·May 27, 2025

CLAUDE.md reduces ESLint violations. ESLint catches what AI misses. Together: 90% clean on first run.

Alignment guide, contradiction prevention, optimal division, and the dual-enforcement model for maximum adherence

Two Tools, One Goal: Consistent Code

CLAUDE.md and eslint.config.js both exist to enforce coding standards. They differ in: when they act (CLAUDE.md at generation time, ESLint at validation time), how they enforce (CLAUDE.md as AI guidance, ESLint as binary pass/fail), and what they can express (CLAUDE.md handles intent and architecture, ESLint handles syntax and patterns). The goal is the same: every piece of code follows the project conventions. The mechanism is different: AI guidance prevents violations, ESLint catches what slips through.

The relationship is: complementary, not competitive. CLAUDE.md does not replace eslint.config.js (ESLint provides absolute enforcement that AI guidance cannot guarantee). eslint.config.js does not replace CLAUDE.md (ESLint cannot guide library selection, architecture decisions, or pattern preferences). Both are needed: CLAUDE.md for generation guidance, ESLint for validation enforcement. The two files work together: CLAUDE.md reduces ESLint violations (the AI generates cleaner code), ESLint catches the rest (the AI is not perfect).

This article provides: a practical alignment guide (how to write CLAUDE.md rules that align with your ESLint config), contradiction prevention (what happens when they disagree and how to avoid it), and the optimal division of responsibility (which standards belong in which file). The goal: two tools working in harmony, not two tools creating confusion.

Aligning CLAUDE.md with eslint.config.js

For every ESLint rule the team cares about: consider whether a corresponding CLAUDE.md rule would help. @typescript-eslint/no-explicit-any (ESLint blocks any type) → CLAUDE.md: "Never use any type. Use unknown and narrow with type guards." The ESLint rule: catches violations. The CLAUDE.md rule: prevents the AI from generating any in the first place. The AI that reads "never use any" produces: fewer ESLint violations. The developer fixes: fewer lint errors. Both rules enforce the same standard at different stages.

Not every ESLint rule needs a CLAUDE.md counterpart. ESLint rules that are: purely syntactic (no-trailing-spaces, no-multiple-empty-lines, no-irregular-whitespace) do not need AI rules (the formatter handles them). ESLint rules that are: auto-fixable (prefer-const, no-var, arrow-body-style) do not need AI rules (ESLint auto-fixes on save). Focus AI rules on: non-auto-fixable ESLint rules that affect code logic (no-floating-promises, no-misused-promises, prefer-await-to-then, no-explicit-any). These are: the rules where generation-time guidance makes the biggest difference.

The alignment checklist: (1) List your enabled ESLint rules. (2) Filter: remove auto-fixable and formatting-only rules. (3) For each remaining rule: write a CLAUDE.md counterpart in natural language. (4) Verify: no contradictions between CLAUDE.md and eslint.config.js. (5) Test: generate code, check ESLint violations. If violations decreased: the alignment is working. The checklist is: a one-time setup (30 minutes) with occasional updates when ESLint config changes.

  • ESLint no-explicit-any → CLAUDE.md 'Never use any, use unknown + type guards'
  • Skip auto-fixable rules: prefer-const, no-var — ESLint auto-fixes, AI rule is redundant
  • Skip formatting rules: handled by Prettier, not AI rules
  • Focus AI rules on: non-auto-fixable logic rules (floating promises, misused promises, any type)
  • Alignment checklist: list rules, filter, write counterparts, verify no conflicts, test
💡 Focus AI Rules on Non-Auto-Fixable Logic Rules

ESLint auto-fixes prefer-const and no-var: adding these to CLAUDE.md wastes tokens. Focus AI rules on: no-explicit-any (AI generates unknown + type guards), no-floating-promises (AI generates awaited calls). These non-auto-fixable rules benefit most from generation-time prevention.

Preventing Contradictions Between the Two

A contradiction: CLAUDE.md says "use default exports for page components" but ESLint has @typescript-eslint/no-default-export enabled. The AI generates a default export (following CLAUDE.md), ESLint flags it as an error (following the config), and the developer is: confused about which standard is correct. The fix: update either the AI rule or the ESLint config to agree. The principle: CLAUDE.md and ESLint must never disagree on the same standard.

Common contradiction sources: import style (CLAUDE.md: "use barrel exports from index.ts" vs ESLint: no-restricted-imports banning barrel exports for performance), naming (CLAUDE.md: "use PascalCase for component files" vs ESLint: @typescript-eslint/naming-convention requiring camelCase for all files), and patterns (CLAUDE.md: "use for...of loops" vs ESLint: no-restricted-syntax banning for...of for performance). Each is: a legitimate convention choice where two tools disagree. The resolution: decide the standard, update both files to agree.

Prevention process: when updating eslint.config.js, check CLAUDE.md for affected rules. When updating CLAUDE.md, check eslint.config.js for potential conflicts. Add a comment in each file referencing the other: // See CLAUDE.md: 'use default exports for pages' in eslint.config.js next to the default-export exception. These cross-references: make contradictions visible during code review and prevent drift as the configs evolve independently.

  • Contradiction: CLAUDE.md says X, ESLint says not-X — developer confused which is correct
  • Common sources: import style, naming conventions, pattern preferences
  • Resolution: decide the standard, update BOTH files to agree
  • Prevention: cross-reference comments in each file. Update both when either changes
  • Principle: CLAUDE.md and ESLint must never disagree on the same standard
⚠️ CLAUDE.md Says X, ESLint Says Not-X

AI generates default export (CLAUDE.md: 'use default exports'). ESLint flags: no-default-export. Developer confused. The principle: CLAUDE.md and ESLint must NEVER disagree. Cross-reference comments in each file. Update both when either changes.

Which Standards Belong in Which File

ESLint-only standards (do not need AI rules): auto-fixable style rules (prefer-const, no-var, arrow-parens), import sorting (eslint-plugin-import with auto-fix), unused variable detection (@typescript-eslint/no-unused-vars), and complexity limits (max-params, max-depth, complexity). These are: enforceable and auto-fixable by ESLint. Adding AI rules for them: wastes tokens without benefit. The AI may generate an unused variable; ESLint auto-removes it.

CLAUDE.md-only standards (ESLint cannot express): architecture patterns ("use Server Components by default"), library selection ("use Zustand not Redux"), implementation approach ("cursor-based pagination, not offset"), and project-specific conventions ("API responses use { data, error } format"). These are: logic-level decisions that ESLint cannot analyze statically. Only AI rules can: guide the AI toward these conventions at generation time.

Both files (AI rule + ESLint rule): no-explicit-any (AI rule prevents, ESLint catches), no-floating-promises (AI rule generates awaited calls, ESLint catches misses), consistent-return (AI rule follows error handling convention, ESLint enforces), and no-restricted-imports (AI rule avoids deprecated packages, ESLint blocks them). These dual-enforcement standards: have the highest adherence rate (AI prevents 90%, ESLint catches the remaining 10%).

  • ESLint-only: auto-fixable, unused vars, complexity — ESLint handles completely, AI rule not needed
  • CLAUDE.md-only: architecture, library selection, patterns — ESLint cannot express, only AI rules work
  • Both: no-any, no-floating-promises, no-restricted-imports — AI prevents 90%, ESLint catches 10%
  • Dual enforcement: highest adherence rate — two layers for the most important standards
  • Token efficiency: do not duplicate ESLint auto-fix rules in CLAUDE.md — save tokens for logic rules
ℹ️ Dual Enforcement = Highest Adherence

no-explicit-any: AI rule prevents generation (90% effective). ESLint catches the remaining 10%. Combined: near-100% adherence. Single-layer (ESLint only): catches 100% but developer fixes every violation. Dual-layer: the AI prevents most violations, the developer fixes the few that slip through.

Practical Example: A TypeScript Project

eslint.config.js enforces: @typescript-eslint/no-explicit-any (error), @typescript-eslint/no-floating-promises (error), @typescript-eslint/prefer-nullish-coalescing (warn, auto-fix), @typescript-eslint/no-unused-vars (error, auto-fix for imports), import/no-cycle (error), and import/no-restricted-paths (error — prevents importing from internal module paths). These rules: provide absolute enforcement for type safety, promise handling, and module boundaries.

CLAUDE.md complements with: "Never use any type. Use unknown and narrow with type guards or Zod schemas." (aligns with no-explicit-any, adds HOW to fix it), "Always await async function calls or handle the returned promise explicitly." (aligns with no-floating-promises, adds the expected pattern), "Use Zustand for global state. TanStack Query for server state. No Redux." (ESLint cannot express this), "API responses: { data: T } for success, { error: { code, message } } for failure." (ESLint cannot express this), "File structure: feature folders with co-located component + test + styles." (ESLint cannot express this).

The combined effect: ESLint catches type and promise issues with absolute enforcement. CLAUDE.md guides the AI toward Zustand, TanStack Query, the API response format, and the file structure — none of which ESLint can express. The AI generates: code that follows both the ESLint standards (type safety, promise handling) and the CLAUDE.md standards (architecture, patterns, conventions). The code passes: ESLint validation on the first run 90% of the time (because CLAUDE.md aligned the AI with the ESLint rules).

Comparison Summary

Summary of AI rules vs ESLint config as complementary tools.

  • Same goal: consistent code. Different mechanism: AI guidance (generation) vs ESLint enforcement (validation)
  • Complementary: CLAUDE.md reduces ESLint violations. ESLint catches what AI misses. Both needed
  • Align: for each important ESLint rule, write a CLAUDE.md counterpart in natural language
  • Prevent contradictions: cross-reference comments, update both files when either changes
  • ESLint-only: auto-fixable, complexity, unused vars — do not duplicate in CLAUDE.md
  • CLAUDE.md-only: architecture, library selection, patterns — ESLint cannot express these
  • Both: no-any, no-floating-promises, restricted imports — dual enforcement = highest adherence
  • Result: AI generates code that passes ESLint on first run 90% of the time