Codex 里的 Skill,我会更在意权限、规则和验证
有一次我只是让 Codex 改
UserSettingsPanel.tsx里的 loading 状态,Skill 里写的是"定位文件、改代码、跑测试"。组件确实改好了,测试也过了,但 diff 里多出了settingsStore.ts和SettingsProvider.tsx的改动。问题不在那几行代码能不能跑,而是我根本没让它重写状态入口。测试只覆盖了 UI 渲染,没有覆盖 store 结构变化,所以测试全绿,事故照样发生。

Claude Code 那篇我更在意触发和上下文。到了 Codex 这里,我的关注点会往后挪一步:它进入 Skill 之后,会读哪些文件,会不会改过界,会跑什么命令,验证失败时会不会继续硬做。
这不是说 Codex 的 Skill 更危险,而是它更接近一个会动手的执行代理。Skill 写得越像操作手册,就越要把权限、修改范围和验证边界写清楚。
不是同一份 SKILL.md 就能跨工具直接用
Codex 和 Claude Code 都可以用 SKILL.md 承载工作流。文件形态看起来接近,但我不会把同一份 Skill 原封不动地跨工具复用。
在我这套使用里,Codex Skill 放在 .codex/skills/<skill-name>/SKILL.md 这类目录下。使用时可以明确点名某个 Skill,也可能让任务描述触发它。这个部分和 Claude 侧有相似之处。
差别出现在 Skill 进入之后。
Claude 侧我经常先担心它有没有进错场:一个普通组件任务,会不会被 description 带去做发布排障;一篇文章润色,会不会被误当成产品方案。Codex 侧我更早会看执行边界:它准备改哪些文件,哪些命令需要权限,验证失败之后有没有退回路径。
同样一句"完成后运行测试",放在 Claude 侧可能只是提醒;放在 Codex 侧,就可能变成下一步真实执行的命令。Skill 不是只影响表达,它会影响工具调用顺序。
Codex 侧的约束不是只有 Skill
我现在看 Codex 任务时,不会只看某个 Skill 本身。
至少有几层东西会一起影响它:
- 当前会话权限:哪些命令能直接跑,哪些需要用户确认。
- 项目规则:比如
.codex/rules里的写作、审稿或工程约束。 - 项目约定:如果代码项目里有
AGENTS.md,里面通常会写目录、测试、提交边界。 - 当前任务描述:用户这次到底只让改哪里,还是允许顺手重构。
- Skill 自己的工作流:文件定位、修改步骤、验证方式、交付标准。

这些东西有优先级冲突时,我更相信当前权限和项目规则。比如 Skill 里写了"检查构建结果",但当前环境不允许直接跑构建命令,这一步就不该绕过去执行。更稳的写法是把退回方案写进 Skill:
如果项目允许构建,运行项目指定的构建命令;如果构建命令受限,优先运行类型检查或相关单测;如果验证命令都不能执行,必须在最终回复里说明原因,并用 diff 确认修改范围。
这段话看起来啰嗦,但它解决的是很实际的问题:模型不会因为 Skill 里有"验证"两个字,就默认所有命令都能跑。
技术类 Skill 里,我会写具体定位命令
Codex 侧的技术 Skill,我会比 Claude 侧多写一类东西:常用定位命令。
不是写"先搜索相关文件"。这句话太宽,最后经常搜出一堆无关结果。更有用的是把这个项目里稳定的入口写出来:
bash
rg "useFormState" --type ts
rg "PaymentCallback" src/
rg "settingsStore" src test
这些命令不是为了显得专业,而是减少模型猜目录的机会。一个前端项目里,"状态管理入口"可能在 src/stores,也可能在 features/settings/model,还可能藏在 provider 里。让模型自己猜,通常比给它一条窄搜索更不稳定。

但定位命令也不能写得太宽。
我见过一种 Skill 写法,上来就让模型跑:
bash
rg "settings"
这和没写差不多。它会把页面、文案、测试、路由、埋点全搜出来,最后模型反而更容易把任务范围扩大。好的定位命令应该服务这次任务的第一步判断:我要找的是组件入口、状态入口、接口入口,还是测试入口。
所以我会在 Skill 里把命令按目的拆开:
| 目的 | 命令示例 | 用来判断什么 |
|---|---|---|
| 找组件入口 | rg "UserSettingsPanel" src |
这次任务主要改哪个 UI 文件 |
| 找状态入口 | rg "settingsStore" src test |
是否真的需要碰状态层 |
| 找测试覆盖 | rg "UserSettingsPanel" test src --glob "*test*" |
有没有相关断言能验证 |
| 找相邻实现 | rg "loading" src/features/settings |
当前模块已有模式是什么 |
这类命令不保证模型一定写对代码,但能把它拉回项目已有结构里。
修改范围要写成硬边界
我现在不太喜欢在 Skill 里写"修改相关文件"。这句话对人来说没问题,对执行代理来说太松。
"相关"可以被解释得很宽。改 loading 状态时,组件相关、store 相关、provider 相关、接口相关、测试相关,全都能算相关。最后 diff 变大,测试也可能过,但任务已经变形。
更稳的写法是把边界写硬一点:
默认只改当前任务指定文件和直接验证所需文件。不改配置、不迁移目录、不重构状态体系、不改公共 provider。除非当前任务明确要求,或者先说明为什么必须扩大范围。
这句话的价值不是禁止模型做正确修复。它是在要求模型扩大范围之前先停一下,把理由暴露出来。

我尤其会盯三类文件:
- 配置文件:
package.json、构建配置、CI 配置。 - 公共入口:全局 provider、路由、store 聚合入口。
- 跨模块共享文件:公共 hook、通用组件、基础工具函数。
这些文件不是不能改,但不应该被 Skill 顺手带着改。因为一旦改动出现在这些位置,影响面就不再是当前任务本身。
验证边界比"跑测试"更重要
Codex 侧 Skill 里,我会把验证写得具体一点。
只写"完成后跑测试"不够。它没有回答三个问题:
- 跑哪个测试。
- 测试不能跑怎么办。
- 测试通过后还要不要看 diff。
我现在更愿意写成这种格式:
text
验证顺序:
1. 优先运行与本次修改直接相关的测试。
2. 如果项目有类型检查且当前环境允许,运行类型检查。
3. 如果测试或类型检查因为权限、依赖或环境失败,不要假装已验证;说明失败原因。
4. 最后检查 diff,确认没有改到任务外文件。
这里的 diff 检查很关键。它不是功能验证,不能证明代码一定正确。但它能拦住另一类问题:模型把任务做大了。
比如一条 UI loading 修复,理想 diff 应该很小。哪怕测试全绿,如果 diff 里出现了 provider 重写,我也会停下来复看。Skill 里把这一步写明,能让 Codex 在收尾时多做一次范围自检。

不要让 Skill 推着模型越权执行
Skill 写得越详细,越容易出现另一个问题:模型把里面每一步都当成应该完成的任务。
但有些步骤本来就依赖权限和上下文。比如:
- 安装依赖。
- 改数据库 migration。
- 跑部署脚本。
- 修改 CI。
- 重写配置。
- 批量格式化整个项目。
这些动作不应该被 Skill 默认推动执行。它们要么需要用户确认,要么需要当前任务明确要求。
我会在 Codex 侧 Skill 里直接写:
不要为了让验证通过而自动安装新依赖、改全局配置、跳过权限限制或扩大重构范围。遇到这类需要时,先说明原因和影响,再等待确认。
这不是保守,而是把执行风险还给人来判断。Skill 可以沉淀经验,但不能替用户授权。
我会用任务后的 diff 反过来修 Skill
Codex 执行完一次任务后,最值得看的不是它最后那段总结,而是 diff。
如果 diff 里反复出现不该改的文件,说明 Skill 的修改边界写得太软。如果任务经常"完成了但没跑验证",说明验证步骤写得太空。如果模型总是先搜一大堆无关文件,说明定位命令太宽。
我一般会按这个顺序回查:
- 这次任务的入口文件找对了吗。
- diff 是否只覆盖当前任务需要的文件。
- 验证命令有没有跑,没跑有没有说明原因。
- 是否触碰了配置、公共入口或跨模块共享文件。
- Skill 里哪一句话给了它扩大范围的空间。
然后只改 Skill 里最小的一段。可能是补一句"不要改 provider",可能是把 rg "settings" 改成更窄的搜索,也可能是把"跑测试"改成具体的验证顺序。
Codex 侧我最在意的不是 Skill 写得多完整,而是它能不能稳住三件事:先找到正确文件,只改该改的范围,最后把验证和没验证的部分说清楚。
表面上看,这是在写一份 SKILL.md。实际是在给一个会读文件、会改代码、会跑命令的执行代理加护栏。Claude 侧更多是在防止进错上下文;Codex 侧更像是在防止它进场之后跑过界。