你 review Codex 的 PR:一个新组件 import 了 componentWillMount;另一个文件调用 require('request');第三处把一个 async callback 直接传进 useEffect,于是 effect 返回了一个 Promise,React 永远不会清理它。这些都不会被你的 build 拦下来——它们都”能跑”——但 componentWillMount 已经从 React 里移除,request 包 2020 年就被废弃,async effect 会泄漏。你现存代码一处都没用这些写法;是 Codex 自己注进去的。
最快的修法(先做这个): 在 AGENTS.md 里加一段明确的”只用现代写法”规则,再把 typescript-eslint 的 @typescript-eslint/no-deprecated 规则设成 error,这样 Codex 输出的废弃代码会 lint fail,agent 在下一轮会自己改正。下面其余步骤是把这套修法变成长期稳定的护栏。
根因是训练数据滞后。Codex 是在一份公开代码快照上训练的,那份快照早于其中某些写法过气的时间点,所以放任不管它就写 2019 风味的 React、2015 风味的 Node、Python 2 风味的 type hint。修法是把三件事组合起来:AGENTS.md 里的显式规则、真的会在废弃代码上 fail 的 lint 强制、以及把 agent 指向最新文档而不是过时的 tutorial。
你属于哪一类?
| diff 里的症状 | 最可能的原因 | 对应小节 |
|---|---|---|
| 这个写法在你 repo 里别处都没出现 | 直接从训练数据拉来 | 原因 1 |
| agent 猜错了你框架的主版本 | AGENTS.md 里没钉版本 | 原因 2 |
npm run lint 在明显老旧的代码上还是过 | lint 没配成能抓废弃 API | 原因 3 |
| 老写法出现在你自己的 README/docs 里 | agent 把它当”house style”抄了 | 原因 4 |
package.json 里新增了一个过时的 npm 包 | 流行但已废弃的依赖 | 原因 5 |
先说一个正确性提醒,免得你把修复用错了目标。不是每个”看起来旧”的写法都是 bug。自 TypeScript 5.1(2023 年 6 月)起,React.FC 不再隐式注入 children prop,所以截至 2026 年 6 月它只是风格偏好,不是废弃——只有你的 house style 说要禁才禁它。相比之下,componentWillMount / componentWillReceiveProps 是真的从现代 React 里移除了,request 和 node-fetch@2 是真的废弃了,useEffect 里的 async callback 是真的 bug。这些当真正的缺陷处理;React.FC 当口味处理。
常见原因
1. 训练数据早于这个 API 废弃
React.FC 在 2019 年很流行,2021 年左右失宠;componentWillMount 在 React 16.3(2018)被废弃,并已从现代 React 生命周期里移除。基于 2022 年以前 GitHub 训练的 model 还在产出它们,还有 componentWillReceiveProps、jQuery、var、Python 2 的 print 语句、Node 的 request 库。
如何判断: diff 里包含一个你 codebase 其他地方都不用的写法。在 repo 里 grep 这个新写法——别处零次出现,就说明 Codex 是从训练数据拉的,不是从你的约定来的。
2. AGENTS.md 没写框架版本约定
Codex 不知道你的 React、Node、Python 或框架要对准哪一代主版本。它会挑训练数据里最常见的写法,可能匹配你的版本也可能不匹配。
如何判断: grep -i 'version\|deprecated\|prefer\|avoid' AGENTS.md 关于框架约定什么都没返回。
3. lint 跑了但不强制废弃检查
你有 ESLint,但抓废弃 API 的那条规则没开。截至 typescript-eslint v8,对应的规则是内置的 @typescript-eslint/no-deprecated(它读 JSDoc 的 @deprecated 标记,需要类型信息)。那个老的独立包 eslint-plugin-deprecation 已经归档、无人维护——新项目别再装它。
如何判断: npm run lint 在 agent 的 PR 上通过了,尽管代码明显废弃,而且你的配置里没有 no-deprecated 这一项。
4. agent 看了你 repo 里过时的 tutorial
你的 README.md 或 docs/setup.md 还在展示 class extends React.Component 加 componentWillMount。Codex 把它当成”house style”抄了。
如何判断: grep -rn 'componentWillMount\|React.FC\|var ' README.md docs/ 有 match。
5. Codex 装了流行但过时的 npm 包
它拉进了 request、moment、node-fetch@2 或 uuid@3——当年都很火,现在都废弃或被取代了。截至 2026 年 6 月,Node 18+ 自带全局的 fetch(基于 undici),所以 request 和 node-fetch 基本用不上了;moment 已进入维护模式,并引导用户改用替代方案。
如何判断: npm install 的 log 里有 “deprecated” 警告,而且 package.json 里新增了一个已知过时的依赖。
最短修复路径
Step 1:AGENTS.md “只用现代写法”规则
AGENTS.md 是 Codex 会自动读取的文件。会话开始时它先加载你全局的 ~/.codex/AGENTS.md,再从 Git repo 根目录往下走到你的工作目录,把沿途每个 AGENTS.md 按这个顺序拼接(越靠近 cwd 优先级越高)进上下文,放在你的 prompt 之前。截至 2026 年 6 月,拼接后的指令链默认上限是 32 KiB,并且同一层级的 AGENTS.override.md 优先级高于 AGENTS.md(见 OpenAI 的 AGENTS.md 指南)。把硬规则写在这里:
## API 和框架规则
目标技术栈:React 19、TypeScript 5.x、Node 22 LTS、Python 3.12。
新代码或改动代码里,禁用:
- React class component——只用 function component + hook
- `componentWillMount`、`componentWillReceiveProps`、`componentWillUpdate`
- 直接传给 `useEffect` 的 `async` callback(effect 必须返回一个清理
函数或 undefined,绝不能返回 Promise)
- jQuery、Backbone,或任何 2020 年以前的代码写法
- `var`——用 `const` / `let`
- TS/ESM 文件里的 `require()`——用 `import`
- npm:`request`、`moment`、`node-fetch`、`uuid@3`——见"approved packages"
- Python:`print` 语句、`urllib2`、`from __future__`、type comment
House style:直接给 props 标类型,不要用 `React.FC`(这是偏好,
不是废弃)。
不确定某个写法是否还活着,先去那个包的 GitHub README 里搜
"deprecated" 再用。
现在 agent 有一份具体清单可以对照,而不是靠猜。
Step 2:打开废弃检查的 lint 规则
你几乎肯定已经装了 typescript-eslint。抓废弃 API 的就是内置的 @typescript-eslint/no-deprecated 规则——不用装额外的包。它在 strict-type-checked 预设里默认就是开的,但下面的示例还是显式打开它,这样无论你 extend 哪个预设它都会触发。它需要类型感知的 lint(它从 type-checker 上读 @deprecated JSDoc 标记),所以确保你的配置启用了类型信息。
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 已在 typescript-eslint v8 移除;用 no-restricted-types
'@typescript-eslint/no-restricted-types': ['error', {
types: {
'React.FC': {
message: 'House style:直接给 props 标类型,不要用 React.FC。',
},
},
}],
'no-restricted-imports': ['error', {
paths: [
{ name: 'request', message: '用内置全局 fetch。' },
{ name: 'moment', message: '用 date-fns 或 Temporal API。' },
{ name: 'node-fetch', message: 'Node 18+ 用全局 fetch。' },
],
}],
},
},
);
相比老教程有两处变化:@typescript-eslint/ban-types 已在 typescript-eslint v8 移除(由 no-restricted-types 取代),废弃检查现在落在 @typescript-eslint/no-deprecated 里,而不是那个已归档的 deprecation/deprecation 插件。配好之后,Codex 的废弃写法会 lint fail,agent 读到 lint 输出就会自己改正。
Step 3:把 agent 指向最新文档
在 AGENTS.md 里加一个 docs 小节,让 agent 引用现行 source,而不是 2023 年以前的博客:
## 参考文档(用这些,忽略更老的 tutorial)
- React:https://react.dev/(不是 legacy.reactjs.org)
- Next.js:https://nextjs.org/docs(App Router、server component)
- Node:https://nodejs.org/docs/latest-v22.x/api/
- TypeScript:https://www.typescriptlang.org/docs/handbook/
- Python:https://docs.python.org/3.12/
引用某个写法时,给上面这些里某一个的链接。不许引用 2023 年
以前的博客。
Step 4:审计 codebase 里已存在的废弃写法
repo 里的老代码等于给 Codex “可以这样写”的许可,所以扫一遍、把现存用法迁掉:
# React
grep -rn 'componentWillMount\|componentWillReceiveProps\|componentWillUpdate' src/
# Node
grep -rn "require('request')\|from 'moment'\|from 'node-fetch'" src/ package.json
# TypeScript 逃生口
grep -rn ': any\b\|@ts-ignore\|@ts-nocheck' src/
一旦 agent 在 repo 里看不到某个写法,它就不会再把它引进来。
Step 5:加一个跑废弃扫描的 CI 步骤
# .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
把它设成 required status check,Codex 就没法把废弃代码 merge 进来,哪怕某次 lint 配置漂移了也拦得住。
Step 6:保持依赖新鲜
老依赖会拖着老的类型定义,一个过期的 @types/react 即使上面这些都配了,也会把 agent 引向老写法。每季度刷一次:
npm outdated
npx npm-check-updates -u
npm install
npm test
最新版 @types/react 带着最新的 @deprecated JSDoc 标记,而这正是 @typescript-eslint/no-deprecated 用来标记坏代码的依据。
如何确认已修好
- 往一个临时文件里丢一行已知废弃的代码——比如一个带
componentWillMount的 class component——然后跑npm run lint。你应该看到一个@typescript-eslint/no-deprecated(或react/no-deprecated)报错。如果 lint 过了,说明类型感知的 lint 没接上,检查parserOptions.projectService。 - 让 Codex 重做最初那个任务。新的 diff 应该用 function component、原生
fetch,effect 返回的是清理函数(或什么都不返回),而不是 Promise。 - 开一个临时 PR,里面放一个废弃写法,确认 CI 的废弃检查 fail。这证明无论 agent 下次生成什么,这道护栏都拦得住。
预防
AGENTS.md明确列出废弃写法和对应的现代替代,并钉住框架的主版本。@typescript-eslint/no-deprecated和react/no-deprecated配成error,并启用类型感知的 lint。AGENTS.md里的参考文档小节把 agent 指向当前正典 source。- 审计现存 codebase,让 agent 没法”对齐”老写法。
- CI 把 lint 和基于 grep 的废弃扫描都设成 required check。
- 保持依赖新鲜——每季度
npm outdated一次,尤其是@types/*。
常见问题
我在 chat 里告诉它别这么写了,为什么 Codex 还在写废弃代码?
chat 里的指令只活在当前这一轮。Codex 在每次会话开始时会重读 AGENTS.md,但不会重读你之前的 chat 消息,所以写在 AGENTS.md 里的规则会一直生效,而一次性的 chat 纠正不会。
React.FC 真的废弃了吗?
没有。自 TypeScript 5.1 起它不再隐式加 children prop,所以截至 2026 年 6 月它是风格选择,不是废弃。很多团队为了一致性仍然避开它,但别把它当正确性 bug——那个标签留给 componentWillMount、async useEffect 和那些过时的包。
我还需要 eslint-plugin-deprecation 吗?
不需要。那个独立插件已经归档,它的 README 现在直接引导用户改用内置的 @typescript-eslint/no-deprecated 规则,后者用类型信息做更广、更准的检测。如果你配置里还有旧插件,删掉它——两个一起跑是冗余。
@typescript-eslint/ban-types 报错说是未知规则——发生了什么?
ban-types 在 typescript-eslint v8 被移除了。要自定义禁用类型清单,用 @typescript-eslint/no-restricted-types(如 Step 2);内置的 {} / Function / Object 检查已拆到 no-empty-object-type、no-unsafe-function-type 和 no-wrapper-object-types。
agent 又把 node-fetch 加回来了——应该用什么替代?
在 Node 18 或更新版本上,全局 fetch(基于 undici)是内置的,不需要任何依赖。要做连接池或更细的控制,直接指向 undici;要 retry 和分页 helper,用 got。把这个选择写进 AGENTS.md 的”approved packages”清单,它就不会再瞎猜。
相关
标签: #Codex #agent #排查 #deprecated