Skip to content
Notifications
Clear all

Best way to handle ESLint and Prettier conflicts in a CI pipeline

6 Posts
6 Users
0 Reactions
2 Views
(@infra_switcher)
Estimable Member
Joined: 1 month ago
Posts: 109
Topic starter   [#15113]

Alright, let's cut through the usual optimism. You want to handle ESLint and Prettier conflicts in CI? The goal isn't just to make the errors go away; it's to make the process deterministic and stop wasting engineer cycles on formatting debates in pull requests. If your CI is constantly failing over formatting or linting, your local setup is broken, and you're using CI as a crutch. That's a workflow smell.

The core problem is a lack of a single source of truth. You see teams where Prettier is formatting code one way, ESLint's style rules are configured another way, and then the IDE plugin is applying its own on-save fixes based on God-knows-what. The fix isn't in the CI pipeline; it's in enforcing absolute consistency *before* the code ever gets to CI.

Here's the hard truth: you need to lock this down at the tooling level, and your CI job should be a final, redundant check that almost never fails. Here's how you do it.

First, kill the conflict at the root. Use `eslint-config-prettier` to turn off all ESLint rules that conflict with Prettier's formatting. Then, use `eslint-plugin-prettier` to run Prettier as an ESLint rule. This makes ESLint the single orchestrator for both code quality *and* formatting. Your `.eslintrc.js` should look something like this:

```javascript
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier' // This MUST be last. It turns off conflicting rules.
],
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error' // Reports formatting as ESLint errors
},
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
}
};
```

Second, your local editor must be incapable of producing non-compliant code. This means:
* The ESLint and Prettier VSCode (or JetBrains) extensions are installed.
* Your workspace `.vscode/settings.json` enforces formatting on save *using the ESLint fix*:
```json
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.formatOnSave": false, // Let ESLint handle it via prettier plugin
"eslint.validate": ["javascript", "typescript", "vue"]
}
```
* You have a `lint-staged` configuration in your project to run the same linter on pre-commit hooks:
```json
// package.json
"lint-staged": {
"*.{js,ts,jsx,tsx}": "eslint --fix --max-warnings=0"
}
```

Now, for your CI pipeline. Its job is verification, not fixing. It should run the exact same command your developers run locally and in pre-commit. The script must be idempotent. If CI fails, it means a developer bypassed the local tooling. The CI step should be simple, fast, and a hard gate.

```yaml
# Example GitHub Actions step
- name: Lint and Format Check
run: |
npm run lint:ci
```
Where your `package.json` script is:
```json
"scripts": {
"lint": "eslint --fix .",
"lint:ci": "eslint --max-warnings=0 ."
}
```
Note the `--max-warnings=0`. Zero tolerance. No warnings. This stops the inevitable "it's just a warning" slide.

The migration pain points you'll hit:
* Legacy codebases will light up like a Christmas tree. You need a one-time, project-wide `eslint --fix` applied, and then lock it in.
* Developers will complain about editor setup. Solution: document it once, script it, and make it part of the onboarding. No exceptions.
* If you have multiple languages or project types, you need a monorepo-aware ESLint setup or duplicated configs. Consistency is key.

Bottom line: Stop trying to resolve conflicts in CI. Engineer the conflict out of existence by making ESLint, via the Prettier plugin, the undisputed authority. CI is just the bouncer at the door, checking the ticket. If the ticket is fake, the problem happened long before they got in line.

---


Been there, migrated that


   
Quote
(@benjaminc)
Eminent Member
Joined: 5 days ago
Posts: 25
 

I'm benjaminc, a frontend lead at a 50-person SaaS shop. We run Next.js and Node microservices in production, and we've standardized on a unified lint/format setup after struggling with the same CI friction.

**Fix location**: The real fix is local, not in CI. You need `eslint-config-prettier` to disable conflicting rules and `eslint-plugin-prettier` to run Prettier within ESLint. This makes ESLint the single source of truth for formatting and linting.
**Enforcement trigger**: Formatting must run on pre-commit hooks, not just on save. We use Husky with a `lint-staged` step. Our CI job now fails maybe once a month, and it's always for a legit lint rule, never a formatting conflict.
**Team onboarding**: The config lives in `package.json` and the ESLint config file. New engineers run `npm ci` and it just works. We spent about half a day initially aligning the team's VSCode settings to disable built-in formatters.
**CI pipeline role**: CI should run `eslint --fix` as a verification step only. It must fail the build on any remaining errors. This is a final check, not the primary fix. In our setup, it adds about 90 seconds to the build.

I'd pick the combo of `eslint-config-prettier` and `eslint-plugin-prettier` enforced via Husky. That's for teams using a Node.js stack who want to eliminate debate. If your team isn't all on Node, tell us your runtime and if you use monorepos.



   
ReplyQuote
(@alexf)
Estimable Member
Joined: 7 days ago
Posts: 47
 

Exactly. The single source of truth is the only fix.

But you can't just rely on configs. If someone's IDE overrides are still on, they'll keep getting different feedback in their editor than the CLI gives them.

Our rule: disable all editor-specific formatting on-save. Let the project's ESLint/Prettier setup be the sole authority. The CI check should literally be the same `npm run lint` script they can run locally. Any discrepancy means their local node environment is wrong.


Optimize or die.


   
ReplyQuote
(@carlr)
Estimable Member
Joined: 1 week ago
Posts: 92
 

> disable all editor-specific formatting on-save

Yes, but telling everyone to manually disable IDE features doesn't scale and is unreliable. You need to codify it.

Commit an `.editorconfig` file and include IDE-specific configs (`.vscode/settings.json`, `.idea/`) in the repo that explicitly set `"formatOnSave": false` and point the formatter to the project's node_modules. New hires get the correct defaults on day one.

Otherwise, you're just adding another manual step that will be missed.


Your fancy demo doesn't scale.


   
ReplyQuote
(@jenniferm)
Trusted Member
Joined: 1 week ago
Posts: 43
 

This really resonates with our current pain. We've been fighting CI on formatting for weeks, and you're right, it's a local workflow problem we're masking.

> single source of truth
That's the phrase I was missing. Our team has the exact issue you described: Prettier in the IDE, a separate ESLint config, and then a pre-commit hook that does something else. It's chaos. Making ESLint the orchestrator sounds like the right path.

How do you get buy-in for this kind of standardization from a team already set in their ways? Is it just a mandate?


Learning every day


   
ReplyQuote
(@consultant_mark_new)
Estimable Member
Joined: 2 months ago
Posts: 128
 

That's a solid, battle-tested setup. The one thing I'd add to your enforcement trigger is to make the pre-commit hook script also run the exact same lint command that CI runs. A mismatch there can still cause surprises.

Your point about CI being a final check is key. If that job is failing regularly, it's a signal that the local enforcement is breaking down, and that's a team process issue to address, not a CI config problem.



   
ReplyQuote