You banned componentWillMount two years ago. ESLint flags it. The codebase has zero usages. Cursor generates a new component and quietly uses componentWillMount. You point out the lint error, the AI says “good catch, fixing now” and rewrites that one file. Three prompts later, in an unrelated component, componentWillMount is back. The AI sees the lint rule, agrees, then forgets across turns. Same story with var, require() in an ESM project, useEffect cleanup patterns from React 16, pg.Pool.connect callbacks instead of promises. The pattern is universal: the AI’s training data still contains the deprecated form, lint feedback is short-lived, and your guardrails are not reaching it where it matters.
Fastest fix (do this first): put the ban in a project rules file the agent reads on every turn, and make ESLint a hard error gate. For Cursor that is .cursor/rules/conventions.mdc with alwaysApply: true (the old single .cursorrules file is deprecated and, as of June 2026, is silently ignored in Agent mode). For Claude Code it is CLAUDE.md. Then enforce eslint . --max-warnings 0 in a pre-commit hook so a passing build is impossible while the deprecated form is present. The in-chat correction never persists; the file does.
Common causes
Ordered by how often each appears in real sessions.
1. The AI trained heavily on pre-deprecation code
Most public code on GitHub still uses the old form. The model saw a million componentWillMount examples and a thousand componentDidMount ones. Probability does the rest.
How to spot it: The deprecated pattern keeps coming back across unrelated files, not just where you corrected it once.
2. Lint feedback only reaches a single turn
Cursor or Claude Code reads the lint output, fixes the one flagged line, and moves on. The next prompt starts a new conversation slice — the rule is not in long-term memory.
How to spot it: Same lint rule fires repeatedly across sessions; the “fix” only sticks within the same conversation.
3. No project-level instruction telling the AI what is banned
Your project rules file (.cursor/rules/*.mdc, CLAUDE.md, or AGENTS.md) does not mention the deprecation, so the AI has no signal that this codebase is stricter than the wider web. A very common 2026 variant: you wrote the rule in a legacy root-level .cursorrules file, but Cursor’s Agent mode no longer loads it, so the rule is effectively invisible.
How to spot it: Check your rules file — if the deprecated pattern is not listed, the AI has no reason not to use it. If your only rules file is .cursorrules, that is the bug: migrate it to .cursor/rules/.
4. ESLint config disables the relevant rule or sets it to “warn”
The rule exists but emits a warning, not an error. The AI’s “lint passed” check passes; the warning gets lost in noise.
How to spot it: eslint --max-warnings 0 fails on the file even though eslint alone exits 0.
5. The deprecation is project-specific (custom rule) and the AI cannot see it
You banned console.log in production code via a custom rule. The AI does not know the rule exists until it runs lint, and even then it may not understand the reason.
How to spot it: Lint output mentions a rule name the AI never references in its explanations.
6. Library upgrade silently deprecated the old form mid-project
You upgraded pg from v7 to v8 and callbacks became deprecated. The AI’s training data is split — half v7, half v8 — and it picks whichever feels right.
How to spot it: npm ls <package> shows a recent major version; the deprecated form maps to a previous major.
Which bucket are you in
| Symptom | Most likely cause | Fastest fix |
|---|---|---|
| Deprecated form returns in new unrelated files | #1 training bias / #3 no project rule | Add the ban to .cursor/rules/*.mdc (alwaysApply: true) or CLAUDE.md |
| Rule is written down but the AI ignores it in Cursor Agent mode | #3, using legacy .cursorrules | Move the rule into .cursor/rules/conventions.mdc |
eslint exits 0 but the pattern is clearly there | #4 rule at warn or disabled | Set rule to "error"; run eslint . --max-warnings 0 |
| Fix sticks in the chat, breaks in a new session | #2 lint feedback is single-turn | Make lint a pre-commit gate, not a chat reminder |
| Lint names a rule the AI never mentions | #5 custom/project-specific rule | Document the rule + its reason in the rules file |
| Old form appeared right after a dependency bump | #6 library upgrade | npm ls <pkg>; run a codemod across src/ |
Before you start
- List every deprecated pattern you currently care about. Write them down explicitly — “we never use X, always use Y”.
- Run
eslint . --max-warnings 0to confirm your rules are actually enforced as errors, not warnings. - Check which rules file the agent actually reads:
.cursor/rules/*.mdcfor Cursor Agent mode,CLAUDE.mdfor Claude Code,AGENTS.mdfor the cross-tool standard. Note whether you are relying on a legacy root.cursorrules(no longer read in Cursor Agent mode as of June 2026). - Capture one example of the AI reintroducing the deprecated form, with the file and surrounding prompt.
Information to collect
- The deprecated pattern (e.g.
componentWillMount,var,require(), callback API). - The replacement (the right way).
- The lint rule name that should fire (
react/no-deprecated,no-var, etc.). - Whether the rule is enabled and at what severity in
eslint.config.js(the flat config; ESLint 10, shipped Feb 2026, removed the old.eslintrcformats entirely). - The contents of
.cursor/rules/*.mdc/CLAUDE.md/AGENTS.md. - Library version if the deprecation is library-tied.
Step-by-step fix
Ordered from quickest wins to durable policy.
Step 1: Make the lint rule a hard error, not a warning
Use the flat config eslint.config.js. ESLint 10 (Feb 2026) removed .eslintrc.* support, so if you are still on an .eslintrc file you are either pinned to ESLint 9 (end-of-life 2026-08-06) or your config is not being read at all:
// eslint.config.js
export default [
{
rules: {
"react/no-deprecated": "error",
"no-var": "error",
"import/no-commonjs": "error",
},
},
];
Then enforce zero warnings in CI and locally — note the explicit . target, which the flat config needs:
eslint . --max-warnings 0
The AI’s “I ran lint, it passed” check now actually fails on the deprecated form. (A warn-severity rule keeps the exit code at 0, so the agent still thinks it is done — that is why severity has to be error.)
Step 2: Add explicit bans to the rules file the agent actually reads
The format depends on the tool, and getting this wrong is the single most common reason the ban never reaches the model:
- Cursor Agent mode: create
.cursor/rules/conventions.mdc. A root.cursorrulesfile still exists for backward compatibility but is ignored in Agent mode as of June 2026, so do not rely on it. The.mdcfile needs frontmatter; setalwaysApply: trueso the rule loads on every turn rather than only when Cursor judges it relevant. - Claude Code: use
CLAUDE.mdat repo root (Claude Code also readsAGENTS.md). - Cross-tool (Codex, Copilot, Gemini CLI, etc.): use
AGENTS.md, the shared standard that 28+ agents read.
A Cursor rule file looks like this — the fenced frontmatter at the top is required:
---
description: Banned legacy patterns this codebase rejects
globs: src/**/*.{ts,tsx,js,jsx}
alwaysApply: true
---
## Banned patterns (do NOT generate)
- componentWillMount, componentWillReceiveProps, componentWillUpdate
-> use componentDidMount + useEffect equivalents
- var -> use const, let
- require(), module.exports -> use ESM import / export
- pg client callback API -> use the promise / async-await form
- console.log in src/ outside of src/lib/logger.ts
If you generate any of the above, the lint check WILL fail.
Verify your code does not contain these patterns before responding.
For CLAUDE.md / AGENTS.md use the same body as plain Markdown (no frontmatter needed). This puts the prohibition into the AI’s working context on every turn.
Step 3: Show the AI the right pattern by example
Add a docs/conventions.md (or similar) with side-by-side examples and reference it from the rules file:
# Banned vs preferred
## React lifecycle
WRONG:
componentWillMount() { this.fetch(); }
RIGHT:
useEffect(() => { fetch(); }, []);
## Module system
WRONG:
const x = require("foo");
RIGHT:
import x from "foo";
Concrete pairs beat abstract bans. The AI patterns its output on the “RIGHT” examples.
Step 4: Run lint in a pre-commit hook
In package.json:
{
"scripts": {
"lint": "eslint . --max-warnings 0"
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": "eslint --max-warnings 0"
}
}
And a pre-commit hook (via husky / lefthook). The AI cannot bypass commit-time enforcement.
Step 5: Add a --fix autofix where possible
Many deprecation rules have autofixes (e.g. no-var, prefer-const, prefer-arrow-callback):
eslint . --fix
Run this after every AI-generated batch. It removes the cognitive load of catching trivial regressions manually.
Step 6: Use codemods for project-wide cleanup
For larger migrations (callback → promise, class → hooks, CommonJS → ESM), use jscodeshift or ts-morph codemods:
npx jscodeshift -t ./codemods/cb-to-promise.js src/
Once codemods run, the codebase has zero examples of the deprecated form — which means the AI, doing RAG over your codebase, sees only the new pattern and starts copying that style.
Verify
eslint . --max-warnings 0exits 0 with no warnings anywhere.- Generate three new files via AI on different prompts — none reintroduce the deprecated pattern.
grep -r "<deprecated-pattern>" src/returns no hits.- Pre-commit hook blocks a deliberately deprecated change with a clear error message.
Long-term prevention
- Add a “deprecations” section to your
.cursor/rules/*.mdc/CLAUDE.md/AGENTS.mdand revisit it whenever you upgrade a major dependency. - Keep ESLint, TSC, and any custom AST linters all set to error-level for deprecated APIs — warnings are training noise that the AI ignores.
- For each banned pattern, include the replacement in the rules file, not just the prohibition. AI is much better at “do this” than “do not do that”.
- Periodically run
grep -r "<deprecated>" src/and clean up any that slipped through; AI tools mirror what already exists in the repo. - When upgrading a major library version, dedicate one PR to codemod-driven cleanup of the deprecated API across the whole codebase. Mixed states confuse both humans and AI.
- Document the why of each deprecation briefly. AI grounds better on “componentWillMount is unsafe in concurrent mode” than on a flat ban.
Common pitfalls
- Leaving the rule at “warn” severity. The AI sees a passing lint check and considers itself done.
- Writing the ban in a root
.cursorrulesfile and assuming Cursor reads it. Agent mode ignores it; move it to.cursor/rules/*.mdc. - Keeping an
.eslintrcafter upgrading to ESLint 10 — it is no longer read, so none of your rules fire. Migrate toeslint.config.js. - Adding the ban to your personal notes but never to the rules file the AI reads.
- Fixing the deprecated pattern in one file and not running grep for the rest — usually there are 5-10 more lurking.
- Assuming the AI “learns” from the correction within a session. It does not persist across sessions, only across turns of the same conversation.
- Mixing deprecated and modern patterns in the same file. The AI picks whichever style appears nearby and propagates it.
For related issues see AI suggests a stale dependency, AI-introduced TypeScript errors, and Claude Code missing project context. For the authoritative formats, see Cursor’s Rules docs and the ESLint configuration files docs.
FAQ
Q: I keep correcting it and it keeps doing the same thing. Am I doing something wrong?
The correction only persists within one conversation. Move the rule into .cursor/rules/*.mdc (Cursor) or CLAUDE.md (Claude Code) so every new session inherits it. In-chat reminders are short-term memory only.
Q: I already have a .cursorrules file with the ban. Why does Cursor still ignore it?
As of June 2026 the legacy root .cursorrules file is deprecated and not loaded in Cursor’s Agent mode — the moment you switch to the agent, the whole file stops applying. Migrate it to .cursor/rules/conventions.mdc with frontmatter alwaysApply: true. You can keep .cursorrules for older non-agent flows, but do not depend on it.
Q: The lint rule is enabled but the AI still generates the deprecated form. Why?
The AI writes the code, then runs lint, then “fixes” only that file in that turn. If you accept the file as-is before lint runs, or run lint manually later, the deprecated form ships. Make lint a hard pre-commit gate.
Q: Should I just delete the deprecated API from the library?
If you control the library, yes — that is the cleanest fix. If it is a third-party library, you cannot. Use the rules file and lint enforcement instead.
Q: How do I get the AI to suggest the modern pattern proactively, not just avoid the old one?
Pair every ban with a concrete RIGHT example in your rules / docs. AI mimics positive patterns far better than it respects abstract prohibitions.
Tags: #Troubleshooting #AI coding #deprecation #eslint #linting