Codex 生成代码不符合项目结构

Codex 把文件建错位置、忽略项目约定?用 AGENTS.md、显式引用同类型文件、plan-first 把它框回项目风格。

你让 OpenAI Codex(Codex CLI 或云端 agent)“加个用户设置页面”,代码能跑,但文件放在 src/UserSettings.tsx,而不是项目里其他页面所在的 src/pages/settings/index.tsx;命名是 UserSettings 而不是项目里一致的 SettingsPage;还用了 useState,可你的项目全用 Zustand。Codex 默认按”通用最佳实践的 React”生成,不会自动推断你仓库里既有的约定。

最快修复: 在仓库根目录放一份 AGENTS.md,把路径、命名、技术栈写清楚,然后每次 prompt 都指向一个具体的同类型文件让它照抄。截至 2026 年 6 月,Codex 每次运行都会自动读取 AGENTS.md,光这一步就能解决大部分情况。下面是完整的诊断、配置和验证方法。

先判断你属于哪一类

现象可能原因跳转
仓库里没有 AGENTS.md / CLAUDE.md / 规则文件Codex 没有可遵循的规范原因 1 / Step 1
有指南但 Codex 仍用通用写法prompt 没指向样本文件原因 2 / Step 2
用错了库(axios、裸 useStateprompt 没钉死技术栈原因 3 / Step 1
一半对一半错项目本身不一致原因 4
文件落在 root,不是 apps/webCodex 选错了 workspace 根原因 5 / Step 5

常见原因

按命中率从高到低排序。

1. 仓库里没有 AGENTS.md(或同类文件)

Codex 读取 AGENTS.md 作为项目指令;Claude Code 读 CLAUDE.md;Cursor 读 .cursor/rules/*.mdc(旧的单文件 .cursorrules 自 Cursor 0.43 起已弃用,但仍能加载)。三者都没有时,Codex 只能扫几个最近编辑的文件靠猜。

你:加个用户设置页面
Codex:(没 AGENTS.md,看了 README,里面没说目录约定)
    -> 创建 src/UserSettings.tsx
你的项目:所有页面在 src/pages/<feature>/index.tsx

如何判断: 看仓库根目录。如果 AGENTS.mdCLAUDE.md.cursor/rules/ 一个都没有,就是这个原因。

2. Codex 没读过任何同类型文件

即使有指南,如果 prompt 没指向具体的”参考样本”,Codex 还是会按训练里的”标准 React 项目”写,与你的本地约定脱节。

如何判断: 看 Codex 在生成前到底有没有读同类型文件。每次会话都会以 JSONL 记录在 ~/.codex/sessions/YYYY/MM/DD/rollout-<session-id>.jsonl,把里面的 tool call 过滤出来:

# 取最近一次会话,只看 tool call(需要 jq)
ls -t ~/.codex/sessions/**/rollout-*.jsonl | head -1 | \
  xargs cat | jq 'select(.type == "tool_call")'

如果在写文件之前没有对同类型文件的 read,就是这种情况。(想实时追踪,也可以用 RUST_LOG=debug codex 2>&1 | grep file:。)

3. Prompt 没钉死技术栈

默认 prompt “实现 X 功能”等于放任 Codex 自由发挥。它会用最通用的 React + axios + useState,无视你项目的 Zustand / SWR / TanStack Query / shadcn-ui 选型。

如何判断: 对比生成代码引入的库和 package.json。如果引入了项目里没装的库(或忽视了已经装的状态管理库),说明 prompt 太宽松。

4. 项目结构本身就不一致

如果项目里一半文件用 kebab-case、一半用 PascalCase,一半在 pages/、一半在 routes/,Codex 会随机模仿其中一种,结果大约一半时间看起来”风格不对”。

如何判断:find src -name '*.tsx' | sort,看命名和位置是否统一。如果一半 PascalCase 一半 kebab-case,那是项目自身的问题,再好的 prompt 也补不全。

5. monorepo 下 Codex 选错了 workspace 根

monorepo 里,Codex 以启动时所在的目录为锚点。如果你从仓库根目录启动,它就会看到 root 的 package.json 当成入口,把文件写到 root,而不是 apps/webpackages/ui

如何判断: 生成的文件出现在 root 或错误的 workspace,而不是和同类型代码在同一个 workspace。

最短修复路径

按收益排序。Step 1 + 2 通常能让 Codex 一次生成就贴近项目。

Step 1:写一份 AGENTS.md,把路径、命名、技术栈全列清楚

放在仓库根目录,Codex 每次运行都会读。想从脚手架起步的话,在 Codex 会话里跑 /init,它会根据检测到的依赖和目录结构生成初稿,你再删到只剩真正重要的规则:

# AGENTS.md

## 项目结构
- 页面:src/pages/<feature>/index.tsx
- 共享组件:src/components/<PascalName>.tsx
- API 调用:src/lib/api/<resource>.ts
- 类型定义:src/types/<resource>.ts
- monorepo 主代码在 apps/web,不要写到 root

## 命名约定
- 组件文件:PascalCase(SettingsPage.tsx)
- hook 文件:useXxx.ts
- 路由 segment:kebab-case(user-settings)

## 技术栈(不要引入替代品)
- 状态管理:Zustand(禁止 Redux / Recoil / 裸 useState 做全局)
- 数据请求:TanStack Query(禁止 axios 裸用)
- 表单:react-hook-form + zod
- 样式:Tailwind + shadcn-ui

## 工作流
- 改完跑 `pnpm typecheck && pnpm lint && pnpm test`
- 新建依赖前先问

注意别超过默认的 32 KiB 上限(~/.codex/config.toml 里的 project_doc_max_bytes),超了 Codex 会截断。规则要精炼,别写成长篇大论。

Step 2:每次 prompt 都引用一个具体的同类型文件

让 Codex 在写之前先读一个同类型的已有文件:

帮我加一个 "Notifications Settings" 页面。

请先读这两个文件,复制它们的结构和风格:
- src/pages/account-settings/index.tsx(同类型页面)
- src/components/SettingsCard.tsx(卡片组件用法)

然后按 AGENTS.md 的命名和目录约定生成新页面。
不要引入新依赖,沿用 Zustand + TanStack Query。

让它去读一个具体文件,比”参考现有风格”这种模糊指令有效得多——因为它把输出锚在你真实的代码上,而不是它训练得来的默认习惯。

Step 3:要求 plan-first,写代码前先确认结构

让 Codex 先输出计划,你确认后再让它动手:

开始写代码前请先输出一个 plan,包含:
1. 要创建 / 修改的文件路径列表
2. 每个文件的简短目的
3. 引入的新依赖(如果有)
4. 涉及哪些既有模块

等我说 "go" 再开始 edit。

如果路径错了,plan 阶段就能拦住,省得回滚。

Step 4:用 tree + naming 校验让 Codex 自查

在 AGENTS.md 末尾加一段自查指令:

# 生成新文件后执行:
tree src/pages -L 2
# 确认新页面路径符合 src/pages/<feature>/index.tsx

或者直接给一个校验脚本,并在 AGENTS.md 里要求必跑:

node scripts/verify-structure.mjs
# 输出 OK 或列出违规文件

Step 5:monorepo 锁定工作目录,并放一份嵌套 AGENTS.md

两个修复可以叠加。第一,从正确的 workspace 启动 Codex(或用参数指向它),让根目录对:

# 用 --cd(别名 -C)设置工作根。这是当前的 flag,
# 旧的 --working-dir 名称已被替换。
codex --cd apps/web "加一个用户设置页面"

# 需要跨 workspace 协作?再授权额外的可写目录:
codex --cd apps/web --add-dir ../packages/ui

# 或在 prompt 开头明确:
"工作目录是 apps/web。所有新文件必须在 apps/web/src 下,
 不要碰 root 或其他 workspace。"

第二,利用嵌套指令文件。Codex 会把从 git root 到当前目录沿途的每一份 AGENTS.md 合并起来,所以在 apps/web/AGENTS.md 里写 web 专属规则,工作在该目录时就会覆盖根目录那份(同目录下若同时存在 AGENTS.override.md,以 override 为准)。把 workspace 专属的路径写进该 workspace 自己的文件里。

怎么确认修好了

重新发一遍请求,然后检查:

  1. 路径: 新文件落在 AGENTS.md 规定的位置(例如 src/pages/<feature>/index.tsx),而不是 root。
  2. 命名: 组件名 / 文件名符合你的约定(SettingsPage 而不是 UserSettings)。
  3. 技术栈: import 和 package.json 一致——Zustand / TanStack Query 进来了,axios / 裸全局 useState 没出现。
  4. 读取记录: 会话 JSONL 里能看到写文件之前对同类型文件的 read。

如果一次干净运行四项全过,说明 AGENTS.md 加载正常、prompt 也锚住了。

常见问题

AGENTS.md 能替代 CLAUDE.md 和 .cursor/rules 吗? 不能。每个工具读自己的文件:Codex 读 AGENTS.md,Claude Code 读 CLAUDE.md,Cursor 读 .cursor/rules/*.mdc。三份都放在仓库根目录并保持同步,免得某个 agent 跟着过时规则走。Codex 还会合并全局的 ~/.codex/AGENTS.md(如果你在那里放了个人默认配置)。

Codex 还是不理 AGENTS.md,为什么? 最常见的是文件太大(超过 32 KiB 的 project_doc_max_bytes 上限,尾部被截断),或者你是从 git root 之上的目录启动 Codex 的,它根本没找到文件。确认文件在根目录、且 Codex 是在仓库内部启动的。截至 2026 年 6 月,Codex 每次运行都会重建指令链,没有需要手动清的缓存。

怎么看 Codex 到底读了哪些文件? 查会话日志 ~/.codex/sessions/YYYY/MM/DD/rollout-<id>.jsonl,过滤 tool_call 条目;或者用 RUST_LOG=debug codex 运行后 grep file:。如果写文件之前没有对相关同类型文件的 read,说明你的 prompt 需要加一句明确的”先读这个文件”。

项目结构本身就不一致,怎么办? 再好的 prompt 也救不了自相矛盾的仓库。挑一套约定,写进 AGENTS.md 作为唯一标准,再把不符合的文件迁移过来(或把遗留区域标为”不要动”)。在那之前,Codex 会照搬它碰巧读到的那种写法。

设置工作目录用哪个 flag? --cd(别名 -C),例如 codex --cd apps/web。旧的 --working-dir 名称已被替换。用 --add-dir 授权额外的可写目录。

预防建议

  • 仓库根目录维护 AGENTS.md(Codex)/ CLAUDE.md(Claude Code)/ .cursor/rules/*.mdc(Cursor),并保持同步。
  • 新增任何文件类型时,先更新 AGENTS.md,再写第一个样本,再让 AI 模仿。
  • 在 prompt 模板里强制”先读一个同类型样本,再开始写”。
  • 写一个 scripts/verify-structure.mjs,在 CI 阶段跑,把命名 / 目录违规的 PR 拦下。
  • monorepo 项目用 codex --cd <workspace> 启动,并加一份 workspace 专属的嵌套 AGENTS.md,别只靠仓库根目录。
  • 每季度 review AGENTS.md,把 AI 反复犯的同一种错(比如总选 useState)写成明确的禁令。

相关阅读

外部参考:OpenAI Codex AGENTS.md 指南Codex CLI 命令行参考

标签: #AI 编程 #排查 #排查 #Codex