LEARN · DEBUGGING GUIDE

ESLint Rule Conflict: How to Diagnose and Fix Competing Linter Directives

When ESLint rules contradict each other, you get confusing errors or silent failures. This guide shows you exactly how to find the culprit, fix the conflict, and prevent it from coming back.

IntermediateBuild tools8 min read

What this usually means

ESLint rule conflicts happen when two or more rules target the same AST node and produce mutually exclusive outcomes. This is common when mixing core rules, plugin rules, and shareable configs (like airbnb or standard) without careful ordering or disabling of duplicates. The conflict can be explicit (e.g., both 'no-unused-vars' and '@typescript-eslint/no-unused-vars' enabled) or implicit (e.g., 'indent' vs 'react/jsx-indent' with different tab settings). ESLint resolves conflicts by last-config-wins, but if rules are in different config blocks or layers, the resolution can surprise you. Often the underlying issue is that a plugin rule overrides a core rule without the core rule being disabled, or two shareable configs apply different values for the same rule.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Run `npx eslint --print-config path/to/problematic/file.js | grep -A5 'ruleName'` to see the resolved config for a specific rule
  • 2Check the order of configs in your .eslintrc: the 'extends' array applies right-to-left, so the last element overrides earlier ones
  • 3Run `npx eslint --env-info` to confirm ESLint and plugin versions – version mismatches cause rule deprecation or rename conflicts
  • 4Add `'no-debugger': 'off'` temporarily to see if the error moves: pinpoints if a plugin rule is masking the core rule
  • 5Use `eslint --debug` on one file to see the resolved rule set and detect duplicates
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • search.eslintrc.js, .eslintrc.json, or eslintConfig in package.json – the source of rule definitions
  • searchnode_modules/eslint-config-*/index.js – shareable configs that may enable conflicting rules
  • searchnode_modules/eslint-plugin-*/rules/*.js – plugin rule implementations that overlap with core rules
  • search.eslintignore – if a file is accidentally being ignored, rules may not apply as expected
  • searchCI pipeline logs (GitHub Actions, Jenkins) – often show exact rule names that conflict
  • searchEditor ESLint output panel (VSCode Output > ESLint) – shows resolved config per file
  • searchThe file that triggers the error: sometimes a specific construct (JSX, decorators) triggers the conflict
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningEnabled both core `indent` rule and `@typescript-eslint/indent` without disabling the core one
  • warningExtending two shareable configs that set the same rule to different values (e.g., airbnb and standard)
  • warningUpgrading a plugin that renamed or deprecated a rule, leaving the old name still in config
  • warningUsing `overrides` with rules that collide with base rules – overrides apply after extends, not before
  • warningMixing `'error'` and `'warn'` severity for the same rule from different configs, causing ESLint to pick the last one
  • warningMonorepo setups where root config applies to all packages, but a package's local config adds a conflicting rule
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildExplicitly disable the core rule when using its plugin equivalent (e.g., `'no-unused-vars': 'off'` before `'@typescript-eslint/no-unused-vars': 'error'`)
  • buildReorder `extends` array: place more general configs first, specific overrides last
  • buildUse `rules` block with the exact rule name and value; avoid relying on `extends` order for fine-grained control
  • buildIn monorepos, use `overrides` per package with `files` pattern to isolate conflicting configs
  • buildRun `eslint-find-rules -u` to find unused, missing, or plugin rules that may be duplicating core rules
  • buildPin plugin versions in package.json to prevent accidental rule changes from minor updates
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedRun `npx eslint --rulesdir /dev/null file.js` to lint without any custom rules – baseline should pass
  • verifiedAdd `'no-console': 'off'` and `'no-console': 'error'` in separate config blocks and confirm the last one wins
  • verifiedCreate a minimal repro: one file, one .eslintrc with only the conflicting rules – if it fails, the conflict is real
  • verifiedCheck that `eslint --print-config` shows the expected rule value (error/warn/off) and no duplicate rule names
  • verifiedRun full lint suite after fix: `npx eslint .` should exit 0 with no warnings about rule conflicts
  • verifiedVerify in CI that the same commit passes both local and remote lint steps
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningDisabling the plugin rule instead of the core rule – you lose the plugin's extra checks
  • warningBlindly copying rules from Stack Overflow without checking if they conflict with your existing config
  • warningUsing `'extends': ['plugin:react/recommended', 'airbnb']` without realizing airbnb already includes react rules
  • warningAssuming `overrides` append to rules – they replace the base rule set unless you use `rules: { ...baseRules, ...overrideRules }`
  • warningIgnoring ESLint's config cascade: a `.eslintrc` in a subdirectory overrides all parent configs, not merges
  • warningUpgrading ESLint or plugins in a feature branch and merging without testing across the whole codebase
( 07 )War story

The Monorepo Monster: When Airbnb and TypeScript Collide

Mid-level Frontend Engineer, monorepo teamReact 17, TypeScript 4.5, ESLint 7.32, eslint-config-airbnb 18.2, @typescript-eslint/eslint-plugin 4.33, Yarn Workspaces

Timeline

  1. 09:15Added new package 'design-system' to monorepo with its own .eslintrc
  2. 09:30Run `eslint .` from root – gets exit code 1 with 'Parsing error: The keyword 'export' is reserved' on a .tsx file
  3. 09:45Checked parser config – root uses @typescript-eslint/parser, package uses default espree
  4. 10:00Fixed parser, but now get 'indent: Expected indentation of 4 spaces but found 2' on JSX
  5. 10:15Discovered airbnb config sets indent: 2 spaces, but package's local config sets indent: 4
  6. 10:30Found @typescript-eslint/indent also enabled and set to 4, conflicting with core indent
  7. 10:45Disabled core indent, set @typescript-eslint/indent to 4, removed local indent rule – lint passes
  8. 11:00Pushed fix, CI green

I was migrating a new 'design-system' package into our monorepo. The root ESLint config used eslint-config-airbnb with TypeScript plugins. The new package had its own .eslintrc that extended a different config for React Native compatibility. When I ran the full lint, I got a parsing error on a simple .tsx file – 'export' is reserved? That was weird. I checked the parser setting and realized the package's local config didn't inherit the TypeScript parser from the root. Classic monorepo pitfall: subdirectories with their own .eslintrc override the root parser unless you explicitly set it.

After fixing the parser, the next error was an indent mismatch. The root airbnb config used 2 spaces, but the package's config specified 4 spaces. ESLint was applying the local rule over the extends, so it expected 4 spaces. But then errors popped up in files outside the package – the root config's indent rule was still 2 spaces. I thought 'overrides' would fix it, but I had misconfigured the files pattern. Plus, I discovered that @typescript-eslint/indent was also enabled and set to 4, conflicting with the core indent rule. ESLint was applying both rules to the same AST nodes, and they disagreed.

I disabled the core 'indent' rule (since the TypeScript version is more accurate), set '@typescript-eslint/indent' to 'error' with 4 spaces, and removed the duplicate indent rule from the local config. I also added a 'parser: @typescript-eslint/parser' to the package's .eslintrc. Then I ran eslint --print-config on a sample file to confirm the resolved config had only one indent rule. Full lint passed. The lesson: always explicitly disable the core rule when using its plugin equivalent, and never assume local configs inherit root settings – they don't merge, they replace.

Root cause

The core 'indent' rule and '@typescript-eslint/indent' were both enabled with different values (2 vs 4 spaces), causing contradictory lint errors. Additionally, the monorepo subpackage's config overrode the root parser setting, leading to parsing errors.

The fix

Disabled core 'indent', set '@typescript-eslint/indent' to 4 spaces, added explicit parser to subpackage config, and removed duplicate indent rule from local config.

The lesson

Always check for duplicate rules when using plugin equivalents; disable the core rule. In monorepos, explicitly configure parser and rules in each package instead of relying on root inheritance.

( 08 )How ESLint Resolves Rule Conflicts: The Cascade

ESLint merges configurations in a specific order: inline comments, then command line options, then config files from the nearest directory upwards, and finally the 'extends' array in the config file itself. The 'extends' array applies in reverse order – the last element takes precedence. This means if you extend 'airbnb' then 'plugin:react/recommended', the react plugin's rules override airbnb's for any rule defined in both.

When a rule is defined in both the core and a plugin (e.g., 'indent' and '@typescript-eslint/indent'), ESLint treats them as separate rules. Both will run and both can produce errors. If they disagree, the user sees both errors, which can be contradictory. The fix is to disable the core rule when using the plugin version. Use 'no-unused-vars': 'off' before '@typescript-eslint/no-unused-vars': 'error'.

Also note that 'overrides' blocks apply after the base config, but they don't merge – they replace. If you override 'rules' in an 'overrides' block, you lose all base rules unless you explicitly spread them. This is a common source of unexpected rule conflicts.

( 09 )Identifying Conflicting Rules with --print-config

The quickest way to see the resolved configuration for a specific file is `npx eslint --print-config path/to/file.js`. This outputs the entire merged config as JSON. Look for the 'rules' object – if you see both 'indent' and '@typescript-eslint/indent', you have a potential conflict. Also check for duplicate rule names with different values.

You can also use `npx eslint --debug file.js` to see which rules are being applied and their order. The debug output lists each rule with its resolved severity. If you see a rule appearing twice with different values, that's your conflict. For example, 'indent: error' followed by 'indent: off' means something is disabling it after it was enabled.

Another useful tool is `eslint-find-rules`. Run `npx eslint-find-rules -u` to find unused rules in your config, or `-m` to find missing rules from plugins. This can reveal if you accidentally have both core and plugin rules without one disabled.

( 10 )Monorepo-Specific Pitfalls

Monorepos are a hotbed for rule conflicts because each package can have its own .eslintrc. The root config applies to all files, but if a package has a local .eslintrc, it completely overrides the root for files in that directory. That means the local config must re-include everything it needs – parser, plugins, rules – unless you use the 'root: true' flag to prevent inheritance.

A common pattern is to set 'root: true' only in the root .eslintrc and have all packages rely on that single config. But if packages need different rules (e.g., different indentation), use 'overrides' in the root config with the 'files' pattern to target specific directories. This keeps the configuration centralized and avoids duplicate rule conflicts.

Another monorepo issue: plugin versions. If the root uses @typescript-eslint/eslint-plugin 4.x and a package uses 5.x, rule names or behaviors may differ. Use a single version via hoisting (yarn workspaces) or npm overrides. Always run `npx eslint --env-info` to confirm versions across the repo.

( 11 )Verifying the Fix Won't Regress

After applying a fix, the first step is to run `eslint --print-config` on the previously failing file and verify that the conflicting rule appears only once with the expected value. Then run `eslint .` across the entire project to ensure no new errors appear.

Write a regression test: create a small ESLint test script that lints a fixture file and asserts exit code 0. Integrate this into your CI pipeline. For example, use `@eslint/eslintrc` to programmatically load and validate the config.

Consider adding a lint step that checks for duplicate rules. You can write a custom ESLint rule that warns if both core and plugin rules exist for the same functionality, or use a tool like `eslint-plugin-eslint-plugin` to enforce config best practices.

Frequently asked questions

What does 'Parsing error: The keyword 'export' is reserved' mean in an ESLint rule conflict context?

This error usually means ESLint is using the wrong parser (e.g., espree instead of @typescript-eslint/parser) for a TypeScript file. This is often caused by a subdirectory .eslintrc that overrides the parser setting without inheriting the root's parser. Fix by setting 'parser: @typescript-eslint/parser' in the local config, or by removing the local config and using root overrides.

How do I know if a rule conflict is causing my lint to fail silently?

Run `npx eslint --debug file.js` and look for duplicate rule names in the output. Also check the resolved config with `--print-config` – if the same rule appears twice with different values, you have a conflict. Silent failures can also happen if one rule disables another via the 'off' value, but the second rule still runs and errors. Always check both rules are not enabled simultaneously.

Can I have both 'indent' and '@typescript-eslint/indent' enabled at the same time?

Technically yes, but they will both run on the same AST nodes and may produce conflicting errors. The recommended approach is to disable the core 'indent' rule and use only the TypeScript version, as it understands TypeScript syntax better. Similarly for 'no-unused-vars' and 'no-shadow'.

Why does my ESLint config work locally but fail in CI?

Version mismatches are the most common cause. Your local machine might have different ESLint or plugin versions than CI. Run `npx eslint --version` locally and in CI to compare. Also check if CI uses a different working directory or config file. Use a lockfile (package-lock.json or yarn.lock) to pin versions across environments.