大概三个月前,我在做一个订单导出功能的时候,Claude Code给了我一版代码,逻辑看着没问题,单测也过了。但上线前两天,我偶然发现一个极端情况下的空指针——那段代码正是Claude自己写、自己检查、自己拍胸脯说没问题的那部分。也就是从那个时候起,我开始认真琢磨一件事:让同一个AI既当作者又当审稿人,它凭什么觉得自己没问题?于是我把另一个AI拉进了开发流,这个实践后来变成了一个叫 Claude-Codex-coop 的小项目,核心思路就一条——让 Claude 和 Codex 互相审查。今天这篇就把整个过程拆开讲:环境怎么搭、工作流怎么设计、实测效果如何、自动化怎么落地,给想试同样玩法的朋友一条能直接走的路线。
1. 为什么我会组一个“双AI审查小组”:单模型的自欺时刻
1.1 第一个让我警觉的瞬间
先还原一下那个空指针场景。当时的需求是导出订单,Claude Code 很快写完了主流程,还顺手把边界判断也做了。我让它自查,它逐行解释,最后结论是“逻辑完整,没有明显问题”。我当时也信了,毕竟每一步解释都说得通。直到我手动构造了一条状态异常的历史订单,程序在取退款时间字段时直接崩了,而那个字段恰好是上一版需求里被废弃、新模型不了解的旧字段。
问题不在Claude“不够聪明”,而在于它对自己生成的代码有天然的路径依赖。它知道当初为什么这么写,所以审查时不是在“找毛病”,而是在“解释合理性”。这个现象在心理学里叫确认偏误,放到AI身上也一样:模型倾向于维护自己已经输出的内容,而不是推翻它。
更有意思的是,当我随手把同一段代码丢给Codex看时,它第一句话就是“这个字段可能在新状态机下不存在”。那一刻我意识到,换一个AI其实不只是换一个工具,而是换了一套训练数据、一套推理习惯、一套对“好代码”的定义。两个模型彼此不了解对方写代码时的上下文,反而成了审查时最大的优点。
1.2 为什么“另一个模型”是最合适的审查者
很多人会问:我自己也能审代码,为什么要让AI来审AI?我的看法是,人工审查和AI互审是不冲突的两道防线,但AI互审解决的是“低水平重复劳动”和“惯性思维”这两个问题。
先看惯性思维。自己做技术的人都知道,自己写的东西最容易“越看越顺眼”,错别字、逻辑漏洞都会被大脑自动补全。AI也一样,它生成代码时已经建立了一套自洽的假设,自审时往往会沿着这些假设去“圆场”。而另一个模型没有这些负担,它看到的只有文件和需求文档,不知道你当初为什么选这个方案、为什么留这个TODO,所以敢于直接指出“这里不合理”。
再看效率。人工审查核心模块没问题,但每个函数都让人审不现实。双AI互审相当于先让机器把关一轮,把明显的逻辑错误、边界问题和安全隐患过滤掉,再交给人看的时候,人只需要盯业务语义和架构层面的事。而且由于Claude和Codex的模型来源不同,它们各自的盲区也不重合——比如某个算法上的偏好、对某些API用法的默认假设——交叉审查正好把两套盲区错开了。
用一句话总结就是:单AI是作者,双AI是作者加编辑。编辑不需要比作者更懂业务,但它能发现作者眼皮底下的错字和病句,这就已经值回票价了。
2. 环境搭建的硬骨头:Claude Code和Codex CLI安装配置实录
2.1 Claude Code:安装异常与登录受限的处理
Claude Code 现在的标准安装方式还是npm包,命令很简单:
npm install -g @anthropic-ai/claude-code但网上搜这个关键词,能搜出一大堆报错,其中出现频率最高的是这条:
error: claude native binary not installed. either postinstall did not run我遇到过两次。第一次是Node版本太旧,Claude Code的新版本对Node 18+有要求,旧版本环境下postinstall脚本直接没执行;第二次是npm用了自定义镜像源,导致安装过程中下载原生二进制那一步失败了。修复方法并不复杂:先确认Node版本,然后强制重装一次:
node -v npm install -g @anthropic-ai/claude-code --force如果重装完还是报错,就检查npm的registry配置,换回默认源再装。另外,实在不想全局安装,也可以用npx @anthropic-ai/claude-code直接跑,但这样每次都要重新解析包,日常用起来不如全局装舒服。
登录这块还有两个常见的坑。一个是组织类账号登录后,Claude Code可能直接报“your organization has disabled claude subscription access for claude code”。这个多半是团队管理员在后台限制了订阅使用范围,个人账号一般不会遇到。解决办法是切换到自己的账号登录,或者用API Key方式接入,后者对独立开发者来说更直接:
export ANTHROPIC_API_KEY="你的key" claude另一个是Windows环境下的老问题:启动时提示“Claude's workspace requires the virtual machine platform on windows. Enable”。Claude Code在Windows上跑代码沙箱依赖虚拟化组件,需要到“控制面板-启用或关闭Windows功能”里勾选“虚拟机平台”,重启机器后就好了。如果不想动系统设置,那就老老实实用WSL2,把Claude Code装在Linux环境里,体验会顺很多。
2.2 Codex CLI:组织设置加载失败与自定义模型接入
Codex CLI 的安装同样简单:
npm install -g @openai/codex登录时可以用ChatGPT账号,也可以用API Key。我自己用下来,日常开发建议直接codex login走账号体系,配额管理更省心;但如果要写脚本自动化调用,API Key方式更稳定,因为它不受交互登录状态影响。
登录后经常有人遇到“codex无法加载组织设置”。这个报错我仔细查过,多数情况下是Codex配置文件里缓存了失效的登录态。处理方法也比较粗暴有效:清掉~/.codex目录下的auth缓存文件,重新codex login一次。如果还是不行,检查一下当前网络环境能否正常访问官方API端点,这个问题往往出在请求被中间设备拦截,而不是Codex本身的问题。
还有一个很容易在网上搜到的报错,叫:
cc switch local proxy failed while handling codex endpoint /responses第一次看到这个错的时候我也懵了一下,后来排查发现,这条报错一般出现在你把Codex的自定义endpoint指向某个本地转发服务、但那个服务没启动或者地址写错的情况下。对策不是去折腾报错文案,而是检查三处:自定义endpoint是否可达、认证头是否正确携带、响应格式是否符合OpenAI规范。这三处理顺了,报错自然消失。
Codex CLI还有一个很多人喜欢的点:它能通过~/.codex/config.toml接入第三方模型服务。网上讨论比较多的场景是把Codex接到DeepSeek这类兼容OpenAI接口的服务上,配置思路其实是通用的,我贴一个参考配置(不同版本字段略有差异,以官方文档为准):
model = "deepseek-chat" model_provider = "deepseek" [model_providers.deepseek] name = "DeepSeek" base_url = "https://api.deepseek.com/v1" env_key = "DEEPSEEK_API_KEY" wire_api = "chat"设置好环境变量DEEPSEEK_API_KEY之后,codex就会走这个自定义提供方。这样做的实际价值在于:你可以让Codex的“审查视角”来自不同的模型底座,进一步拉开和Claude Code之间的差异性,互审效果会更好,成本也可能更低。
2.3 两个CLI共存时的配置隔离
Claude Code和Codex CLI装在一起,默认是不冲突的。Claude的配置在~/.claude,Codex的配置在~/.codex,目录天然隔离。真正容易出问题的是环境变量。
我一开始图省事,把ANTHROPIC_API_KEY和OPENAI_API_KEY一起写进了.bashrc,结果Claude Code进程偶尔会读到一个被污染的环境,虽然多数时候没事,但出了错特别难排查。后来我的做法是写一个.env文件,在需要运行某个CLI的终端里手动加载:
# 跑Claude相关任务 source .env.claude export ANTHROPIC_API_KEY=xxx unalias claude 2>/dev/null || true # 跑Codex相关任务 source .env.codex export OPENAI_API_KEY=yyy另外,这两个CLI默认都能读写文件、能执行命令,权限相当大。如果只是做互审实验,建议把所有工作文件放进一个专门的项目目录里,并且不要把生产环境的凭证放到工作目录里。AI能读到的秘密,就等于你亲手交出去的秘密,这个习惯无论对哪个AI工具都适用。
3. Claude-Codex-coop工作流:作者、审查者、仲裁者三个角色怎么转
3.1 角色分配:谁先写、谁来审、如何交替
这个项目的核心不是“同时开两个窗口各问一句”,而是要让两个模型在一个明确的角色分工下协作。默认工作流我设计为:Claude Code先写实现,Codex做第一轮审查,然后根据审查报告让Claude修复,改完再由Codex复核。一轮结束后换位,Codex写新功能,Claude来审。
为什么要交替?因为如果长期让Claude写、Codex审,Claude会慢慢形成“反正有人兜底”的随手风格,审查者也会固定成一套挑刺模式,时间长了两个模型可能相互适应,让审查流于形式。交替之后,双方都要体验“被挑刺”的过程,反而会促使它们在写代码时更谨慎。
实际跑下来,更合理的做法不是所有代码都互审,而是按风险等级选模块。我的分级标准是:
- 必审:数据写入逻辑、权限校验、支付金额计算、状态机迁移
- 选审:新增接口、数据库查询、并发处理
- 跳过:UI文案、纯格式转换、样板代码
这样既保证关键路径的可靠性,又不会让token消耗和等待时间变得不可接受。
当两个AI意见不一致时,需要一个仲裁机制。我的做法是:把作者代码、审查报告、双方争议点打包,交给其中一方做“法官”角色,让它读完整上下文后给出裁决;如果仲裁结果和初始审查方向相反,我再人工看一眼。这个机制下,两个AI会互相约束,不太容易出现一边倒的错误结论。
3.2 用文件系统做上下文中转:需求文档、代码快照、审查报告
很多人在用AI协作时喜欢直接把上一轮的对话文本复制粘贴到另一个工具的对话框里,这种操作在单轮实验没问题,但一旦进入多轮迭代,就会遇到上下文爆炸、格式错乱、找不到历史版本的问题。Claude-Codex-coop的解决方案很土但很稳:用文件系统做中转站。
典型的目录结构长这样:
project/ docs/ requirements.md change_log.md generated/ impl.py impl_v2.py reviews/ review_by_codex.md review_by_claude.md关键是几个文件各司其职。requirements.md是写给AI看的需求说明,里面不能只有一句“做一个订单导出”,而是要包含验收标准、边界条件、禁止事项。我第一次写需求文档时只写了功能描述,结果Claude生成的代码功能全对但边界全漏。后来我强制自己在文档里加一段“边界与例外”清单,比如“当订单状态为已取消时,不生成退款链接”“时间字段一律使用ISO 8601格式”,审查质量立刻上了一个台阶。
change_log.md是作者AI在完成修改后生成的一个简短说明,写清楚自己改了什么、为什么改、改了哪些文件。这个文件对审查者极其重要,因为审查者在没有上下文的情况下,很难区分“这是故意的设计”和“这是不小心的疏漏”。有了change log,审查者就能聚焦在“改动是否合理”而不是“为什么这里和昨天不一样”。
审查报告由审查方负责输出,格式我固定为四条:严重级别(P0致命/P1严重/P2建议)、文件与行号、问题描述、最小修复建议。这两条约定下来之后,后续的修复循环和自动化解析都变得非常简单。
3.3 审查提示词的关键设计:逼AI说“不好”
工具链就位后,能不能审出真问题,很大程度取决于审查提示词。经验是:你越允许AI夸你,它越会夸你;你越逼它挑刺,它越能挑出刺来。
我使用的审查提示词模板如下:
你是一名严格的代码审查者。你的任务只有一个:找出问题。 不需要称赞任何设计,不要输出“整体实现良好”这类话。 请阅读 requirements.md 和 change_log.md,对照 impl.py 逐项检查: 1. 是否存在需求未覆盖的边界条件 2. 是否存在可能导致异常退出或数据损坏的错误处理缺失 3. 是否存在安全风险(注入、越权、敏感数据泄露等) 4. 是否存在明显的性能问题或资源泄漏 5. 是否存在命名混乱或逻辑冗余 按要求输出审查报告,每个问题都必须给出: - 严重级别:P0 / P1 / P2 - 文件与行号 - 问题描述 - 最小修复建议 如果没有任何P0/P1问题,仍必须给出至少两个P2级改进建议。“不需要称赞”“必须给出至少两个建议”这两句是关键。没有这两句的时候,审查报告经常出现“代码整体质量不错,可以合并”这种毫无价值的结论;加上之后,审查者被迫进入找茬模式,哪怕代码确实没大问题,也会给出具体可操作的改进点。
另一个高阶技巧是:让审查者先复述需求,再对照代码。在提示词里加一句“在审查前,先用100字以内复述你对需求的完整理解”,能强制审查者把注意力拉回到“代码是否符合需求”而不是“代码是否自洽”。这个方法在发现“AI和AI之间理解偏差”时特别有效,有一种情况是两个模型各说各话,最后发现它们对同一句需求的理解根本不一致,而复述机制能早早暴露这个问题。
4. 实测复盘:双AI互审到底抓出了哪些真问题,又漏掉了什么
4.1 三个真实抓出bug的案例
这套工作流跑了一个多月,我整理了几个典型的实战案例,都是真实抓到过问题、并且成功在测试环境里复现的,写出来给大家做个参考。
第一个案例是缓存过期判断。当时Claude Code实现了一个带过期时间的缓存工具类,逻辑看起来很简单:存入时记录expire_at,读取时比较当前时间。Codex审查时指出,代码里比较用的是“秒”,但写入expire_at时用的是“毫秒时间戳”,两个单位混用会导致缓存永远不命中或者永不过期。这个问题单看代码很难发现,因为两处代码在不同函数里,各自都说得通,编译也能通过,但运行时行为完全错了。属于典型的单位不一致Bug,人眼容易看漏,机器反而容易抓住。
第二个案例来自Codex生成的一个文件导出接口。它实现了鉴权逻辑,但Claude审查时发现,鉴权token只在接口初始化时读取了一次,没有做定期刷新;一旦token过期,接口就会抛401,而且没有重试机制。表面上看这个接口的功能是“导出”,鉴权是附属逻辑,但实际运行中token轮换是常态,这个坑非常隐蔽。Claude给出的修复建议也很具体:把token获取放进请求拦截器里,每次调用前检查有效期。
第三个案例是个性能问题。Claude实现了一个用户列表查询接口,查询本身很快,但Codex注意到它对每条记录都发了一次额外的数据库查询,典型的N+1问题。在100条数据时性能尚可,到了10000条就会直接超时。审查报告里甚至标出了具体行号,修复起来几乎零成本。这类问题让我确定了一点:AI互审在“逻辑正确但性能隐患”的发现上,比人肉review要稳得多。
这些案例的共同点在于:问题都在“两行代码之间的隐含关系”上,作者写的时候不会觉得有问题,人审的时候容易跳过,但另一个AI从完全不同的角度切入,往往一眼就能看到矛盾。
| 问题描述 | 发现方 | 严重级别 | 修复成本 |
|---|---|---|---|
| 缓存过期时间单位混用,秒与毫秒不一致 | Codex审查Claude代码 | P1 | 一行改动 |
| 文件导出鉴权token未刷新,过期即失败 | Claude审查Codex代码 | P1 | 重构鉴权调用 |
| 用户列表查询存在N+1,数据量增大后超时 | Codex审查Claude代码 | P2 | 批量查询替换 |
4.2 它们俩都点头、结果还是出错的场景
双AI互审也远不是万能的。我踩过最深刻的一个坑,是财务相关的金额处理。
当时需求文档要求“金额保留两位小数”,Claude和Codex在这个表述上没有任何分歧,代码也写得干净利落。但上线后对账时发现,有一笔订单的金额差了0.01元。原因是业务上要求的是“四舍五入到分”,但会计合规场景更合适的做法是“银行家舍入”,也就是逢五不总进、看前一位奇偶。我们的需求文档没有写明舍入规则,两个AI自然都按普通四舍五入实现。它们双双通过审查,因为代码本身的逻辑确实是对的,问题出在需求描述和真实业务规则之间的缝隙里。
第二个漏网之鱼是并发竞态。一个库存扣减逻辑,两轮互审都通过了,压测时偶发超卖。深究原因,问题出在“先查后改”这个操作序列上,两个AI在单线程视角下都没看出问题,但并发场景下需要原子操作。后来的解决办法是在审查提示词里加了一条:“如果代码涉及共享状态修改,额外检查并发安全性。”即便如此,这也只能降低概率,不能完全消除。
这两次经验让我得出了一个重要结论:AI互审解决的是“代码和需求的一致性”以及“通用工程缺陷”,但它解决不了“需求本身是否反映了真实世界”这件事。所以在后续的审查提示词中,我加了一条:“如果发现需求文档本身有歧义或可疑之处,以P1级别标注并直接提问,不要埋头实现。”这一步看起来简单,实际上把审查边界推进到了需求层面。
4.3 成本和时间的实测数据
说一次互审到底烧多少token、花多少时间,数据来自我的实际环境:一个300行左右的Python模块,需求文档600字,只审查这一个文件。
Claude Code生成实现的消耗大约在8000 token左右,Codex审查同样规模的代码大约在9000 token左右,一轮往返合计约17000 token。按API按量付费来算,单轮成本折合人民币不到几块钱;如果用的是订阅套餐,这部分成本就更加可以忽略。时间上,命令行非交互模式下,一次“写出来+审一遍”的完整流程大约要3到5分钟,如果中间要修复再复审,再加一轮。
这个成本在我看来完全可以接受,尤其是对比它带来的收益——一次互审能拦截掉的可能就是上线事故,而事故的修复成本和社会损失远高于这点token开销。
当然,成本也不是没有优化的空间。我在项目后期做了一个很实用的调整:只审diff,不审全文件。改动10行就只把改动部分和紧邻的上下文交给审查者,而不是把整个5000行项目都塞进去。这样既控制token,又加快速度,同时还能提升审查的专注度,因为审查者不会被无关代码干扰。
5. 把互审流程自动化:CLI脚本串联与后续扩展
5.1 一个可以直接抄走的互审脚本
互审如果全靠手动敲命令,一次两次还能接受,日常开发效率就太低了。所以我把这套工作流压成了一个bash脚本,核心逻辑是先让Claude生成实现,再让Codex审查,发现P0/P1问题就循环修复,直到通过或达到最大轮数。
#!/bin/bash # coop-review.sh - Claude写,Codex审,自动迭代 set -u PROJ_DIR="${1:-.}" REQ="$PROJ_DIR/docs/requirements.md" IMPL="$PROJ_DIR/generated/impl.py" REVIEW="$PROJ_DIR/reviews/review_by_codex.md" MAX_ROUNDS="${2:-3}" # 第1步:Claude Code生成实现 echo ">> [1/$MAX_ROUNDS] Claude Code 开始实现..." claude -p "阅读 $REQ,将实现写入 $IMPL,并在 docs/change_log.md 记录你的设计决策;只输出结果摘要。" --output-format text # 第2步:循环审查-修复 for (( round=1; round<=MAX_ROUNDS; round++ )); do echo ">> 第 ${round} 轮审查开始..." codex exec --full-auto "阅读 $REQ、$IMPL 和 docs/change_log.md,按模板输出审查报告到 $REVIEW;不要称赞代码,只列问题。" if grep -qE "P0|P1" "$REVIEW"; then echo ">> 发现 P0/P1 问题,Claude 开始修复..." claude -p "阅读 $REVIEW 中所有 P0/P1 问题,修复 $IMPL,不要改动其他逻辑;修复后更新 change_log.md,并说明每个问题如何解决。" --output-format text else echo ">> 未发现 P0/P1 问题,流程结束。" break fi done echo ">> 互审完成,最终报告:$REVIEW"几个细节要注意:set -u而不是直接set -e,因为grep -q在没匹配到关键词时返回非零,如果开着set -e,脚本就会在中途退出,没法走到“未发现问题”的分支。MAX_ROUNDS一定要限制,否则模型可能会陷入无穷尽的“修复-再报错-再修复”循环,把token烧光。
跑之前建议先在工作目录里准备好docs/requirements.md,并确保Claude Code和Codex CLI都能在非交互模式下正常运行。第一次跑的时候,有的环境会提示权限确认,先手动跑一次claude -p "test"和codex exec "test"验证一下。
这个脚本虽然简陋,但已经是完整的“写-审-修-复审”闭环了。我后来在多个小模块上反复跑过,稳定性和结果的一致性都很好,基本可以作为日常开发的一个固定工序。
5.2 后续扩展方向:接CI、PR机器人、多模型仲裁
脚本跑通之后,能做的事情就多了。我目前正在尝试的几个扩展方向,写出来给有同样需求的朋友参考。
第一个方向是接入CI。把互审脚本放到GitHub Actions里,每次有新提交就自动跑一轮“写-审”闭环,审查报告作为构建产物留存。这样代码评审不再是开发完成后的事后诸葛,而是每时每刻都在进行的。需要注意的地方是CI环境里要保护好API Key,用GitHub Secrets存储,别直接写进脚本。
第二个方向是做成PR评论机器人。拿到Pull Request的diff文件,喂给Codex生成审查意见,再把格式化的评论通过GitHub API回贴到PR页面上。这个的价值在于让所有参与人都能看到AI的审查意见,而不是只有跑脚本的那个人知道。技术难度不大,主要是解析diff格式和调用API的粘合代码。
第三个方向是多模型仲裁。目前是Claude和Codex两方互审,遇到分歧时由其中一方再当法官。后续我想引入第三个模型做中立仲裁,两个模型的审查报告都给它看,让它输出最终裁决。这个方向理论上能把双AI互审的盲区再缩小一圈,代价是token成本会线性上升。根据我的实测,只有当项目进入核心模块的重构阶段时,这个成本才值得掏。
第四个方向是沉淀审查数据。每一次互审产生的审查报告、修复记录、最终通过的代码都是很好的语料。可以定期统计“哪个模型更常抓到哪种类型的问题”“哪类问题两个模型都总是漏掉”,然后反向改进提示词。这套数据驱动的思路,越攒越值钱。
写在最后的实操体会
这套双AI互审流程跑下来,我最深的感触是:它不会让代码自动变得完美,但会非常有效地把“低级错误”和“惯性盲区”挡在上线之前。单模型写代码就像一个人赶稿,越写越顺也越写越飘;双模型互审就像有个编辑坐在对面,看见你把人名写错会立刻拍桌子,但它也只能管到你写出来的那些字,管不到你选题本身有没有问题。所以别把互审当成银弹,把它当成一道低成本高性价比的质量闸门就对了。
如果你也想试,建议从一个小模块开始,先手动跑两轮感受一下两个模型的风格差异,再上脚本自动化。等脚本稳定了,再慢慢覆盖更多核心代码。整个过程不需要一次到位,但跑起来之后,你会明显感觉到“写代码”这件事的节奏变了——从一脚油门踩到底,变成了边开边看后视镜。这个变化,我觉得是值得的。