You review the Codex PR. A new component imports componentWillMount. Another file calls require('request'). A third passes an async callback straight into useEffect, so the effect returns a Promise and React never cleans it up. None of these are flagged by your build — they all “run” — but componentWillMount has been removed from React, the request package was deprecated in 2020, and async effects leak. Your existing code uses none of these patterns; Codex injected them.
Fastest fix (do this first): add an explicit “modern only” block to your AGENTS.md, then turn the typescript-eslint rule @typescript-eslint/no-deprecated into an error so Codex’s deprecated output fails lint and the agent self-corrects on the next pass. Everything below makes that durable.
The root cause is training-data lag. Codex was trained on a snapshot of public code from before some of these patterns fell out of favor, so left alone it writes idiomatic-for-2019 React, idiomatic-for-2015 Node, and Python-2-flavored type hints. The fix is a combination of explicit AGENTS.md rules, lint enforcement that actually fails on deprecated code, and pointing the agent at current docs instead of stale tutorials.
Which bucket are you in?
| Symptom in the diff | Most likely cause | Section |
|---|---|---|
| Pattern appears nowhere else in your repo | Pulled straight from training data | Cause 1 |
| Agent guessed your framework major version wrong | No version pinned in AGENTS.md | Cause 2 |
npm run lint passes on obviously old code | Lint not configured to catch deprecation | Cause 3 |
| Old pattern is in your own README/docs | Agent copied it as “house style” | Cause 4 |
A stale npm package was added to package.json | Popular-but-deprecated dependency | Cause 5 |
A correctness note first, so you don’t waste a fix on the wrong target. Not every “old-looking” pattern is a bug. Since TypeScript 5.1 (June 2023) React.FC no longer implicitly injects a children prop, so as of June 2026 it is a style preference, not a deprecation — ban it only if your house style says so. By contrast componentWillMount / componentWillReceiveProps are genuinely removed from modern React, request and node-fetch@2 are genuinely deprecated, and an async callback in useEffect is a genuine bug. Treat those as real defects; treat React.FC as taste.
Common causes
1. Training data predates the deprecation
React.FC trended in 2019 and lost favor around 2021; componentWillMount was deprecated in React 16.3 (2018) and removed from the modern React lifecycle. Models trained on pre-2022 GitHub still reach for them, along with componentWillReceiveProps, jQuery, var, Python 2 print statements, and the Node request library.
How to spot it: the diff includes a pattern the rest of your codebase does not use. grep your repo for the new pattern — zero occurrences elsewhere means Codex pulled it from training data, not from your conventions.
2. No AGENTS.md guidance on framework versions
Codex does not know which React, Node, Python, or framework major version you target. It picks the most common pattern from training, which may or may not match your version.
How to spot it: grep -i 'version\|deprecated\|prefer\|avoid' AGENTS.md returns nothing about framework conventions.
3. Lint runs but does not enforce deprecation
You have ESLint, but the rule that catches deprecated APIs is off. As of typescript-eslint v8 the relevant rule is the built-in @typescript-eslint/no-deprecated (it reads JSDoc @deprecated tags and needs type information). The older standalone eslint-plugin-deprecation package is archived and unmaintained — do not install it on a new setup.
How to spot it: npm run lint succeeds on the agent’s PR despite obviously deprecated code, and your config has no no-deprecated entry.
4. Agent referenced an outdated tutorial in your own repo
Your README.md or docs/setup.md still shows class extends React.Component with componentWillMount. Codex read it as “house style” and copied it.
How to spot it: grep -rn 'componentWillMount\|React.FC\|var ' README.md docs/ returns matches.
5. Codex added a popular-but-stale npm package
It pulled in request, moment, node-fetch@2, or uuid@3 — all popular in their day, all now deprecated or superseded. As of June 2026 Node 18+ ships a native global fetch (built on undici), so request and node-fetch are rarely needed; moment is in maintenance mode and points users to alternatives.
How to spot it: the npm install log shows “deprecated” warnings, and package.json newly includes a known-stale dependency.
Shortest path to fix
Step 1: AGENTS.md “modern only” rule
AGENTS.md is the file Codex reads automatically. On session start it loads your global ~/.codex/AGENTS.md, then walks from the Git repo root down to your working directory and concatenates every AGENTS.md it finds along the way, in that order (closer to the cwd wins), into context before your prompt. As of June 2026 the merged instruction chain is capped at 32 KiB by default, and a per-directory AGENTS.override.md takes priority over AGENTS.md at the same level (see OpenAI’s AGENTS.md guide). Put the hard rules there:
## API and framework rules
Target stack: React 19, TypeScript 5.x, Node 22 LTS, Python 3.12.
Do not use, in new or modified code:
- React class components — use function components and hooks
- `componentWillMount`, `componentWillReceiveProps`, `componentWillUpdate`
- `async` callbacks passed directly to `useEffect` (the effect must return
a cleanup function or undefined, never a Promise)
- jQuery, Backbone, or any code patterns from before 2020
- `var` — use `const` / `let`
- `require()` in TypeScript/ESM files — use `import`
- npm: `request`, `moment`, `node-fetch`, `uuid@3` — see "approved packages"
- Python: `print` statement, `urllib2`, `from __future__`, type comments
House style: type props directly instead of `React.FC` (this is a
preference, not a deprecation).
When unsure whether a pattern is current, check the package's GitHub
README for "deprecated" before using it.
The agent now has a concrete list to check against instead of guessing.
Step 2: Turn on the deprecation lint rule
You almost certainly already have typescript-eslint installed. The deprecation catch is the built-in @typescript-eslint/no-deprecated rule — no extra package needed. It ships on by default in the strict-type-checked preset, but the example below turns it on explicitly so it fires regardless of which preset you extend. It requires type-aware linting (it reads @deprecated JSDoc tags off the type-checker), so make sure your config enables type information.
Flat config (eslint.config.js):
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
export default tseslint.config(
...tseslint.configs.recommendedTypeChecked,
{
plugins: { react: reactPlugin },
languageOptions: {
parserOptions: { projectService: true },
},
rules: {
'@typescript-eslint/no-deprecated': 'error',
'react/no-deprecated': 'error',
// ban-types was removed in typescript-eslint v8; use no-restricted-types
'@typescript-eslint/no-restricted-types': ['error', {
types: {
'React.FC': {
message: 'House style: type props directly, do not use React.FC.',
},
},
}],
'no-restricted-imports': ['error', {
paths: [
{ name: 'request', message: 'Use the built-in global fetch.' },
{ name: 'moment', message: 'Use date-fns or the Temporal API.' },
{ name: 'node-fetch', message: 'Use the global fetch in Node 18+.' },
],
}],
},
},
);
Two things changed versus older guides: @typescript-eslint/ban-types was removed in typescript-eslint v8 (replaced by no-restricted-types), and the deprecation check now lives in @typescript-eslint/no-deprecated rather than the archived deprecation/deprecation plugin. With this in place, Codex’s deprecated patterns fail lint, the agent reads the lint output, and it self-corrects.
Step 3: Point the agent at current docs
Add a docs section to AGENTS.md so the agent cites current sources instead of pre-2023 blog posts:
## Reference docs (use these, ignore older tutorials)
- React: https://react.dev/ (NOT legacy.reactjs.org)
- Next.js: https://nextjs.org/docs (App Router, server components)
- Node: https://nodejs.org/docs/latest-v22.x/api/
- TypeScript: https://www.typescriptlang.org/docs/handbook/
- Python: https://docs.python.org/3.12/
If you cite a pattern, link to one of these. Do not cite blog posts from
before 2023.
Step 4: Audit the codebase for existing deprecated patterns
Old code in the repo gives Codex permission to copy the pattern, so sweep and migrate the existing usages:
# React
grep -rn 'componentWillMount\|componentWillReceiveProps\|componentWillUpdate' src/
# Node
grep -rn "require('request')\|from 'moment'\|from 'node-fetch'" src/ package.json
# TypeScript escape hatches
grep -rn ': any\b\|@ts-ignore\|@ts-nocheck' src/
Once the agent no longer sees a pattern in the repo, it stops re-introducing it.
Step 5: Add a CI step running the deprecation scan
# .github/workflows/deprecation-check.yml
name: Deprecation check
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '22' }
- run: npm ci
- run: npm run lint
- name: Custom deprecation scan
run: |
if grep -rn -E 'componentWillMount|componentWillReceiveProps|node-fetch' src/; then
echo "Deprecated patterns found"
exit 1
fi
Mark this as a required status check and Codex cannot land deprecated code, even if a lint config drifts.
Step 6: Keep dependencies fresh
Old dependencies pull old type definitions, and a stale @types/react will steer the agent toward old patterns even with everything above in place. Refresh quarterly:
npm outdated
npx npm-check-updates -u
npm install
npm test
A current @types/react carries up-to-date @deprecated JSDoc tags, which is exactly what @typescript-eslint/no-deprecated reads to flag bad code.
How to confirm it’s fixed
- Drop a known-deprecated line into a scratch file — e.g. a class component with
componentWillMount— and runnpm run lint. You should see a@typescript-eslint/no-deprecated(orreact/no-deprecated) error. If lint passes, type-aware linting is not wired up; checkparserOptions.projectService. - Ask Codex to re-do the original task. The new diff should use function components, a native
fetch, and an effect that returns a cleanup function (or nothing) rather than a Promise. - Open a throwaway PR with a deprecated pattern and confirm the CI deprecation check fails. That proves the guardrail holds regardless of what the agent generates next time.
Prevention
AGENTS.mdlists the deprecated patterns and modern replacements explicitly, and pins the framework major versions.@typescript-eslint/no-deprecatedandreact/no-deprecatedare configured aserror, with type-aware linting enabled.- A reference-docs section in
AGENTS.mdpoints the agent to current canonical sources. - The existing codebase is audited so the agent does not “match” old patterns.
- CI runs both lint and a grep-based deprecation scan as required checks.
- Dependencies stay fresh —
npm outdatedquarterly, especially@types/*.
FAQ
Why does Codex keep writing deprecated code even after I told it not to in chat?
Chat instructions live only in the current turn. Codex re-reads AGENTS.md at the start of every session but not your earlier chat messages, so a rule written once in AGENTS.md persists where a one-off chat correction does not.
Is React.FC actually deprecated?
No. Since TypeScript 5.1 it no longer implicitly adds a children prop, so as of June 2026 it is a style choice, not a deprecation. Plenty of teams still avoid it for consistency, but do not treat it as a correctness bug — reserve that label for componentWillMount, async useEffect, and the stale packages.
Do I still need eslint-plugin-deprecation?
No. That standalone plugin is archived and its README now points users to the built-in @typescript-eslint/no-deprecated rule, which uses type information for broader, more accurate detection. Remove the old plugin if it is still in your config — running both is redundant.
@typescript-eslint/ban-types errors that it’s an unknown rule — what changed?
ban-types was removed in typescript-eslint v8. Use @typescript-eslint/no-restricted-types for a custom banned-types list (as in Step 2); the built-in {} / Function / Object checks moved to no-empty-object-type, no-unsafe-function-type, and no-wrapper-object-types.
The agent added node-fetch again — what should it use instead?
On Node 18 or newer, the global fetch (built on undici) is built in, so no dependency is needed. For pooling or advanced control, point it at undici directly; for retries and pagination helpers, got. Encode the choice in your AGENTS.md “approved packages” list so it stops guessing.