Here's what I've learned after six months of AI-assisted coding: Claude writes syntactically correct JavaScript that looks good in a code review, passes my eyeball test, and then breaks in production because the AI used var instead of const, forgot a return statement, or left an unused variable that causes a memory leak. The code works in the happy path. It fails in the edge cases that deterministic rules would have caught immediately.
ESLint is the tool I'm bookmarking to fix that gap. It's a pluggable JavaScript and TypeScript linter that enforces style rules, catches common bugs, and flags anti-patterns — the exact class of mistakes that AI models trained on millions of lines of "works on my machine" code from GitHub love to repeat. I haven't set up a rigorous "lint everything AI writes" workflow yet, but the case for it gets stronger every time I merge AI-generated code that passes tests but violates basic quality rules.
What ESLint Actually Does
ESLint is a static analysis tool for JavaScript and TypeScript. You configure a set of rules (either pre-built rule sets or your own custom rules), and ESLint scans your code to find violations. It catches:
- Syntax errors and typos — undeclared variables, missing semicolons, unreachable code.
- Problematic patterns — using
==instead of===, unhandled promise rejections, missingawaitkeywords. - Style violations — inconsistent indentation, spacing, quote styles, naming conventions.
- Potential bugs — unused variables, shadowed variables, infinite loops, missing error handling.
- Security issues — use of
eval(), unsafe regex patterns, XSS vulnerabilities.
It's fully pluggable: you can use pre-built configs (Airbnb, Google, Standard) or build your own rule set. You can auto-fix many violations (eslint --fix), integrate it with your editor for real-time feedback, and run it in CI to block merges that violate the rules.
ESLint has been around since 2013 and is the de facto standard for JavaScript linting. If you've written JavaScript professionally in the past decade, you've probably encountered it. But the reason to use it has changed: it's no longer just about team style consistency. It's now a critical gate for catching the systematic errors that AI coding assistants introduce.
Why AI-Generated Code Needs Deterministic Rules
When I write code myself, I internalize the rules over time. I know to use const by default, to avoid var, to handle promise rejections, to validate inputs. I make mistakes, but they're random — one day I forget a null check, another day I shadow a variable. There's no pattern.
AI-generated code has systematic patterns. The model was trained on millions of repositories, many of which contain bad practices, outdated syntax, and edge-case bugs. When Claude or Copilot writes code, it's statistically probable based on what it's seen, not necessarily correct based on modern best practices. The result is code that:
- Uses
varinstead ofletorconstbecause older JavaScript code (pre-ES6) usedvareverywhere. - Forgets to
awaitasync functions or doesn't handle promise rejections because the training data includes plenty of fire-and-forget async calls. - Leaves unused imports or variables because the AI generated them "just in case" and then refactored without cleaning up.
- Uses
==instead of===because loose equality is common in older codebases, even though it's a known footgun. - Writes deeply nested conditionals or overly complex logic because the AI optimizes for "looks like code I've seen" not "easy to maintain."
These aren't random mistakes. They're predictable, recurring patterns. ESLint is designed to catch exactly these kinds of issues. It doesn't reason about logic or intent (that's what AI code reviewers like PR-Agent are for). It enforces mechanical correctness: does this code follow the rules we've agreed are non-negotiable?
The Rules I'm Planning to Enforce
When I set up ESLint for AI-assisted workflows, I'm planning to start with these rules (from the recommended configs, plus some extras):
Bug Prevention
no-unused-vars— Flag variables that are declared but never used (common in AI-generated code).no-undef— Catch references to undeclared variables (typos or missing imports).no-unreachable— Detect code that can never execute (AI sometimes generates dead code).require-await— Ensureasyncfunctions actually useawait.no-await-in-loop— Flag inefficient sequential async calls that should be parallelized withPromise.all().
Modern JavaScript Enforcement
no-var— Banvar; requireletorconst.prefer-const— Enforceconstfor variables that are never reassigned.prefer-arrow-callback— Use arrow functions instead offunctionkeyword for callbacks (cleaner, avoidsthisbugs).prefer-template— Use template literals instead of string concatenation.
Common Pitfalls
eqeqeq— Require===and!==instead of==and!=(avoid type coercion bugs).no-eval— Baneval()and similar unsafe patterns.no-implied-eval— Catch hiddenevalusage likesetTimeout("code", 100).no-console— Flagconsole.log()in production code (should use proper logging).
Promise and Async Handling
no-floating-promises(TypeScript ESLint) — Catch promises that aren't awaited or handled.no-misused-promises(TypeScript ESLint) — Prevent promises used where they shouldn't be (e.g., in conditionals).prefer-promise-reject-errors— Ensure promise rejections use Error objects, not strings or primitives.
I'd start with a popular config like eslint:recommended or @typescript-eslint/recommended and add these rules on top. The goal isn't to catch every possible issue — it's to catch the high-frequency, low-effort mistakes that AI tools make systematically.
How I Plan to Integrate It
The workflow I'm planning to test:
- Local linting: Set up ESLint in my editor (VS Code, Neovim) to show inline warnings as I review AI-generated code. This catches issues immediately, before I even commit.
- Pre-commit hook: Use a git hook (via
huskyorpre-commit) to runeslint --fixon staged files. Auto-fix the trivial stuff (formatting, spacing), block the commit if there are unfixable violations. - CI enforcement: Run ESLint in the CI pipeline and post results as inline PR comments using ReviewDog. This ensures no AI-generated code reaches production without passing the linter, even if I bypass the pre-commit hook in a hurry.
The key is making it automatic and non-negotiable. I don't want to remember to run ESLint manually after the AI writes code. I want it to run every time, catch issues in-context (inline in the PR), and block merge if critical rules are violated.
The Problem ESLint Doesn't Solve
ESLint is great at catching mechanical errors, but it doesn't reason about logic. It can't tell you:
- Whether your authentication logic is secure.
- If your algorithm is correct but inefficient.
- Whether the code does what the function name says it does.
- If there's a subtle race condition or off-by-one error.
Those require either human review or AI-powered logic analysis (tools like PR-Agent). ESLint and AI review are complementary layers:
- ESLint enforces syntactic and stylistic correctness (fast, deterministic, zero cost).
- AI review reasons about semantic correctness (slower, probabilistic, costs API credits).
- Human review handles product fit, architecture, and judgment calls (slowest, most expensive, most valuable).
If I skip ESLint and rely only on AI or human review, I'm wasting reviewer time on trivial issues that a linter would catch instantly. If I skip AI/human review and rely only on ESLint, I'll ship code that's syntactically perfect but logically broken. The right approach is to run all three, in sequence, and let each layer focus on what it does best.
Why I Haven't Set This Up Yet (But Will Soon)
I know ESLint. I've used it on projects before. But I haven't built a rigorous, AI-specific linting workflow yet — one where every piece of AI-generated code automatically goes through the linter before I even look at it, and violations block the merge in CI.
The reason is friction: setting up ESLint, choosing the right rules, configuring auto-fix, wiring it into pre-commit hooks and CI, integrating it with ReviewDog for inline comments — that's a few hours of setup work. It's not hard, but it's not trivial. I've been relying on manual review and occasional local linting instead.
But the cost of not having it is adding up. Last week I merged a Copilot-written function that used var in a loop and caused a subtle closure bug that only triggered in production under concurrent load. ESLint would have flagged var instantly. The bug took two hours to diagnose and fix. That's more than enough time to justify the setup investment.
So it's bookmarked, and it's moving to the top of my "set up this week" list. The plan is to start with the recommended config, add the promise/async rules (since AI often gets those wrong), integrate it with ReviewDog for CI feedback, and run it on every PR. If it catches even one production bug per month, it's paid for itself many times over.
The Config I'm Starting With
Here's the .eslintrc.json I'm planning to use as a starting point:
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"no-var": "error",
"prefer-const": "error",
"eqeqeq": ["error", "always"],
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"no-console": ["warn", { "allow": ["warn", "error"] }],
"require-await": "error",
"no-floating-promises": "error",
"prefer-promise-reject-errors": "error"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module"
}
}
This catches the most common AI-generated mistakes (var, loose equality, unhandled promises, unused variables) without being overly strict on style. I can tighten or loosen rules based on what actually shows up in practice. The key is to start simple, run it on a real project, and tune based on signal vs. noise.
Pairing ESLint With the Rest of the Toolchain
ESLint fits into the broader AI-assisted development workflow like this:
- Code generation: Aider, Copilot, or Claude writes the code.
- Immediate linting: ESLint (in the editor) flags syntax/style issues inline as I review the AI's output.
- Pre-commit check: ESLint auto-fixes trivial issues, blocks commit if critical rules are violated.
- CI enforcement: ReviewDog runs ESLint in CI and posts inline PR comments for any issues that slipped through.
- AI logic review: PR-Agent reviews the logic, architecture, and semantics (not syntax).
- Human review: I or a teammate review product fit, edge cases, and judgment calls.
ESLint is the first automated gate. It's fast (runs in milliseconds), deterministic (same input = same output), and free (no API costs). It catches the low-hanging fruit so that the expensive layers (AI review, human review) can focus on harder problems.
If I had to pick one tool to add to my AI coding workflow right now, it would be ESLint + ReviewDog. Not because it's the most sophisticated, but because it's the highest ROI: minimal setup, zero ongoing cost, catches real bugs that AI tools make systematically. Everything else is icing.
The Real Reason to Lint AI Code
The deeper issue isn't that AI writes bad code. It's that AI writes plausible code that looks correct on a quick skim but violates rules I've internalized over years of debugging production issues. When I write code, I subconsciously avoid var, loose equality, and unhandled promises because I've been burned by them. The AI doesn't have that experience. It only knows "this pattern appears frequently in my training data."
ESLint encodes that hard-won experience as enforceable rules. It's the collective wisdom of the JavaScript community, distilled into 300+ rules that say "don't do this, we've learned it causes bugs." When I let an AI write code, I'm trusting it to generate statistically likely patterns. ESLint is the safety net that says "statistically likely isn't good enough — we have standards."
That's why I'm prioritizing this. It's not about distrusting AI. It's about adding a deterministic checkpoint that catches the predictable mistakes so I can spend my review time on the unpredictable ones. AI writes the first draft. ESLint checks the rules. I check the logic. Together, we ship better code faster.