去年我在重构一个内部数据处理工具时,连续三天被同一个问题坑:Claude Code 写出了逻辑没错但风格混乱的代码,我问它“这里是不是有 bug”,它一口咬定没有。换我问 Codex,它在另一个方向犯迷糊。后来我发现,单靠一个 AI 编程代理,就像让作者自己审自己的稿子——句子通不通顺他清楚,但结构性的盲区他看不见。于是我把 Claude Code 和 Codex 这两个命令行 Agent 串了起来,让它们互相审查对方的产出。这套“Claude-Codex-coop”协作流程,就是今天想跟你分享的东西。
这套方案解决的核心问题很直接:当一个 AI 独立完成编码任务时,错误往往会在它的“舒适区”里被反复确认,而不是被纠正。两个不同训练来源、不同归纳偏好的模型互相做 reviewer,相当于给代码上了两道不同视角的质检线。适合谁用?适合那些已经在用 Claude Code 或 Codex 写真实项目、却对产出质量不够放心的人,也适合对多 Agent 协作工作流感兴趣的技术爱好者。你不需要很深的机器学习背景,只要会装 npm 包、会写 shell 脚本,就能把这套东西跑起来。
1. 为什么“双 AI 互审”比单 AI 靠谱
1.1 单一 agent 的“确认偏误”陷阱
先说一个很多人没意识到的事实:大模型代码生成出错,往往不是随机错误,而是沿着模型的“归纳偏好”系统性出错。什么意思呢?Claude 可能偏爱把逻辑拆得很碎、变量命名偏长,它在生成这类代码时很流畅,但正因为流畅,它对自己的产出缺乏警觉。当它回顾自己写的代码时,它倾向于“顺着自己熟悉的思路再解释一遍”,而不是“从一个陌生 reviewer 的角度挑刺”。这种自审,本质上是一种自我确认,抓不到真正的结构性问题。
我在实践中反复观察到:Claude Code 写一个函数时,如果它在“边界条件处理”上本来就不擅长,那让它自己检查“边界条件有没有问题”,它大概率会回答“没问题”。这不是模型“笨”,而是它没有另一个归纳偏好作为参照系。Codex 也是一样,OpenAI 系模型对某些模式有偏好,对另一些模式有盲区。人写代码尚且要 code review,AI 写代码凭什么不需要?
1.2 异质模型交叉验证的价值
那为什么“两个 AI 互审”能见效,而不是两个 AI 一起把错误确认两遍?关键在于模型的异质性。Claude 的训练数据分布、指令跟随方式、对代码风格的偏好,和 Codex 有明显差异。一个模型觉得“自然”的写法,在另一个模型眼里可能就是“可疑”的。这在效果上等价于:你不是让同一个作者改自己的稿子,而是让一个风格完全不同的编辑来审。
我自己的体验是,交叉审查能抓出的问题集中在三类:第一类,逻辑边界缺失,比如空列表、None 值、超大输入没有兜底;第二类,资源管理问题,比如文件句柄未关闭、连接池未释放;第三类,接口契约不一致,比如函数签名和调用方的预期不匹配。这些问题有一个共同特点——单靠模型“内部知识”很难发现,必须有一个外部视角,真正去看“这个函数的输入输出是否自洽”。两个模型互审,恰好提供了这个外部视角。
1.3 什么项目值得用 coop,什么不值得
也不是所有任务都值得上互审。我的经验是:临时脚本、一次性数据清洗、示例代码,单 agent 就够了,互审反而浪费时间。值得上这套流程的,是满足下面至少两条的项目:
- 代码会被后续维护,生命周期超过一个月
- 逻辑复杂,边界条件多,比如支付、权限、并发处理
- 多模块协作,接口契约需要严格对齐
- 你要交付给别人用,而不是自己跑一次就扔掉
按这个标准筛选,互审的投入产出比会高很多。我平时大概有三分之一的项目走 coop,剩下的用单 agent 快速出活。
2. 环境准备:把两个命令行 Agent 跑起来
2.1 Claude Code 安装与常见报错处理
Claude Code 的安装本身不复杂,用 npm 全局安装就行:
npm install -g @anthropic-ai/claude-code装完直接输入claude进入交互模式,首次运行会让你登录账号并选择订阅计划。安装过程中我踩过一个高频坑,就是报错信息里带着claude native binary not installed. either postinstall did not run。这个问题的本质是 npm 安装后的 postinstall 脚本没有正常执行,导致 CLI 的核心二进制没落盘。处理办法有两个:一是重新执行一次构建,二是强制重装。
npm rebuild @anthropic-ai/claude-code # 仍然不行的话 npm uninstall -g @anthropic-ai/claude-code npm install -g @anthropic-ai/claude-code --force还有一个 Windows 下容易撞上的坑:启动时提示 workspace 需要开启虚拟机平台。Claude Code 在 Windows 上依赖虚拟化环境来跑沙箱,所以你得先在“启用或关闭 Windows 功能”里勾选“虚拟机平台”和“适用于 Linux 的 Windows 子系统”,然后重启。如果你已经在用 WSL2,这个环境通常已经就绪。装好之后,先在交互模式里跑一个最简单的任务,确认端到端通畅,再往下搭互审环境。
2.2 Codex 安装、登录与远端/本地模型切换
Codex 的安装同样是 npm 全局包:
npm install -g @openai/codex codex login登录流程会走浏览器授权,之后 CLI 会保存会话凭证。如果你不想用默认的云端服务,Codex 也支持通过环境变量把接口指向别的大模型服务。这里有个常见需求:有人想把 Codex 接到 DeepSeek 这类模型上,做法是设置接口地址和密钥,模型名选对方支持的 OpenAI 兼容模型。还有人想在 Claude Code 里调用本地模型,比如通过 LM Studio 起一个兼容 OpenAI 协议的服务,然后设置ANTHROPIC_BASE_URL指向本地端点,再配一个ANTHROPIC_API_KEY。
export ANTHROPIC_BASE_URL=http://localhost:1234/v1 export ANTHROPIC_API_KEY=local-key注意:走本地模型或第三方模型时,互审的质量会随模型能力明显波动。本地小模型的审查深度和闭源旗舰级模型不能比,用来跑流程演示可以,用在真实项目上要谨慎。
Codex 也有桌面版客户端,如果你更习惯图形界面,可以装桌面版。不过互审流程需要命令行可控,所以我建议桌面版只用来浏览会话,实际操作还是走 CLI。
2.3 VSCode 集成与工作目录规划
我常用的方式是 VSCode 里开两个终端,一个跑 Claude Code,一个跑 Codex,配合一个固定项目目录。VSCode 的集成终端可以给每个终端起别名,我习惯叫“term-claude”和“term-codex”。这样切来切去时不会跑错会话。
工作目录规划上,不要直接让两个 agent 在同一堆文件里乱写。我一开始就是两个 agent 共用同一目录,结果互相覆盖文件,审查报告也被对方当源码读,乱成一锅粥。后来我改成按“分区 + 评审区”的结构,效果好很多:
coop-project/ ├── stage-a/ # Claude 的主场,写代码在这里 ├── stage-b/ # Codex 的主场,写代码在这里 ├── review/ # 互审报告的存放区 └── prompts/ # 评审指令模板这个结构有讲究:每个 agent 在自己的“主场”里写代码,不会被对方干扰;审查报告单独放一个目录,避免被误当源码;prompts 目录统一管理评审模板,方便迭代。
3. coop 设计:仓库拆分、互审流程与脚本实现
3.1 双 Agent 交替工作的状态机
coop 的核心不是“两个 AI 同时干活”,而是“两个 AI 交替干活,一个写、一个审、再反审”。我把这个流程理解为一个状态机:
- 状态 A:Claude 在 stage-a 里实现功能
- 状态 B:Codex 读取 stage-a 的代码和需求,输出 review 报告
- 状态 C:Claude 根据 review 报告修改代码
- 状态 D:Codex 复核修改结果,决定是否通过
- 下一轮:角色对调,Codex 写,Claude 审
为什么要角色对调?因为审查能力也需要被验证。如果永远只有 Codex 审 Claude 的代码,那 Codex 的“审查盲区”就永远暴露不出来。对调之后,两边既写过也审过,对彼此的工作模式有更完整的理解。实操中,我通常让两个 agent 分别完成不同模块,各自在对方模块上做审查,而不是在同一个文件上反复横跳。
3.2 互审脚本:用 Bash 把两个人串起来
互审流程如果全靠手动复制粘贴 prompt,效率太低。我写了一个简单的 Bash 脚本,核心逻辑是把“审查指令 + 目标文件 + 需求描述”拼成一段上下文,然后调用对应的 CLI。
#!/usr/bin/env bash # coop-review.sh # 用法: ./coop-review.sh <claude|codex> <目标文件> <需求描述文件> reviewer="$1" target_file="$2" requirement_file="$3" # 读取需求描述 requirement=$(cat "$requirement_file") # 拼接审查指令 prompt="你是一名独立的代码审查员。请审查以下文件:${target_file} 需求描述:${requirement} 审查重点: 1. 功能正确性:实现是否满足需求 2. 边界条件:空输入、异常输入、极端情况 3. 资源与安全:文件句柄、网络请求、内存使用 4. 可维护性:命名、结构、注释 输出格式: - 结论:通过 / 需修改 - 问题清单:每项标注[严重]/[一般]/[建议],包含文件位置、原因、修改建议 - 值得肯定的点:最多3条" if [ "$reviewer" = "claude" ]; then claude -p "$prompt" \ --allowedTools "Read,Glob,Grep,Bash" \ --permission-mode plan else codex exec --full-auto "$prompt" \ --sandbox read-only fi注意脚本里两个关键细节:第一,我限制了审查者的工具权限,只允许读文件、搜索、看目录,不允许直接改文件。审查者一旦能改代码,就容易“顺手修一下”,这样会把“审查报告”和“修复动作”混在一起,失去互审的意义。第二,我用--permission-mode plan(Claude 侧)和--sandbox read-only(Codex 侧)来强制只读。这就是互审和普通对话式编码的关键区别——审查者只负责输出判断,修不修、怎么修,由写代码的那个 agent 决定。
3.3 审查 prompt 模板:把“挑刺偏好”显式化
互审的效果高度依赖审查指令的质量。我最早用的 prompt 是“请审查这个文件有没有问题”,结果两个模型都不约而同地回“没有明显问题”——因为它们都不知道“问题”的标准是什么。后来我把审查标准显式化,模板稳定成下面这个样子:
你是代码审查员。你的任务是找出代码中会影响正确性和可维护性的问题。 请按照以下优先级审查: 1. 正确性:逻辑错误、错误处理缺失、并发/重复执行风险 2. 边界:空输入、超长输入、特殊字符、文件不存在、网络超时 3. 资源:句柄释放、连接关闭、内存占用 4. 可维护性:命名清晰度、函数长度、重复代码、魔术数字 禁止做的事: - 不要改动代码 - 不要停留在“建议优化性能”层面,除非有具体证据 - 不要因为风格差异而扣分 一个问题如果无法给出具体文件位置和触发条件,就不算有效问题。这个模板的核心是“禁止空洞问题”。AI 特别擅长提“可以考虑进一步优化性能”“建议增强代码健壮性”这类废话。模板里明确要求“无法给出触发条件就不算有效问题”,能过滤掉绝大多数套话,逼着审查模型给出可复现的 bug 场景。
4. 实操全过程:从“写”到“审”的完整闭环
4.1 演示项目:一个 JSON 批量清洗工具
为了把流程讲明白,我用一个实际跑过的例子来说明。项目需求:写一个 Python 命令行工具,输入一个 JSON 数组文件,输出清洗后的新文件。清洗规则包括:删除空字段、把字符串类型的数字转成数字类型、统一日期格式。文件可能很大,需要考虑逐行处理。
这个任务看起来简单,但它有足够的边界条件(空文件、非法 JSON、超大文件、字段类型不一致),非常适合演示互审的价值。
4.2 第一轮:Claude 写,Codex 审
我先让 Claude Code 在 stage-a 里实现这个工具。它生成的初版代码核心逻辑是这样:
import json import re import sys def clean_json(input_path, output_path): with open(input_path) as f: data = json.load(f) for item in data: for key in list(item.keys()): if item[key] is None or item[key] == "": del item[key] elif isinstance(item[key], str): if re.fullmatch(r"\d+", item[key]): item[key] = int(item[key]) elif re.fullmatch(r"\d{4}-\d{2}-\d{2}", item[key]): item[key] = item[key] with open(output_path, "w") as f: json.dump(data, f, ensure_ascii=False, indent=2) if __name__ == "__main__": clean_json(sys.argv[1], sys.argv[2])代码能跑,但问题一抓一把。我把需求描述和这个文件传给 Codex 执行审查,Codex 输出的报告要点如下:
结论:需修改 [严重] clean_json 第 12 行:变量 file_path 拼写不一致 实际参数名是 input_path,第 12 行却写成了 file_path,会导致 NameError。 [严重] clean_json 第 3 行:json.load 一次性加载全部数据 需求提到文件可能很大,一次性加载会导致内存爆炸。 [一般] clean_json 第 8 行:打开文件未指定 encoding 在非 UTF-8 默认编码的环境下,读取会乱码或抛异常。 [一般] clean_json 第 18 行:日期格式统一逻辑为空操作 elif 分支只是把字符串重新赋值为自身,没有真正统一格式。 [建议] 删除字段后没有记录字段名,不利于数据质量追踪这份报告里最让我意外的是第一条——Claude 在遍历item.keys()时删除了item[key],但实际上代码里用的是input_path参数,并没有file_path这个变量。这种低级错误,让 Claude 自己检查,它大概率会认为是“没问题”的,因为代码里确实写的是input_path,而错误发生在它最初构思时的变量命名残留。这种“书写时的手滑”,只有外部审查者能发现。
4.3 第二轮:Codex 修,Claude 反审
拿到审查报告后,我让 Codex 在 stage-b 里重写这个工具。Codex 版本:
import json import sys from datetime import datetime def clean_json(input_path, output_path, error_log_path="errors.log"): errors = [] cleaned = [] with open(input_path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue try: item = json.loads(line) except json.JSONDecodeError as e: errors.append(f"行 {line}: {e}") continue for key in list(item.keys()): value = item[key] if value is None or value == "": item.pop(key) continue if isinstance(value, str): if value.isdigit(): item[key] = int(value) else: try: item[key] = datetime.strptime(value, "%Y-%m-%d").date().isoformat() except ValueError: pass cleaned.append(item) with open(output_path, "w", encoding="utf-8") as f: json.dump(cleaned, f, ensure_ascii=False, indent=2) if errors: with open(error_log_path, "w", encoding="utf-8") as f: f.write("\n".join(errors)) if __name__ == "__main__": clean_json(sys.argv[1], sys.argv[2])这个版本明显吸收了审查意见:逐行处理、指定 encoding、错误日志、日期统一都实现了。但注意,它把输入格式从“数组 JSON 文件”改成了“每行一个 JSON 对象”的 NDJSON 格式——这在某种程度上改变了需求语义。
这时候让 Claude 做反审。Claude 的输出:
结论:需修改 [严重] clean_json 第 12 行:输入格式假设发生变化 需求是“JSON 数组文件”,但逐行读取的假设将整个文件按 NDJSON 格式解析。 一个合法的 JSON 数组文件会被解析成 0 个对象,导致输出为空数组。 建议改为 json.load 后遍历数组,或明确需求变更为 NDJSON。 [一般] 错误日志写入时没有考虑日志目录不存在的情况 [一般] 日期解析失败时静默忽略,建议记录到 errors 列表你看,互审的“互”字在这里真正体现出来了:Codex 修正了 Claude 的问题,但它自己引入了新的需求偏差;Claude 作为反审者,准确地抓住了这个偏差。如果只有 Codex 一人在场,这个“JSON 数组被当 NDJSON 解析”的问题很可能带着上线。
4.4 参数与节奏调优:让别人干活,但别放养
互审流程跑顺之后,我总结出几个调优要点。
第一,审查工具的权限必须从紧,不要给写权限。我见过有人为了让审查者“更自由”而放开 Edit 权限,结果审查者直接把代码改了,审查报告变成了一堆 commit message。互审的价值在于“分开看”,混在一起就退化成单 agent 了。
第二,上下文长度要控制。Claude Code 和 Codex 都有上下文窗口上限,如果审查者一次读太多文件,就容易把较早的结论“忘掉”。我的经验是:一次审查只针对一个文件或一个模块,不要同时让审查者读十个文件。项目大了,就让审查者按模块分批审,每批输出一份报告。
第三,审查阈值要设好。如果让审查者“尽量多找问题”,两个模型会陷入挑刺循环,连变量名风格都拿来当问题说。我在模板里限定“最高优先级 3 个问题”,反而让报告更有可操作性。低优先级问题不是不说,而是单独放在“建议”区块,不让它干扰主线。
5. 常见问题与排查技巧实录
5.1 高频安装与运行问题速查表
我在搭这套环境时踩过不少坑,整理成一张速查表,方便你直接对照:
| 现象 | 原因 | 处理 |
|---|---|---|
claude native binary not installed | npm postinstall 未执行 | npm rebuild或强制重装 |
| Windows 启动 Claude Code 报虚拟化相关提示 | 虚拟机平台未启用 | 启用 Windows 虚拟机平台功能,或在 WSL2 里运行 |
| 登录时提示组织禁用 Claude 订阅访问 | 企业策略限制 | 换个人 API Key,或把服务指向本地/第三方模型端点 |
Codex 接口返回/responses路径错误 | 端点配置不正确或服务不兼容 | 检查 base URL 和模型名是否匹配 |
| 两个 agent 同时操作同一文件 | 目录规划不当 | 严格区分 stage-a / stage-b |
| 审查报告像 AI 套话,没有具体问题 | prompt 没有限定格式 | 强制要求“必须给出触发条件” |
| 二次审查时 agent 忘了上一轮结论 | 上下文太长被裁剪 | 单文件审查,报告喂回去 |
| 互审陷入无限挑刺 | 没有设置优先级阈值 | 限最高优先级问题数 |
5.2 端点配置与模型切换的排查思路
有一个容易让人懵的情况:Codex 把接口指向第三方大模型服务后,请求时报出跟/responses路径相关的错误。这类问题十有八九不是“代码写错了”,而是端点和协议不完全兼容。排查思路按顺序走:先确认环境变量里的 base URL 有没有写对,末尾的/v1是常见遗忘点;再确认模型名在目标服务里真实存在;最后看返回体的结构是不是 OpenAI 兼容格式——有些服务提供了 OpenAI 兼容接口,但字段不完整。
注意:任何环境变量改动后,都要新开终端重新加载,CLI 不会自动重新读取。我因为这个浪费过不少时间。
5.3 两个 agent 互相“认错人”的问题
还有一件有意思的事——当你同时开两个 agent 会话时,偶尔会出现“身份漂移”。Claude Code 会突然用 OpenAI 模型的口吻回答,Codex 会冒出 Anthropic 风格的长篇分析。原因通常是 prompt 里没有明确“你是谁”。在互审指令里加上“你是一名独立的代码审查员,不代表任何特定模型厂商”这句话,能有效降低这种串味现象。这听起来像个玄学,但实际测下来确实有用。
5.4 递进式互审:把上一轮报告喂给下一轮
最后分享一个我越用越顺手的技巧:不要每次都从零开始让审查者读代码,而是把它变成递进式审查。第一轮审查报告出来后,下一轮修复时,把报告也喂给写代码的 agent,让它带着“上一轮的问题清单”去修。这样可以避免修完 A 问题引入 B 问题,而且能让两个 agent 在一轮轮交互中逐渐对齐对“项目契约”的理解。
我在实际使用中发现最有效的方式是在 prompts 目录下留存每一轮的报告,文件名按review-round-01.md、review-round-02.md递增。到第三轮以后,agent 输出的代码质量会明显稳定下来,审查报告里“建议”类的内容占比上升,“严重”类的问题大幅减少。这个过程,像极了真人团队里磨合几轮之后的默契——AI 之间也会形成这种默契,前提是你给它们建立好反馈回路。