「为什么我的 Codex 没推特上那么神」:从一句配置自伤,到让两个 AI 分工审我的代码
本文背景:一次「我到底哪里用错了 Codex」的排查全过程,由两个视角交替记述——sean(人) 负责提出疑惑、拍板、踩刹车、纠偏;Claude Code(AI,也就是我) 负责排查、接线、跑验证、写这篇复盘。文中所有代码示例均为脱敏的通用样子,不涉及任何真实项目——这一点本身也是被 sean 纠偏的结果,后面会讲到。
0. 一个憋了很久的疑惑
先说清楚我的起点,因为它可能和很多人一样。
我在 X(推特)上刷到过一堆帖子,都在说 Codex(OpenAI 的命令行编码 agent)体验特别好、能力特别强。可我自己用下来,完全没这种感觉——它总是畏手畏脚:干到一半停下来问我「要不要继续」「下一步我建议这样,可以吗」,一个本该一口气做完的任务,被切成七八段来回确认。
于是我抛给 Claude 一个很朴素的问题:
「我很好奇别人都是怎么和 Codex 沟通的。X 上很多人说 Codex 体验特别好,但我实际用下来并没有。所以我怀疑,要么是我的使用方式有问题,要么是我没借助第三方插件的力量。」
这个疑惑有两个隐含假设:(a)是我不会用;(b)我缺了某个别人都在用的插件。 后面会看到,(a) 对了一半,(b) 基本是错的——真正的答案比「缺插件」有意思得多。
1. 第一个反转:不是 Codex 不行,是我把它教停了
排查的第一步不是去查 Codex 有多强,而是去看我给它的配置。
Codex 和 Claude Code 一样,会读一个项目级的指令文件——Codex 读 AGENTS.md,Claude 读 CLAUDE.md。我在某个项目的 AGENTS.md 里,写过这么一条自以为很贴心的规则(大意):
沟通节奏:长任务保持短更新,说明正在查什么、发现什么、
下一步做什么。看起来人畜无害吧?我当初写它,是想让 agent「多汇报、别闷头干」。可正是这句 「下一步做什么」,成了元凶。
我让 Claude 做了个对照实验:同一台机器、同一批任务,为什么 Claude 几乎不过度请示,Codex 却频频停下来?答案是——Claude 读的那个 CLAUDE.md 里,压根没有这条规则。差异不在模型,在配置。
这条规则的作用机制是这样的:你要求 agent 在动手前先「播报下一步」,等于反复提示它「说完计划就该停一下」。对一个被训练成「少废话、埋头把活干完」的 agent 来说,这种 preamble(开场白 / 过程播报)就是一个提前结束的触发器。
我当时是纯靠调试发现这个结论的。写完这篇复盘去查证时,才发现 OpenAI 官方的 Codex prompting guide 早就白纸黑字写过一模一样的话:
"GPT-5-Codex does not emit preambles, and prompting and asking for them will likely result in the model stopping early."
(GPT-5-Codex 本就不输出开场白;你去要求它输出,很可能导致模型提前停止。)
也就是说,我花钱买的「强」,被我自己一句配置给按住了。 我以为在教它好好沟通,其实在教它半途而废。
需要说句公道话(这也是发文前审稿逮到的一处不严谨):这条「别要 preamble」的建议,最强的适用范围是 GPT-5-Codex 那一代;从 gpt-5.3-codex 往后,官方又让开场白/过程消息变得可以主动提示了,不再是一刀切。但我这台机器上的实际观感就是过度请示,删掉那条规则就好了——所以在我的场景里,这个机制确实成立。别把它当成永远普适的定律,当成「先去查查你这条配置是不是在教 agent 提前停」的排查方向就好。
修法很简单:把「下一步做什么」这类要求过程播报的措辞删掉,换成明确的「在同一回合内把任务推到闭环,只有真正卡住、且属于高危或方向性选择时才停下来问」。改完之后,那种「切成八段来回确认」的体感立刻消失了。
第一个教训:当一个工具的体验远不如别人吹的那么好,先怀疑自己的配置,而不是工具本身。很多「AI 不好用」,是配置自伤。
2. 「codex exec 是一次性的」——这是不是死路?
解决了过度请示,我又撞上第二个体感问题。
我平时用 codex exec(Codex 的非交互模式,一条命令跑一个任务、出结果就结束)。它给我的感觉是**「一次性」的**:跑完这一发就散了,没法接着上一轮的上下文继续聊。我当时的原话是:
「那就有问题呀,不能持续配合下去。」
如果真是这样,那「让 Codex 当一个能持续协作的伙伴」这条路就断了。但查下来,这是个误会。Codex 的非交互模式其实能续接:
# 接着最近一次会话继续,并给出新指令
codex exec resume --last "把你刚发现的竞态修掉"
# 或者点名某个会话 id 继续
codex exec resume 7f9f9a2e-1b3c-... "按这个计划实现"resume 会带着上一会话的对话记录和上下文,你只需要补新指令。会话本身以 JSONL 文件存在 ~/.codex/sessions/YYYY/MM/DD/ 下(OpenAI 官方非交互模式文档)。
所以「一次性」是错觉——单发 ≠ 死路。但手动一发一发地 resume、再把 Codex 的输出复制粘贴给另一个工具,太累了。这把我逼到了第三步,也是真正的转折。
3. 换个思路:与其让一个 AI 干到底,不如让两个 AI 分工
我一直在问「怎么让 Codex 一个人更能干」。但更好的问题也许是:为什么非要让它一个人干?
我平时其实是拿 Claude Code 当大脑/编排者,Codex 当执行 + 审查。两个工具之间的信息,一直是我用手在中转(复制粘贴)。真正该自动化的,是这个「手动中转」。
办法是 Codex 自带的一个能力:codex mcp-server。它能把 Codex 自己作为一个 MCP 服务跑起来,让另一个 agent(比如 Claude Code)像调用工具一样驱动它。据 OpenAI 官方的 Agents SDK 文档(讲的正是「用别的 agent 编排 Codex」):
"The server exposes two tools:
codex()to start a conversation andcodex-reply()to continue one, and keeps Codex alive across multiple agent turns."(该服务暴露两个工具:
codex()开启一段对话、codex-reply()继续对话,并让 Codex 在多个 agent 回合之间保持存活。)
于是路线定了:把 Codex 通过 MCP 注册进 Claude Code。
claude mcp add --scope user codex -- <codex 二进制路径> mcp-server重启会话后,Claude 这边就多了 codex / codex-reply 两个原生工具,threadId 负责把多轮串成同一个会话。我在 Claude 里发一句、Codex 那边就答一句,全程不用我再复制粘贴。
分工也随之敲定,而且是 sean 明确拍的板:
Claude Code = 实现层;Codex = 只做对抗式审查,不再让它写实现。
为什么这么分?因为我对 Claude 的实现满意、对 Codex 一遍成稿的质量不够满意;而 Codex 真正的独家价值,不在「写得多好」,在它是另一个模型——后面第 4.2 节细说。
注:
codex mcp这一族命令还比较新,接口会随版本变化——我不是空口说,是真撞见过:某次桌面端更新后,codex这个工具悄悄少了一个参数。这个「可能变」的坑,下面 4.1 会再提。
4. 三个必须解决的工程细节
把两个 AI 接起来只是第一步。要让它长期能用、不给我添乱,有三个坑得先填。
4.1 版本漂移:让我用的 Codex 永远跟着桌面端走
我机器上其实有两个 Codex 二进制:一个是 brew 装的命令行版,一个是桌面端 App 内置的。它俩共用同一份配置目录 ~/.codex,但版本经常不一样——桌面端会自动更新,brew 那个得手动升。
这就带来一个恼人的问题,我当时是这么描述的:
「假如明天 Codex 桌面端更新了,我又得跑来跟你说吗?或者我忘了说、或者新开一个对话,怎么办?」
这是个典型的**「谁负责同步版本」**难题。如果我用的是 brew 那个独立 CLI,那我就得盯着它、时不时手动升、还得记得告诉 AI「我升级了」。一旦忘了,版本就漂移。
治本的解法不是写个定时脚本去查更新(那又是新的维护负担),而是换靶子:把 MCP 注册指向桌面端 App 内置的那个二进制。
| 二进制 | 谁更新它 | 我让 AI 用哪个 |
|---|---|---|
| 桌面端 App 内置 | 随桌面端自动更新 | ✅ 就用这个 |
brew 独立 CLI | 只有手动 brew upgrade 才动 | 留作 fallback |
这样一来,逻辑就闭环了:我本来就会更新桌面端 → App 内置二进制自动变新 → AI 下次新开会话时自动就是新版。 注意是「新开会话」而不是「下一次调用」——MCP 服务在会话启动时被拉起、之后常驻,桌面端更新只换了磁盘上的二进制,不会热替换正在跑的那个进程;等下次新会话重新拉起,才用上新版。但这对我毫无负担:我不用记、不用手动同步,反正每开一个新对话都自动跟上。一个「需要我持续维护」的问题,被改造成了「搭我本来就在走的顺风车」。
代价是放弃了对这个二进制的版本钉控。但这里划算,因为我够 Codex 是走稳定的 MCP 工具接口,真出问题(比如某次更新改了工具参数——前面提的 experimental 风险就应验过一次,某个版本悄悄删掉了一个参数)下次启动时就能发现,大不了把 MCP 指回某个钉死的旧版当兜底。
4.2 为什么要让「另一个模型」审:去相关的盲点
这是整套设计里我觉得最值钱的一点。
一个很自然的疑问是:Claude Code 自己就能做对抗式审查(我让它挑自己实现里的毛病),那再叫 Codex 审一遍,不是重复吗?会不会冲突?
不重复。关键词是去相关(decorrelated)。
一个模型审自己写的代码,它的盲点是和实现相关的——它没想到的攻击面,往往在审查时同样想不到,因为背后是同一套「思维习惯」。而换另一个模型来审,它带着不同的训练、不同的盲点,能逮住第一个模型在结构上就够不到的东西。
所以顺序是这样定的,而且只对高危改动上 Codex:
- Claude 先自审(便宜、在环内,但盲点和实现相关);
- Codex 压最后(贵、跨模型、独立),且只审核心/高危的改动(认证、支付、并发、流式、持久化那一类);普通改动 Claude 自审就够。
倒过来(Codex 先审)就是拿最贵的资源去帮 Claude 抓它自己也能抓的东西,浪费。
4.3 上下文污染:审查得跑在隔离的房间里
还有一个我很在意的问题:Codex 审查的输出,会以什么形式回到 Claude 这边? 我担心的是上下文污染——如果把 Codex 那一大段带思考过程、带寒暄的原始输出,直接灌进 Claude 的主对话,会有两个坏处:吃掉大量 token,以及锚定 Claude 的判断(让它不自觉地跟着 Codex 走)。
解法是:让审查跑在一个隔离的「房间」里(一个独立的 subagent),只把结构化的干净结论返回主线程。
主对话(Claude 编排 + 实现 + 对账)
│
└── 派一个隔离 subagent ──► 在里面驱动 Codex 审查
(原始噪音留在这个房间里)
│
└── 只回:[严重度][结论][触发序列] 的干净清单原始的审查过程、思考、客套,全留在那个隔离房间里;回到主线程的只有提炼过的 findings。治污染靠的是「审查在哪跑」,不是靠用哪个工具。
5. 拉通一遍:一次真实的「实现 → 自审 → 隔离审查 → 对账」
光有设计不算数,得跑一遍真的。下面用一个脱敏的通用示例演示整条链——注意,这里本来我想直接拿一段真实的生产代码来审,但 sean 当场踩了刹车:那段代码涉及线上安全逻辑,公开博客里必须换成中性示例。这是这篇文章里我被纠偏的第 N 次,也正好印证了后面的一个教训:AI 不是自动驾驶。
假设我们要审的,是这么一段逻辑(纯属虚构,只为演示):一个「带宽限期的一次性放行」——某个凭据在正常轮换后被作废,但如果客户端因为网络丢包在几秒内重发了旧凭据,就当成「重试」放行一次,而不是一律判为攻击、把用户的所有凭据全撤掉。
# —— 纯演示用的中性伪代码,非任何真实项目 ——
def handle_replay(old_token):
with row_lock(old_token): # 行锁,序列化并发重放
eligible = (
within_grace_window(old_token) # 在宽限窗内
and not old_token.grace_consumed # 单次消费闸:消费过就不再放行
and not external_revoked(old_token) # 外部登出检查(异常吞掉→False,即 fail-open)
)
if eligible:
old_token.grace_consumed = True # 落闸,这一发放行后即作废
return issue_successor(old_token) # 放行一次
revoke_everything(old_token.user) # 其余一律判攻击、全撤
raise Unauthorized()第一步,我(Claude)先自审,记下几个最担心的点。其中排第一的是:那个「单次消费闸」(grace_consumed)到底是不是原子的?它依赖行锁一直持有到事务提交、且这个标志被落盘——万一 issue_successor() 没提交、把提交推给了上层,锁提前释放,并发的第二次重放会不会也被放行?
第二步,把 Codex 当隔离陪审员,在一个独立 subagent 里驱动它做对抗式审查,只让它回结构化结论。
第三步,也是最关键的一步——对账。我把 Codex 的每一条结论,逐条和我自己的判断比对。结果特别能说明「去相关」的价值,四种情况都出现了:
| 结果类型 | 具体发生了什么 |
|---|---|
| ✅ 我最担心的点,被验证清白 | Codex 真的去把 issue_successor() 的实现读了,确认它确实提交、锁也确实持有到提交——我排第一的疑虑被排除了 |
| ⚠️ 我俩都同意、且要改 | 那个 external_revoked() 外部检查在异常时「放行」(fail-open)。我们都同意它有风险,而 Codex 进一步点出:这里放行漏掉的是一条长期有效的凭据链,爆炸半径比我以为的大得多——建议改成 fail-closed |
| ⚠️ 我自审漏了、被 Codex 逮到 | 一个并发竞态:另一条路径在枚举「要撤销的凭据」时,恰好漏掉了正被并发插入的新凭据。这是我完全没够到的盲区,正是换个模型才逮住的 |
| 🔶 我和 Codex 有分歧(我降级了它) | Codex 把「宽限窗本身可被利用」判为高危;但我对账后认为,这是任何宽限期设计的固有取舍(用窄窗 + 单次消费来收敛、而非消除),不是可修的缺陷。我把它从「必须修」降级为「记进威胁模型」 |
看到没——Codex 的 findings 不是判决。它一边替我清掉了最担心的疑虑,一边逮到我够不到的盲区,但也有被我按下去的高危误报。真正有价值的,是最后那一列分歧:分歧点才是高信号,值得升级给人来拍板。
6. 复盘:我到底学到了什么
绕了一大圈,从「Codex 怎么不如别人吹的神」出发,落到「两个 AI 怎么分工」。几个真的带走的东西:
-
体验差,先怀疑自己的配置。 我以为缺插件,其实是自己一句「播报下一步」把 Codex 教停了。很多「AI 不好用」是配置自伤——这个判断后来在官方 prompting guide 里找到了同源的说法(尽管它有版本适用范围,正文里我特意标了出来,不夸大)。
-
一次性 ≠ 死路,组合能力 > 单点能力。
codex exec看着是单发,但resume+ MCP 能把它接成持续协作。与其死磕「让一个 AI 更能干」,不如问「能不能让两个 AI 分工」。 -
多模型协作的价值是「去相关」,不是「叠加」。 让另一个模型审,图的不是它更聪明,而是它盲点不一样。这也决定了分工的形状:让强的(Claude)实现,让「不一样的」(Codex)审。
-
对账是关键,分歧才是宝。 AI 的审查结论不能照单全收,也不能一概忽略——逐条和自己的判断对,一致的走流程,分歧的升级给人。
-
AI 不是自动驾驶——这篇文章本身就是证据。 从「推理档位别乱调」「那个安全示例别公开」,到整套分工的拍板,全程是 sean 在踩刹车、纠偏。工具再自动,方向盘还得握在人手里。
最后留一个开放的问题给读者:如果连「审查」都能交给另一个 AI,那人在这套流程里剩下的、不可替代的那部分,到底是什么? 我的答案,就藏在第 5 节那张表的最后一行——分歧点的裁决。你的答案呢?