做了多年 AI 编程工具落地,我最大的感受是:单个 CLI 再强,也只是“一个人干活”。真正贴近团队协作时,Claude Code 负责全局重构和方案设计,Codex 负责快速写测试和命令执行,两个工具各有长板。问题在于,它们默认各开各的 session,互相看不到对方的上下文和任务进度,于是很容易出现“Claude 改完代码,Codex 不知道”“Codex 跑完测试,Claude 不知道结果”的割裂感。
本文介绍一套本地双向桥接方案:不引入复杂服务端,只用文件协议 + 少量脚本,让 Claude Code 和 Codex 在同一个仓库里交换任务、同步状态、共享交接上下文。适合已经装了 Claude Code 和 Codex CLI、希望让两个 Agent 协作的开发者。读完你能理解桥接原理,拿到可复制脚本,也能照着跑通一个“Claude 写模块、Codex 补测试”的完整案例。
1. 为什么需要双向桥接:Claude Code 与 Codex 的定位差异
1.1 Claude Code 更适合什么
Claude Code 是 Anthropic 推出的命令行 AI 编程助手。它以“代理式(Agentic)”方式工作,可以读取整个仓库、执行命令、修改文件、运行测试,并在多轮工具调用中自主推进任务。
在实际项目中,Claude Code 的优势主要体现在三点:
- 长上下文与全局感知:在大仓库里,它能同时理解多个模块的依赖关系,适合做重构、跨文件变更、架构调整。
- 规划能力强:让它“先分析再动手”,它通常会先给出方案,再逐步修改代码并验证结果。
- 生态丰富:支持 CLAUDE.md 作为仓库级指令文件,支持 Hooks、Subagents、Skills 等机制,便于把团队规范注入到 AI 工作流中。
这些特点决定了它更适合做“脑力活”:拆解需求、设计接口、处理复杂重构。
1.2 Codex 更适合什么
Codex 是 OpenAI 推出的命令行编程 Agent。它提供交互式会话和codex exec非交互执行模式,能读取仓库、执行命令、生成 patch,并且支持通过config.toml进行沙箱和权限配置。
Codex 的强项在于“手快”:
- 单文件或小范围任务效率高:写测试、修 bug、补文档、执行格式化等局部工作,交给它通常很快。
- 命令执行方便:
codex exec可以直接跑 shell 命令,适合做“读文件、改文件、跑测试、返回结果”这种线性流程。 - 与 IDE 生态集成好:很多开发者把它配置在编辑器里做增量任务。
1.3 桥接到底在解决什么问题
很多人以为“双向桥接”是把两个模型像插件一样拼到一起,让它们实时对话。其实更务实的做法是:让两个 CLI 共享一个任务交换层,把“上下文、任务指令、执行结果”以文件形式同步。
这套方案解决三个具体问题:
- 上下文不丢失:Claude 改到一半,把任务和文件范围写到交接文件,Codex 一读就知道要干什么。
- 状态可追溯:每次交接都有任务 ID、时间戳、结果记录,不会出现“不知道谁改过”的混乱。
- 充分发挥模型优势:让擅长规划的工具做规划,让擅长执行的工具做执行。
它的本质不是“让两个 AI 实时聊天”,而是“让两个 AI 在同一个仓库内按约定协作”。
2. 环境准备与版本说明
2.1 需要哪些基础环境
先确认本机环境。
node -v git --version claude --version codex --version本方案基于 Node.js 脚本和 shell 命令,最低要求是 Node.js 能运行脚本,Git 能用于版本管理。不同操作系统下,Claude Code 和 Codex CLI 的安装方式略有差异,但核心逻辑一致。版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路,不绑定特定版本号。
2.2 安装 Claude Code
Claude Code 可以通过 npm 全局安装,也可以使用官方提供的原生安装方式。这里以 npm 为例:
npm install -g @anthropic-ai/claude-code安装后检查版本并登录:
claude --version claude第一次进入交互界面后,按提示完成账号认证。Claude Code 会在本地保存登录凭据,并在~/.claude/目录生成配置文件,其中settings.json用于保存模型、代理等自定义配置。
仓库级的CLAUDE.md文件会被 Claude Code 自动读取,作为全局指令。这是桥接方案的重要入口。
2.3 安装 Codex CLI
Codex CLI 同样可以通过 npm 安装,包名通常是@openai/codex:
npm install -g @openai/codex安装后检查版本并登录:
codex --version codex loginCodex CLI 的配置文件通常位于~/.codex/config.toml,这里可以配置模型提供商、沙箱权限等。具体选项会随版本变化,建议先运行codex --help和codex exec --help查看当前版本支持的参数,避免照搬网上旧命令。
2.4 确认非交互模式可用
桥接脚本的核心依赖是“非交互执行”:Claude Code 的-p模式和 Codex 的exec模式。它们让一个 Agent 可以在命令行中被另一个 Agent 或脚本唤起。
先验证两个命令都能跑通:
claude -p "用一句话介绍你自己" codex exec "输出 hello bridge"如果这两条能正常返回结果,说明本机具备桥接条件。如果claude -p或codex exec在当前版本中提示参数变更,请以官方--help输出为准。
3. 双向桥接的设计思路
3.1 桥接要解决的核心问题
两个 CLI 工具各自维护独立的会话状态,AI 模型本身不保留“跨工具记忆”。要让 Claude Code 和 Codex 协作,必须解决四个问题:
- 任务如何传递:Claude 怎么告诉 Codex“接下来由你完成”;
- 上下文如何同步:交接时的文件范围、需求说明、约束条件怎么记录;
- 状态如何查询:任务是被哪个 Agent 创建的,现在处于 pending、running、done 还是 failed;
- 冲突如何避免:两个 Agent 同时修改同一个文件,是灾难。
考虑到 Claude Code 和 Codex 都能读文件、写文件、执行命令,最稳定可靠的方式就是“文件协议”:把交接任务写成 JSON 文件,把状态记录在任务 JSON 和统一的日志里。
3.2 目录结构与命名约定
在仓库根目录下创建桥接目录.cc-codex-bridge/:
repo/ ├── .cc-codex-bridge/ │ ├── bridge.js │ ├── state.json │ ├── log/ │ │ └── bridge.log │ ├── tasks/ │ │ ├── task-20250101-001.json │ │ └── task-20250101-002.json │ └── scripts/ │ ├── handoff-to-codex.sh │ └── handoff-to-claude.sh ├── CLAUDE.md ├── AGENTS.md ├── src/ └── tests/bridge.js是核心脚本,负责创建任务、更新状态、列出任务;tasks/存放每个任务对应的 JSON 文件;state.json记录序列号等轻量状态;log/下追加桥接日志,方便事后审计。
CLAUDE.md是 Claude Code 的仓库指令,AGENTS.md是 Codex 的仓库指令。两个文件都告诉各自 Agent:本仓库存在桥接协议,遇到交接任务时读哪个文件、执行哪条命令。
3.3 协作约定:谁负责什么
桥接不是让两个 Agent 抢活干,而是约定好分工。可以根据项目阶段灵活设定,比如:
- 规划与重构:交给 Claude Code,因为它擅长多文件分析和方案设计;
- 测试与命令执行:交给 Codex,因为它适合快速写测试、跑命令、修局部问题;
- 任务交接的“仲裁者”:交给桥接脚本,所有交接必须通过任务 JSON,不允许直接在对话里互相喊话。
这套约定虽然简单,但非常重要。它避免了两边同时改一个文件的情况,也让每个任务的边界变得清晰。
4. 本地桥接脚本完整实现
4.1 bridge init 初始化脚本
先创建桥接目录和核心脚本。脚本使用 Node.js 内置模块,不依赖第三方包,方便复制到任意项目。
#!/usr/bin/env node // 文件路径:.cc-codex-bridge/bridge.js const fs = require('fs'); const path = require('path'); const BRIDGE_DIR = __dirname; const TASKS_DIR = path.join(BRIDGE_DIR, 'tasks'); const LOG_DIR = path.join(BRIDGE_DIR, 'log'); const STATE_FILE = path.join(BRIDGE_DIR, 'state.json'); function ensureDirs() { for (const dir of [TASKS_DIR, LOG_DIR]) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } } function loadState() { if (!fs.existsSync(STATE_FILE)) { return { initialized: true, taskSeq: 0, lastTask: null }; } return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')); } function saveState(state) { fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2)); } function now() { return new Date().toISOString(); } function nextId(state) { state.taskSeq += 1; const day = new Date().toISOString().slice(0, 10).replace(/-/g, ''); return `task-${day}-${String(state.taskSeq).padStart(3, '0')}`; } function log(msg) { fs.appendFileSync(path.join(LOG_DIR, 'bridge.log'), `${now()} ${msg}\n`); } function handoff({ from, to, prompt, scope }) { ensureDirs(); const state = loadState(); const id = nextId(state); const task = { id, from, to, status: 'pending', prompt, scope: scope || [], result: '', created_at: now(), updated_at: now(), }; const taskFile = path.join(TASKS_DIR, `${id}.json`); fs.writeFileSync(taskFile, JSON.stringify(task, null, 2)); state.lastTask = id; saveState(state); log(`handoff ${id} from ${from} to ${to}`); console.log(`task created: ${id}`); console.log(`file: ${taskFile}`); return task; } function markStatus(id, status, result) { const taskFile = path.join(TASKS_DIR, `${id}.json`); if (!fs.existsSync(taskFile)) { console.error(`task not found: ${id}`); process.exit(1); } const task = JSON.parse(fs.readFileSync(taskFile, 'utf8')); task.status = status; if (result !== undefined) { task.result = result; } task.updated_at = now(); fs.writeFileSync(taskFile, JSON.stringify(task, null, 2)); log(`${id} status -> ${status}`); console.log(`${id}: ${status}`); } function listTasks(statusFilter) { const files = fs.readdirSync(TASKS_DIR).filter((f) => f.endsWith('.json')); const tasks = files .map((f) => JSON.parse(fs.readFileSync(path.join(TASKS_DIR, f), 'utf8'))) .filter((t) => !statusFilter || t.status === statusFilter) .sort((a, b) => (a.id < b.id ? -1 : 1)); for (const t of tasks) { console.log(`${t.id}\t${t.status}\t${t.from}->${t.to}\t${t.scope.join(',')}\t${t.prompt.slice(0, 60)}`); } } function main() { const [cmd, ...args] = process.argv.slice(2); switch (cmd) { case 'init': ensureDirs(); if (!fs.existsSync(STATE_FILE)) { saveState({ initialized: true, taskSeq: 0, lastTask: null }); } console.log('bridge init ok'); break; case 'handoff': { const opts = {}; for (let i = 0; i < args.length; i += 2) { opts[args[i].replace(/^--/, '')] = args[i + 1]; } if (!opts.to || !opts.prompt) { console.error('usage: node bridge.js handoff --to codex|claude --prompt "..." [--scope "file1,file2"]'); process.exit(1); } handoff({ from: process.env.BRIDGE_FROM || 'unknown', to: opts.to, prompt: opts.prompt, scope: opts.scope ? opts.scope.split(',') : [], }); break; } case 'done': markStatus(args[0], 'done', args.slice(1).join(' ')); break; case 'fail': markStatus(args[0], 'failed', args.slice(1).join(' ')); break; case 'status': listTasks(args[0]); break; default: console.log(`usage: node bridge.js init node bridge.js handoff --to codex|claude --prompt "..." [--scope "file1,file2"] node bridge.js status [pending|running|done|failed] node bridge.js done <taskId> <result> node bridge.js fail <taskId> <reason>`); } } main();这段脚本是桥接的核心。它不直接修改任何业务代码,只负责维护“任务状态机”:pending表示等待另一个 Agent 处理,done表示完成,failed表示失败并记录原因。
设计原则是:任何 Agent 都不知道对方内部状态,它只需要读 JSON、写 JSON。这样即使 Claude Code 或 Codex 升级了,只要命令行入口不变,桥接协议仍然稳定。
4.2 任务交接协议与状态同步
一个典型的任务文件内容类似:
{ "id": "task-20250101-001", "from": "claude", "to": "codex", "status": "pending", "prompt": "为 src/word_counter.py 编写 pytest 测试,覆盖空字符串、标点、大小写,并运行测试", "scope": ["tests/test_word_counter.py"], "result": "", "created_at": "2025-01-01T10:00:00.000Z", "updated_at": "2025-01-01T10:00:00.000Z" }关键字段说明:
from:发起交接的一方,方便追溯;to:接收任务的一方;prompt:任务描述,要写清目标、约束、验收标准;scope:允许修改的文件范围,防止 Agent 越界改动;result:接收方完成任务后的结果说明。
scope这个字段非常重要。AI Agent 如果只看到“请完成测试”,情绪上可能会顺手修改源文件。通过scope明确“只允许改tests/下文件”,可以把风险控制在一定范围内。
4.3 让 Claude Code 调用 Codex 的入口
为了让 Claude Code 能主动把任务交给 Codex,需要提供一个包装脚本。下面这个脚本的作用是:创建交接任务,然后调用codex exec让 Codex 读取任务并执行。
#!/usr/bin/env bash # 文件路径:.cc-codex-bridge/scripts/handoff-to-codex.sh set -euo pipefail PROMPT="${1:?usage: handoff-to-codex.sh <prompt> [scope]}" SCOPE="${2:-}" BRIDGE_DIR="$(cd "$(dirname "$0")/.." && pwd)" cd "$BRIDGE_DIR/.." FLAGS=() if [ -n "$SCOPE" ]; then FLAGS+=(--scope "$SCOPE") fi OUTPUT=$(BRIDGE_FROM=claude node "$BRIDGE_DIR/bridge.js" handoff --to codex --prompt "$PROMPT" "${FLAGS[@]}") echo "$OUTPUT" TASK_ID=$(echo "$OUTPUT" | grep -oE 'task-[0-9]+-[0-9]+' | head -n1) codex exec "请阅读 .cc-codex-bridge/tasks/${TASK_ID}.json,按 prompt 和 scope 完成任务;完成后运行 node .cc-codex-bridge/bridge.js done ${TASK_ID} '补充完成说明'"脚本中最关键的是最后一行的codex exec。它让 Codex 自己读取任务 JSON,而不是依赖 Claude 在 prompt 里粘贴大段内容。这样任务描述、范围约束、完成标准都集中在一个可追溯的文件里。
注意:codex exec在部分版本中可能有沙箱限制,如果提示写文件被拒绝,请查看codex exec --help中关于沙箱和权限的参数,并确保在合法、可回滚的测试环境里执行。
4.4 让 Codex 调用 Claude Code 的入口
反向协作的脚本逻辑完全一致,只是把codex exec换成claude -p:
#!/usr/bin/env bash # 文件路径:.cc-codex-bridge/scripts/handoff-to-claude.sh set -euo pipefail PROMPT="${1:?usage: handoff-to-claude.sh <prompt> [scope]}" SCOPE="${2:-}" BRIDGE_DIR="$(cd "$(dirname "$0")/.." && pwd)" cd "$BRIDGE_DIR/.." FLAGS=() if [ -n "$SCOPE" ]; then FLAGS+=(--scope "$SCOPE") fi OUTPUT=$(BRIDGE_FROM=codex node "$BRIDGE_DIR/bridge.js" handoff --to claude --prompt "$PROMPT" "${FLAGS[@]}") echo "$OUTPUT" TASK_ID=$(echo "$OUTPUT" | grep -oE 'task-[0-9]+-[0-9]+' | head -n1) claude -p "请阅读 .cc-codex-bridge/tasks/${TASK_ID}.json,按 prompt 和 scope 完成任务;完成后运行 node .cc-codex-bridge/bridge.js done ${TASK_ID} '补充完成说明'"这里可以看到桥接的核心思想:两边并不需要知道对方的内部运行机制,只要遵守“读任务 JSON、改文件、更新状态”的约定,就能在同一个仓库里完成上下文交接。
4.5 双向握手验证
桥接脚本搭建完成后,先不要急着接到真实项目,先在临时目录里做一次“握手验证”。
# 初始化 node .cc-codex-bridge/bridge.js init # 模拟 Claude 向 Codex 交接任务 BRIDGE_FROM=claude node .cc-codex-bridge/bridge.js handoff --to codex --prompt "输出 hello bridge" --scope "tmp" # 查看任务状态 node .cc-codex-bridge/bridge.js status pending # 模拟 Codex 完成任务 node .cc-codex-bridge/bridge.js done task-20250101-001 "hello bridge output ok" # 查看最终状态 node .cc-codex-bridge/bridge.js status done如果能看到任务状态从pending变为done,说明桥接协议本身是通的。接下来再接入codex exec和claude -p,就会顺利很多。
5. 完整实战演示
5.1 初始化一个示例仓库
下面用一个极简 Python 项目演示“Claude Code 写功能、Codex 补测试并运行”的双向协作流程。
先创建项目结构:
mkdir -p cc-codex-demo/src cc-codex-demo/tests cd cc-codex-demo git init创建src/word_counter.py,这是一个简单的单词统计模块:
# 文件路径:src/word_counter.py from collections import Counter def word_count(text: str) -> dict[str, int]: """统计文本中每个单词出现的次数,忽略大小写与标点。""" normalized = ''.join(ch if ch.isalnum() or ch.isspace() else ' ' for ch in text) words = [w.lower() for w in normalized.split() if w] return dict(Counter(words))这个模块虽然简单,但已经包含边界情况:空字符串、标点符号、大小写、重复单词。适合作为两个 Agent 协作的切入点。
5.2 配置 CLAUDE.md 与 AGENTS.md
在仓库根目录创建CLAUDE.md:
# 仓库协作约定 本仓库使用 Claude Code 与 Codex 双向桥接。 ## 规则 1. 当需要把任务交给 Codex 时,执行: node .cc-codex-bridge/bridge.js handoff --to codex --prompt "任务描述" --scope "文件范围" 2. 你也可以调用脚本: bash .cc-codex-bridge/scripts/handoff-to-codex.sh "任务描述" "文件范围" 3. 不要直接修改 .cc-codex-bridge/tasks/ 下的 JSON 文件,除非你是在执行 bridge.js 的命令。 4. 修改代码前先确认 git 分支,避免与另一个 Agent 冲突。再创建AGENTS.md:
# Codex 协作约定 本仓库使用 Claude Code 与 Codex 双向桥接。 ## 规则 1. 开始工作前先查看待办: node .cc-codex-bridge/bridge.js status pending 2. 如果收到交接任务,读取 .cc-codex-bridge/tasks/ 下的对应 JSON 文件,按 prompt 与 scope 执行。 3. 不要修改任务文件中未声明的文件。 4. 完成后登记状态: node .cc-codex-bridge/bridge.js done <taskId> "完成结果说明" 5. 不要将密钥、口令写入任务文件或日志。这两个文件是“协作协议”的灵魂。Claude Code 和 Codex 在启动时都会读取仓库根目录下对应的指令文件,因此不需要在每次对话里重复说明规则。
5.3 场景:Claude Code 生成功能,Codex 补充测试
第一步,启动 Claude Code,让它在src/下生成模块。假设它已经生成了word_counter.py。
第二步,通过桥接脚本把“补测试”的任务交给 Codex:
BRIDGE_FROM=claude node .cc-codex-bridge/bridge.js handoff \ --to codex \ --prompt "为 src/word_counter.py 编写 pytest 测试,覆盖空字符串、纯标点、大小写、重复单词,测试文件放在 tests/test_word_counter.py,并执行 pytest" \ --scope "tests/test_word_counter.py"输出会给出任务 ID,例如:
task created: task-20250101-001 file: /path/to/cc-codex-demo/.cc-codex-bridge/tasks/task-20250101-001.json第三步,调用 Codex 执行任务:
codex exec "阅读 .cc-codex-bridge/tasks/task-20250101-001.json,按任务描述完成测试编写并运行 pytest"Codex 会读取任务 JSON,看到scope只允许修改tests/test_word_counter.py,于是只创建测试文件并运行。测试通过后,按AGENTS.md的约定执行状态登记:
node .cc-codex-bridge/bridge.js done task-20250101-001 "pytest 3 passed"第四步,回到 Claude Code,查看协作结果:
node .cc-codex-bridge/bridge.js status done输出会显示任务已经从pending变成done,并带有结果说明。Claude Code 只需要继续读取tests/test_word_counter.py,就能了解 Codex 做了什么。
5.4 反向场景:Codex 发现问题,Claude Code 修复
双向协作的价值在于“反向也能流转”。假设 Codex 在测试时发现word_count对None输入会抛异常,它不需要自己憋着改,而是可以把问题甩给 Claude Code。
Codex 发起交接:
BRIDGE_FROM=codex node .cc-codex-bridge/bridge.js handoff \ --to claude \ --prompt "word_count 函数对 None 输入会抛 AttributeError,请增加类型保护;同时补充测试覆盖入参为 None 的场景" \ --scope "src/word_counter.py,tests/test_word_counter.py"然后调用 Claude Code 处理:
bash .cc-codex-bridge/scripts/handoff-to-claude.sh \ "word_count 函数对 None 输入会抛 AttributeError,请增加类型保护;同时补充测试覆盖" \ "src/word_counter.py,tests/test_word_counter.py"Claude Code 读取任务 JSON 后,会修改src/word_counter.py,补充类似if text is None: return {}的保护逻辑,再更新测试文件。完成后登记状态,Codex 再次运行测试就能看到结果。
5.5 结果说明
这个场景虽然简单,但演示了完整闭环:任务从发起方进入桥接层,接收方读取任务文件并执行,完成后回写状态,发起方可以随时查看结果。
相比“两个 CLI 各自开窗口、人工复制粘贴上下文”,这套文件协议的优点是:
- 任务描述不会被截断;
- 文件范围有约束;
- 结果有日志;
- 任何一步出错都可以通过
bridge.js status和bridge.log追溯。
6. 常见问题与排查思路
6.1 unable to locate the codex cli binary
现象:在 VS Code 的 Codex 插件或桌面端工具中启动 Codex,提示unable to locate the codex cli binary,有时还会出现set codex_cli_path or ensure the electron app can access codex之类的说明。
原因:IDE 或桌面应用启动时并不会完整继承终端里的 PATH 环境变量,尤其是通过 nvm、Volta 等版本管理器安装的 Node 全局包,路径经常不在桌面应用可见范围内。
排查步骤:
which codex codex --version echo $PATH确认codex真实路径后,在 IDE 设置中指定codex_cli_path,或者将 Node 全局 bin 目录手动加入系统 PATH。改动后重启 IDE。
预防:安装 CLI 后立即确认codex --version可执行;不要只依赖桌面端启动,先用终端跑通基础命令。
6.2 model is not a model this version of claude code recognizes
现象:启动 Claude Code 时提示类似:
"deepseek-v4-pro" is not a model this version of claude code recognizes, so ...原因:这通常是因为在settings.json或环境变量ANTHROPIC_MODEL里指定了一个当前安装的 Claude Code 版本不认识的模型名。模型名拼写有误、版本不支持、第三方模型别名未同步,都可能导致这个报错。
排查步骤:
claude config get model echo $ANTHROPIC_MODEL如果设置了不认识的模型名,清除模型配置:
claude config unset model unset ANTHROPIC_MODEL或者直接进入~/.claude/settings.json,删除或修正model字段。
预防:不要照抄网上任意模型名;使用claude model相关帮助命令查看当前版本支持列表,或直接使用默认模型。
6.3 cc switch local proxy failed while handling codex endpoint /responses
现象:通过本地代理切换工具连接 Codex endpoint 时,日志报类似local proxy failed while handling codex endpoint /responses,请求失败。
原因:这里说的 local proxy 是本地调试或模型转发服务,通常用于把 CLI 请求转发到自定义模型服务。失败原因常见于:本地服务没启动、端口配错、请求体不符合服务端预期、以及代理规则覆盖了不该覆盖的接口。
排查步骤:
- 确认本地服务进程是否在运行:
ps aux | grep -i proxy - 用 curl 直接请求 endpoint,看返回是否正常:
curl http://127.0.0.1:你的端口/responses -d '{"test": true}' - 暂时恢复 CLI 默认配置,确认是否能正常请求。如果默认配置正常,再逐项检查 proxy 配置。
预防:local proxy 只用于本地调试环境,不要随意把生产环境流量指向本机地址;配置变更前先备份原配置文件。
6.4 Claude Code 529 / 请求过载
现象:执行任务时返回 529 或类似的 overloaded 错误,请求没有正常完成。
原因:模型服务端负载过高,或当前账号配额不足。桥接过程中如果同时发起多个大 prompt 请求,更容易触发限流。
排查与处理:
- 稍后重试,或者降低并发;
- 检查账号配额;
- 把大的交接任务拆成多个小任务,避免两个 Agent 同时发起超长请求。
预防:在桥接脚本中加入简单重试逻辑;单次任务 prompt 尽量精简,把“读代码”的工作交给 Agent 自己完成,而不是在任务 JSON 里贴整段代码。
6.5 常见问题速查表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 找不到 codex 命令 | PATH 未配置或工具链冲突 | 用which codex确认路径,配置codex_cli_path |
| 找不到 claude 命令 | npm 全局目录不在 PATH | 修复 npm 全局 bin 路径 |
| 模型名不被识别 | 配置了错误或过时的模型名 | 检查settings.json与ANTHROPIC_MODEL |
| 本地代理失败 | 服务未启动、端口冲突 | 检查本地服务与请求返回 |
| 529 过载 | 服务端限流 | 重试、拆任务、降低并发 |
| 任务 JSON 被误改 | Agent 直接编辑任务文件 | 在 CLAUDE.md 和 AGENTS.md 中强制规定 |
7. 最佳实践与工程建议
7.1 上下文管理
任务文件里的 prompt 不要写成长篇大论。Claude Code 和 Codex 都能自行读取仓库代码,因此任务描述只需写清“目标、范围、验收标准”。把大量上下文粘贴进任务文件,反而会让模型抓不住重点,同时消耗更多 token。
建议的 prompt 结构:
- 目标:一句话说明要交付什么;
- 范围:明确允许修改的文件或目录;
- 验收:说明如何验证成功;
- 约束:补充特殊限制,比如“不要改公共接口”“不要引入新依赖”。
7.2 只读与写入边界
桥接脚本的所有命令,本质上是在当前工作目录下执行。因此要特别注意文件写入边界。
推荐做法:
- 任务 JSON 中的
scope字段要写具体,不要让 Agent 自由发挥; - 本地开发时用独立分支或 Git worktree,让两边在不同分支上改代码,最后人工合并;
- 对
state.json这类桥接自身状态文件,要求 Agent 不直接修改; - 对生产环境保持最小权限原则,桥接脚本只用于开发环境。
7.3 日志与审计
.cc-codex-bridge/log/bridge.log记录了每一次任务创建、状态变更。实际项目中建议对日志做简单统计分析:
- 哪些任务交接给 Codex 后经常失败;
- 哪些任务在 Claude 和 Codex 之间反复流转;
- 单个任务的交接轮数是否过多。
如果发现“Claude 交给 Codex,Codex 又交回 Claude,反复三轮以上”,说明任务拆分可能不合理,应该停下来人工介入,而不是让两个模型无限循环。
7.4 安全与成本控制
Claude Code 和 Codex 的登录凭据都保存在本机,桥接方案不需要开放网络端口,也不要把任务 JSON 同步到公共仓库。任务文件和日志中不应该出现密钥、口令、生产数据。
成本方面,每次交接都是一次模型调用。建议:
- 小任务批量处理,而不是一条条 handoff;
- 设置最大交接轮数;
- 在 CI 或 cron 中增加定时检查 pending 任务的脚本,及时发现卡死任务。
8. 总结与下一步
这套本地桥接方案的精髓不在于脚本本身,而在于“用文件协议解耦两个 AI Agent”:Claude Code 不需要理解 Codex 的内部状态,Codex 也不需要了解 Claude 的会话历史,它们只需要遵守同一个任务 JSON 约定,就能在同一个仓库里接力完成工作。
如果你打算在真实项目中尝试,建议先从临时目录跑通init、handoff、status、done四个命令,再引入codex exec和claude -p,最后再迁移到有实际业务代码的分支。
下一步可以考虑:把桥接状态接入 Git commit 信息,让每次交接都有代码提交记录;或者写一个简单的 TUI 看板,实时展示 pending 任务;再进阶一点,可以给两个 Agent 分配不同的git worktree,让它们真正并行工作,最后统一合并。这样一来,Claude Code 和 Codex 就不再是两个孤立的工具,而是一套可以编排的本地多人协作系统。