1. 为什么 Codex 在 Monorepo 里总改错子项目
一个仓库里塞进apps/web、apps/admin、apps/api、packages/ui、packages/utils、packages/types之后,你只丢一句「修复用户列表筛选」,Codex 面对的是一个没有边界的搜索空间。它不知道「用户列表」到底在 web 还是 admin,不知道筛选组件是不是来自公共 ui 包,也不知道查询参数类型是不是在 types 里被三个应用共用。于是它倾向于选一个「看起来更通用」的改法:顺手把公共组件改了,顺手把类型抽到 packages,顺手在根目录装了个依赖。结果就是你要的只是管理后台一个小 bug,diff 里却躺着用户端页面、公共类型和根锁文件。
这不是 Codex 读不懂目录,而是仓库没有把工作区边界、依赖方向和修改权限写给它看。普通单应用项目只有一套依赖文件、一套构建配置、一套测试命令,Codex 猜错的空间很小。Monorepo 里每个子项目都有自己的package.json、tsconfig、构建脚本和运行入口,Codex 必须自己推断「这个任务属于谁、能碰谁、碰了会影响谁」。推断一旦缺失,越界修改就是必然。
我试过在一个 pnpm workspace 里让 Codex 修 admin 的表格分页,它把packages/ui里的Table组件默认pageSize从 10 改成 20,理由是「更符合管理后台习惯」。admin 是好了,web 端用户列表也跟着变成 20 条一页,直接引发线上反馈。这类问题的根因不是模型能力,而是任务没有绑定工作区、公共包没有引用检查、依赖方向没有约束。
要解决它,核心是三件事:先建立工作区地图,再给每个任务绑定唯一主要工作区,最后用分目录AGENTS.md把边界写成 Codex 每次都会读到的规则。下面从接入配置开始,一步步给出可复制的骨架和验证动作。
2. 接入 TaoToken 与 Codex 的前置配置
在讨论边界控制之前,得先让 Codex 稳定跑起来。我用 TaoToken 作为统一入口,它兼容 OpenAI 风格的接口,Codex、Cline、Claude Code 这类工具都能接。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api ,注意 API 地址不带 UTM 参数,配置时别把查询串一起粘进去。
第一步是拿 Key。打开控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 创建一个新 Key,复制出来先存到本地环境变量,别直接写进仓库文件。我习惯用.env.local并加进.gitignore:
# .env.local 不要提交到仓库 TAOTOKEN_API_KEY=sk-你的实际Key TAOTOKEN_BASE_URL=https://taotoken.net/api第二步是确认模型 ID。不同工具对模型名的写法不完全一致,Codex 走 OpenAI 兼容协议时通常填gpt-5-codex或你账号下可用的编码模型 ID。具体可用列表在模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里能直接看到,选一个编码能力强的即可。如果你要长期跑 Agent 类任务,Coding Plan 页面 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 有对应的套餐说明,按自己的调用频率选。
第三步是配置 Codex 的auth.json。Codex CLI 读取的凭据文件通常在~/.codex/auth.json,把 Base URL、Key、Model ID 三件套写全,缺一个都会在请求时报 401 或模型不存在:
{ "OPENAI_API_KEY": "sk-你的实际Key", "OPENAI_BASE_URL": "https://taotoken.net/api", "model": "gpt-5-codex" }如果你用的是 Cline 或 Claude Code,配置位置不同但三件套一致。Cline 在 VS Code 设置里填 API Provider 为 OpenAI Compatible,Base URL 填https://taotoken.net/api,Key 填上面那个,Model ID 填编码模型名。Claude Code 走 Anthropic 协议时参考接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里的说明,把 Base URL 指向对应端点。CC Switch 这类多配置切换工具也支持,本质还是把这三项填对。
配置完成后先做一次最小验证,别急着让它改代码。在终端里发一个只读请求,确认链路通:
curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5-codex", "messages": [{"role": "user", "content": "只回复 ok"}] }'返回里能看到choices数组且内容为ok,说明 Key、Base URL、模型 ID 三者都对上了。这一步很关键,因为后面所有边界控制都建立在 Codex 能正常请求的前提上。如果这里就报错,先解决接入问题,别把配置错误和越界修改混在一起排查。
3. 可复制的 AGENTS.md 骨架与依赖图约束
边界控制的核心是把规则写进 Codex 每次都会读的文件。根目录放一份全局AGENTS.md,每个子工作区再放一份局部规则,Codex 进入对应目录时会叠加读取。先给根目录骨架:
# 全局 Monorepo 规则 ## 工作区地图 - apps/web:用户端应用,面向 C 端 - apps/admin:内部管理后台 - apps/api:后端接口服务 - packages/ui:公共组件库,被 web 与 admin 依赖 - packages/utils:纯函数工具,无业务逻辑 - packages/types:公共类型定义,被 api、web、admin 依赖 ## 依赖方向 - apps 可以依赖 packages - packages 不得反向依赖 apps - packages/ui 可以依赖 packages/types - packages/types 不得依赖任何 UI 组件 - 禁止通过相对路径跨工作区导入源码 ## 修改权限 - 每个任务必须明确一个主要工作区 - 未在任务中声明的工作区默认不可修改 - 修改 packages 下任何包前,必须先输出引用方清单 - 根目录 package.json 与锁文件非必要不修改 ## 依赖安装 - 新增依赖必须用 --filter 指定工作区 - 禁止在根目录直接 add 业务依赖 - 锁文件变化必须单独说明原因再给apps/admin/AGENTS.md一份局部规则,把管理后台的边界写死:
# apps/admin 规则 - 本工作区仅用于内部管理后台 - 不修改 apps/web 下任何文件 - 页面组件优先复用 admin 内部组件 - 修改权限逻辑必须执行路由测试 - 需要公共包能力时,先说明原因,不直接改 packagespackages/ui/AGENTS.md则强调向后兼容:
# packages/ui 规则 - 不允许包含具体业务逻辑 - 修改默认行为必须保持向后兼容 - 新增可选属性优先于破坏性修改 - 修改后必须验证 web 与 admin 两个应用 - 禁止从 apps 反向导入任何代码依赖图约束光靠文字还不够,最好让工具能自动识别。pnpm workspace 在根pnpm-workspace.yaml里声明范围:
packages: - "apps/*" - "packages/*"然后在根package.json里加一个依赖检查脚本,用madge或dependency-cruiser检测循环依赖和越界引用:
{ "scripts": { "check:deps": "depcruise --config .dependency-cruiser.js apps packages" } }.dependency-cruiser.js里禁止 packages 反向依赖 apps:
module.exports = { forbidden: [ { name: "no-packages-to-apps", severity: "error", from: { path: "^packages" }, to: { path: "^apps" } }, { name: "no-cross-workspace-relative", severity: "error", from: {}, to: { path: "\\.\\./\\.\\./packages" } } ] };这套配置的作用是:即使 Codex 想抄近路用相对路径跨包导入,CI 或本地检查也会直接报错。把pnpm check:deps写进提交前钩子,越界修改在提交阶段就被拦住。规则文件加自动检查双管齐下,Codex 的修改范围会被压到目标包内。
4. 验证一次越界修改是否被拦住
规则写完要实测,否则不知道 Codex 到底读没读。我构造一个典型越界场景:任务只要求改 admin 的用户列表筛选,但故意留一个诱惑——packages/ui里有个FilterInput组件,改它能「顺便」让 admin 更省事。看 Codex 会不会越界。
先给 Codex 一个带边界的任务描述:
当前任务属于 apps/admin。 目标:修复管理后台用户列表筛选参数错误。 允许修改: - apps/admin/src/pages/users - apps/admin/src/api/users.ts 暂不修改: - apps/web - apps/api - packages/ui - packages/types 如果发现必须修改公共包,请先说明原因,不要直接执行。然后观察它的动作。理想情况下它会先输出工作区地图,再只改 admin 目录。如果它提出「建议修改 packages/ui 的 FilterInput 以复用」,这就是越界信号,此时不要批准,而是让它先输出引用方清单:
修改 packages/ui 前,先输出: 1. 当前组件被哪些工作区引用 2. 修改是否改变默认行为 3. 是否可以通过新增属性解决 4. 是否需要保持旧调用兼容 5. 哪些应用必须执行回归测试用rg快速确认引用范围:
rg "FilterInput" apps packages输出会告诉你apps/web和apps/admin都在用。这时候正确做法是新增一个可选属性,而不是改默认行为。让 Codex 在packages/ui里加defaultKeyword?: string,保持旧调用不变,然后只在 admin 里传这个新属性。这样 web 端行为完全不受影响。
改完后跑分层验证。第一层只测 admin:
pnpm --filter admin test pnpm --filter admin type-check pnpm --filter admin build第二层因为动了packages/ui,补测公共包和依赖它的应用:
pnpm --filter @project/ui test pnpm --filter @project/ui build pnpm --filter web build pnpm --filter admin build最后检查锁文件有没有被动过:
git diff --stat git diff -- pnpm-lock.yaml如果本轮没新增依赖,锁文件却出现大量差异,立刻停下来查原因,别让 Codex 以「自动修复依赖」为由重新生成整个锁文件。实测下来,只要任务描述里写清允许和禁止的目录,再配合AGENTS.md的规则,Codex 越界的概率会明显下降。真正需要公共包改动时,把它拆成独立任务,单独评估影响面。
5. 常见报错与越界排查
接入和边界控制过程中会遇到几类典型报错,逐个对照排查。
401 Unauthorized或invalid api key:Key 没填对或没生效。检查auth.json里的OPENAI_API_KEY是否和 API Keys 页面创建的一致,注意别把Bearer前缀重复写进去。环境变量方式的话确认$TAOTOKEN_API_KEY在当前 shell 里能echo出来。
local proxy failed或连接被拒:Base URL 写错。确认是https://taotoken.net/api,不要带 UTM 查询串,也不要漏掉/api。有些工具要求填到/v1,按接入文档里的说明来,别自己拼。
reading choices报错或返回体解析失败:通常是模型 ID 不存在或返回了非预期结构。去模型对话页确认可用模型名,auth.json里的model字段要和实际可用的一致。如果返回体里没有choices,多半是请求被拒或模型名拼错。
OAuth相关报错:Claude Code 走 Anthropic 协议时可能出现,检查是否误用了 OpenAI 风格的 Key 去请求 Anthropic 端点。按接入文档把协议和端点对应上,别混用。
越界修改类问题没有报错,但 diff 会说话。常见信号有三个:一是git diff --stat里出现你没授权的目录;二是pnpm-lock.yaml在没新增依赖时发生变化;三是packages下出现从apps反向导入的语句。前两个用git diff直接看,第三个用pnpm check:deps或rg "from \"\\.\\./\\.\\./apps"扫一遍。
还有一种隐蔽情况:Codex 把业务逻辑抽到了packages/utils,理由是「两个应用都有类似代码」。这时候别急着接受,先判断两段代码是否真的表达相同业务、未来变化方向是否一致。过早抽取会让公共包参数越来越多,改一个应用要兼容另一个,维护成本反而更高。重复代码不一定马上要共享,让它先留在各自工作区里更安全。
排查顺序建议固定:先确认接入链路通(curl 能返回 ok),再看任务描述有没有绑定工作区,最后看AGENTS.md规则有没有被读到。三层都过了还越界,就把任务拆得更细,一次只让它碰一个目录。
6. 把改动锁在目标包内的长期做法
边界控制不是一次性配置,而是每次任务都要执行的纪律。我的习惯是每个任务开头先让 Codex 输出工作区地图,确认它知道当前任务属于谁、依赖谁、会影响谁,再放它动手。任务结束时让它输出变更报告,把主要工作区、直接修改文件、公共包改动、间接受影响工作区、依赖变化、验证结果列清楚。报告里出现未授权目录,直接回退重来。
分目录AGENTS.md要随仓库演进更新。新增一个apps/mobile-web就补一份局部规则,新增一个公共包就在根规则里登记依赖方向。规则文件本身也要进版本控制,让团队每个人都用同一套边界。依赖图检查脚本挂到 CI 上,越界引用在合并前就被拦下,而不是等线上出问题才发现。
如果你日常主要是单个子项目的功能修改、明确目录内的 bug 修复、单一工作区测试,Codex 配合上面这套规则基本够用。长期维护多个工作区、频繁改公共组件和类型、一个任务影响多个应用时,可以考虑 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里更适合长任务和连续验证的方案,减少分析依赖图和跑多轮测试时的中断。但再高的配置也替代不了包边界,工作区职责模糊的话,调用空间再大也还是会越界。
真正稳定的 Monorepo 开发,不是让 Codex 理解仓库里每一个文件,而是让它清楚知道本轮任务属于哪个工作区、可以改哪些包、每一处公共变化会影响哪些应用。把这三件事写进规则、写进任务描述、写进验证流程,改动自然就锁在目标包里了。