Codex 又造了一个和现有类型重名的 interface:怎么让它先搜再写

Codex 新建了一个 User 或 ApiResponse 类型,其实仓库里已经有一份一模一样的。用 AGENTS.md、types barrel、ripgrep 和 ts-morph CI 检查强制它先搜。

你 review Codex 的 PR,发现它在 src/auth/types.ts 加了个 User interface。三个问题一起来了:你 src/types/user.ts 里已经有 User、新这个字段稍有不同、现在一半代码 import 老的、一半 import 新的。TypeScript 不会报错,因为两个结构兼容到能互相赋值——所以 bug 不在编译期暴露,而是跑到运行时才炸。

Codex 写之前没搜。它读了正在改的那个文件,识别出需要的形状,就在原地写了一个新声明。没有一个强制函数——AGENTS.md 里显式的 “先搜” 规则、agent 知道去查的单一 import path、加上一个会因重名而失败的 CI 检查——每次 refactor 都会重演。

TL;DR——最快修复

  1. AGENTS.md 里加一条 类型复用 规则,要求 Codex 在声明前先用 rg(ripgrep)搜名字,找到就 import 现有的。Codex 本来就把 rg 当主力搜索工具,所以这是它会本能去跑的命令(截至 2026 年 6 月)。
  2. src/types/index.ts 做成 barrel,所有共享 type 都走 @/types,让 “这个是不是已经有了” 一次搜索就能答。
  3. 加一个 ts-morph CI 检查,对重名的 interface / type / enum / class 直接让 build 失败。Codex 看到红 CI 会自己修。

光做 Step 1 就能挡掉大部分情况。Step 2、3 补的是 UserResponse 这类最容易撞的泛名。

常见原因——你属于哪一类

PR / transcript 里的现象大概的原因修法
新增 interface Xrg "interface X" src/ 显示别处还有一份Agent 创建前没搜AGENTS.md “先搜” 规则(Step 1)
find src -name 'types.ts' 返回 10+ path,没有中心索引Type 散落,没有明确归处@/types types barrel(Step 2)
rg -i 'type|interface|reuse' AGENTS.md 没结果没有复用 type 的指引AGENTS.md 规则(Step 1)
transcript 只对当前文件 read_file,没搜过现有 type 在没加载的 barrel 里barrel + AGENTS.md 指向(Step 1 + 2)
diff 加了泛名 type(UserResponseConfig名字和 DOM/库类型撞了,agent 以为就是那个搜更广 + ts-morph CI(Step 1 + 3)

1. Agent 创建前没搜

Codex 默认流程是 “读我要改的文件,补缺的东西”。缺的是个 type,它就写一个 inline。它不一定会先在整个 repo 搜现有定义。

如何判断:PR 加了新的 interface Xtype X = ...,而 rg -n "interface X|type X " src/ 显示别处还有一份。

2. Types 散落各处,没有中心索引

一半 type 在 src/types/,一半和首个使用者放一起(src/auth/types.tssrc/api/types.ts)。没有明显的 “type 该放哪” 答案,Codex 就加到当前文件里。

如何判断find src -name 'types.ts' -o -path '*/types/*.ts' 返回十个以上 path,而且没有 src/types/index.ts 在 re-export。

3. AGENTS.md 没要求搜

Codex 干活之前会先加载 AGENTS.md(从 Git root 一路走到工作目录,最近的那份优先,默认合并上限 32 KiB)。如果这份文件里没有复用 type 的规则,Codex 就选局部最方便的路径。

如何判断rg -i 'type|interface|reuse' AGENTS.md 没结果。

4. 现有 type 在 barrel 里,agent 没加载

你有 src/types/index.ts 在 re-export 所有 type,但 agent 只 open 了它要改的那个文件。barrel 没见到,就当作没有。

如何判断:transcript 只对当前文件 read_file,没有跨 repo rg / 搜过这个 type 名。

5. Type 名和常见库重名

Codex 看到 Response 以为是 DOM 的 Response,于是新建了 ApiResponse,根本没查你是不是已经有了。泛名(UserItemResponseErrorConfig)最容易撞。

如何判断:diff 加的 type 是泛名,而已有那个也是同样的名字。

最短修复路径

Step 1:AGENTS.md 加 “先搜再建” 规则

Codex 在规划过程里一直在跑 rg(ripgrep),所以一条写成 rg 命令的规则它是真会去执行的:

## Type 和 interface 复用

新建 `type``interface``class``enum` 之前:

1. 先搜名字:
   `rg -n "(interface|class|enum) <Name>\b|type <Name>\b" src/`
2. 已有定义就 import,不要重复声明。
3. 已有那个缺你需要的字段,用 `interface X extends BaseX` 扩展,
   或就地更新——不要分叉。
4. 新的共享 type 放 `src/types/<domain>.ts`。除非 type 是单模块私有,
   不要 inline 在 feature 文件里。

名字泛的时候(`User``Item``Response`)搜广一点——
`packages/``apps/``shared/` 都搜。

这是 agent 真能跟着做的规则:一条搜索命令加一个决策树。AGENTS.md 控制在 32 KiB 上限内,别让内容被截断。

Step 2:把 type 收口到单一 import path

src/types/index.ts 做成 barrel,re-export 所有共享 type:

// src/types/index.ts
export type { User } from './user'
export type { Session } from './session'
export type { ApiResponse, ApiError } from './api'
export type { Permission } from './permission'

然后把 AGENTS.md 指向它:

所有共享 type 从 `@/types` 出口,统一从这里 import:

    import type { User } from '@/types'

`@/types` 里没有的,当作 module 私有。任何可能被复用的 type 在新建之前都先搜 `@/types`

单一 import path 让 “之前有没有” 一次搜索就能答。

Step 3:ts-morph CI 抓重名 type

这能补上 review 和 AGENTS.md 漏掉的部分。下面的脚本用 ts-morph(截至 2026 年 6 月为 28.x),覆盖 interface、type alias、enum、class——和 AGENTS.md 规则点名的四种声明一致:

// scripts/check-duplicate-types.ts
import { Project } from 'ts-morph'

const project = new Project({ tsConfigFilePath: 'tsconfig.json' })
const decls = new Map<string, string[]>()

const record = (name: string, path: string) => {
  const list = decls.get(name) ?? []
  list.push(path)
  decls.set(name, list)
}

for (const file of project.getSourceFiles('src/**/*.ts')) {
  const path = file.getFilePath()
  for (const d of file.getInterfaces()) record(d.getName(), path)
  for (const d of file.getTypeAliases()) record(d.getName(), path)
  for (const d of file.getEnums()) record(d.getName(), path)
  for (const d of file.getClasses()) {
    const name = d.getName()
    if (name) record(name, path)
  }
}

const dupes = [...decls.entries()].filter(([, files]) => files.length > 1)
if (dupes.length > 0) {
  for (const [name, files] of dupes) {
    console.error(`Duplicate type ${name}:`)
    for (const f of files) console.error(`  ${f}`)
  }
  process.exit(1)
}

接到 CI 当 required check(npm run check:types)。Codex 看到失败的 CI 输出,下一轮会自己修。

Step 4:让 ESLint 指向规范位置

codebase 有自定义 ESLint rule 的话,加一个:发现重复声明共享 type 就报错并指向规范路径。没有自定义 rule 也能用 eslint-plugin-importno-duplicatesno-restricted-imports,把 import 都收口到 @/types

{
  "rules": {
    "no-restricted-imports": ["error", {
      "patterns": [{
        "group": ["**/auth/types", "**/api/types", "**/user/types"],
        "message": "共享 type 从 @/types import,不要走深路径。"
      }]
    }]
  }
}

Codex 读得到 ESLint 输出,会自己把 import path 改对。

Step 5:agent PR 先看 import

这是 ts-morph 抓不到的情况的人工兜底——同名但形状稍不同。加进 PR checklist:

- [ ] 没有新增 `interface``type``@/types` 里重名
- [ ] 新的共享 type 都在 `src/types/<domain>.ts`,不在 feature 文件里 inline
- [ ] `npm run check:types` 过(跑 ts-morph 的重名检查)

怎么确认修好了

  1. 本地跑一遍重名检查:npm run check:types 应该 exit 0 且什么都不打印。
  2. 确认 Codex 加载到了新规则。在 repo 根目录跑 codex --ask-for-approval never "Summarize the type-reuse rules in AGENTS.md.", 看它的回答里有没有这条规则。如果没有,说明你的 AGENTS.md 超了 32 KiB 上限,或放错目录了。
  3. 给 Codex 一个会动到共享 type 的任务,读 transcript:在任何新声明之前,你应该能看到它对这个 type 名跑了 rg 搜索。

预防

  • AGENTS.md 强制 “先搜再建”,连具体 rg 命令一起给
  • 所有共享 type 放 src/types/,从 src/types/index.ts 出口
  • ts-morph CI 检查重名的 interface / type / enum / class
  • ESLint no-restricted-imports 把 import 收到 @/types
  • Reviewer 抽查 agent PR 里新加的 interface / type 声明
  • 泛名(UserItemResponse)强制加 domain 前缀,避免未来撞名

常见问题

为什么 TypeScript 不直接抓出重复? 因为两份声明在不同文件、不同 module scope 里,它们是两个碰巧同名的不同类型。只要字段结构兼容,TypeScript 就允许互相赋值。只有当某个字段在一个形状上有、另一个上没有时,你才会感觉到这个 bug。

AGENTS.md 规则里该用 rg 还是 greprg(ripgrep)。截至 2026 年 6 月,Codex、Cursor 和大多数 coding agent 都把 ripgrep 当搜索主力——它会遵守 .gitignore、在大 repo 上快得多,也是 Codex 默认就会用的命令。rg 没装时 grep 也能用,但 agent 不太会主动去跑它。

Codex 老是不理这条规则,怎么办? 先确认它加载了:跑 codex --ask-for-approval never "Summarize the type-reuse rules in AGENTS.md."。如果回答里没有这条规则,多半是你的 AGENTS.md 超了默认 32 KiB 上限(project_doc_max_bytes),或者不在 Codex 会走的路径上(从 Git root 走到工作目录)。把文件精简掉,或者把规则挪到离被改代码最近的那份 AGENTS.md——最近的那份优先。

仓库里已经存在的重复怎么清? 跑 Step 3 的 ts-morph 脚本,列出每个重复名字和它的文件路径。选定规范声明,删掉其余的,然后让 type-checker 来驱动修复:每个报错的 import 都指向一处需要切到 @/types 的代码。改完再跑 npm run check:types,直到全绿。

IDE 里的 Codex 和 CLI 都适用吗? 都适用。两者读的是同一条 AGENTS.md 链(全局 ~/.codex/AGENTS.md,再加从 Git root 往下的项目文件)。ts-morph CI 检查和 ESLint 规则跑在你的流水线里,不管 PR 是哪个 Codex 入口产出的。

相关

标签: #Codex #agent #排查 #types