news 2026/9/7 17:38:11

Claude Code与Codex双向桥接:Python实现本地协作方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Claude Code与Codex双向桥接:Python实现本地协作方案

当开发团队同时使用 Claude Code 和 Codex 两个 AI 编程代理时,最头疼的问题通常不是单次问答质量,而是上下文不互通。Claude Code 在长会话拆解、跨文件重构和需求分析上表现稳定;Codex 在执行修改、调用 OpenAI 生态接口时更加直接。但两者安装在同一台机器上时,默认互不知道对方已经改过哪些文件、回答过哪些问题、留下过哪些决策。与其不断复制粘贴任务描述,不如在本地搭一座双向桥:通过一个共享目录、一组 JSON 消息和一个本地 HTTP 服务,让两个代理可以互相派发任务、回传结果、共享仓库变更记录。这篇文章围绕这个 local bridge 的最小实现展开,会先说明设计边界,再给出可运行的 Python 桥接脚本,最后覆盖安装和运行阶段常见的 codex cli binary not found、模型名不被识别、代理端点失败等高频问题。

1. 双向协作的核心难点:Claude Code 和 Codex 各自维护一套会话

1.1 两个代理的互补场景

在实际项目里,两个代理不一定是竞争关系,更多是分工关系。比如一个较大的后端重构任务,可以先让 Claude Code 分析现有接口、梳理依赖关系、给出分阶段改造方案;然后再让 Codex 去改代码、跑测试、处理编译错误。问题在于,Claude Code 和 Codex 各自的会话上下文是独立的。Claude Code 记得的方案,Codex 并不知道;Codex 改动的文件列表,Claude Code 也看不到。如果人工把一段描述复制过去,短任务还能接受,长任务很容易丢失关键信息,比如要绕过哪个模块、哪些文件可以改、哪些文件不能动、验收标准是什么。

这类需求催生了一个并不复杂但非常实用的工程组件:本地桥。它不改变两个 AI 代理本身的运行方式,也不要求它们共享同一个后台模型。桥只负责在两端之间传递结构化信息,让一方能向另一方派发任务、接收结果、查看执行状态。实现地点放在本机,不依赖云服务,因此离线可用,也便于审计。

1.2 本地桥接的定义与边界

本地桥接可以理解为一组轻量协议和工具。协议定义一个消息该包含哪些字段;工具提供 push、pull、update、list 等命令,让 Claude Code 和 Codex 都可以通过执行 shell 命令来读写消息。

这里要明确桥接的边界。桥不负责让两个代理直接看到对方的终端输出,也不负责替它们做语义理解。桥只解决三件事:

  • 任务派发:Claude Code 创建一条 task_request,Codex 能看到。
  • 结果回传:Codex 完成或失败后,创建一条 task_result,Claude Code 能看到。
  • 状态同步:消息是否已经读取、任务是否完成、失败原因是什么。

边界清晰能避免把桥做成“又大又难维护的系统”。实际使用中,桥的角色更像一个邮局,而不是翻译器。两端仍然按照自己的方式理解任务,桥只保证信息不丢失、可追踪、可回放。

1.3 为什么优先选择本地文件加 HTTP 的轻量方案

设计桥接方案时,可以考虑数据库、消息队列、共享文件、HTTP 服务等形式。针对两个 CLI 代理协作的场景,本地文件加轻量 HTTP 是性价比最高的组合。

文件作为存储层有几个明显优势。消息是 JSON 文件,可以直接cat查看;消息修改支持 diff;任务状态可以纳入 git 审计;即使桥的 HTTP 服务没有启动,代理仍然可以通过 CLI 命令直接读写文件。HTTP 服务只作为可选的查看入口,不承担主要存储职责。这样即使 HTTP 进程崩溃,桥接数据也不会丢,只要.bridge/目录还在,就能恢复。

这里也要说明,如果团队使用人数较多,或需要一个常驻服务做任务路由、超时重试、权限控制,那么后续可以迁移到 SQLite 或真正的消息队列。文章先给出最小闭环,避免一开始就引入过重依赖。

2. 先摸清环境:CLI 安装、路径和可用扩展点

2.1 检查 Claude Code 与 Codex CLI 是否可用

在配置桥之前,先确认两个 CLI 在终端里能正常运行。输入以下命令:

claude --version codex --version

如果命令行找不到claudecodex,说明 CLI 没有加入当前用户 PATH,或者安装目录不在预期位置。还可以用which查看实际可执行文件路径:

which claude which codex

对桥接脚本来说,知道codex的完整路径很重要。因为桥接工具可能会在脚本内部调用另一个代理,如果只写codex,而运行环境 PATH 不完整,就会触发unable to locate the codex cli binary这类错误。这个问题在 VSCode 扩展、桌面应用等场景中尤其常见,因为图形界面进程的环境变量往往和终端不一致。

2.2 识别常见的 codex cli binary not found 问题

在一个已有的 Claude Code 会话里直接执行codex --version,如果报错内容类似:

unable to locate the codex cli binary. set codex cli path or ensure the executable is in PATH

这说明当前进程没有找到 Codex CLI。可能原因有三类:

  • PATH 不完整:Codex 安装在~/.codex/bin或 npm 全局目录,但当前 PATH 没包含该目录。
  • 环境变量丢失:桌面应用或插件启动时,没有继承终端里的 shell 配置。
  • 安装不完整:Codex CLI 还没有安装成功,或安装后没有重新打开终端。

检查方式如下:

echo "$PATH" ls -l ~/.codex/bin/codex 2>/dev/null npm root -g 2>/dev/null

如果确定 Codex 已安装但路径不在 PATH,可以临时导出:

export PATH="$HOME/.codex/bin:$PATH"

如果使用本地桥脚本,建议在脚本里检测codex可执行文件,找不到时输出友好提示,而不是让上层代理误以为桥本身坏了。

2.3 Claude Code 与 Codex 的可扩展接口对比

要接入本地桥,不需要修改代理内部代码,只需要使用它们提供的调用外部命令能力。两者对比如下:

能力Claude CodeCodex CLI
执行 shell 命令支持,能直接运行 Bash 命令支持,可通过命令行或脚本执行
项目级说明文件CLAUDE.mdAGENTS.md
扩展指令Agent Skill / Hook / MCP自定义指令、配置文件、skills
状态存储本地会话本地会话
适合桥接的方式通过 Bash 调用 bridge.py通过 shell 调用 bridge.py

基于这个对比,桥接方案可以只依赖“执行 shell 命令”这一能力。在 Claude Code 侧,把桥接命令写进项目说明或 Skill 中;在 Codex 侧,把桥接命令写进AGENTS.md中。两个代理都能在需要时调用同一套bridge.py,从而实现双向协作。

3. 设计桥接协议:先定义“消息”和“状态”两个实体

3.1 消息分类

桥接并不是简单的“一边发一句话,另一边回一句话”。为了让代理知道该怎么处理,需要给消息一个明确的 type。常见类型包括:

  • task_request:请求对方完成一个任务。
  • task_result:任务执行完成或失败后的结果回传。
  • query:询问对方状态、上下文、文件信息。
  • notify:同步一次变更,不要求对方必须回复。

在实现中,task_result必须与某个task_request关联,这样发起方才知道结果对应哪个任务。关联字段使用task_id。一条task_request消息创建后,会有一个唯一 ID;task_result在创建时把这个 ID 带入task_id字段。

3.2 桥接目录结构与 JSON Schema

在本地方案中,所有消息都存放在项目根目录下的.bridge/messages/目录里。目录结构如下:

. ├── bridge.py ├── CLAUDE.md ├── AGENTS.md └── .bridge/ └── messages/ ├── 20250321102000-ab12cd.json ├── 20250321103000-ab34ef.json └── 20250321104000-ab56cd.json

每条消息是一个 JSON 文件。核心字段如下:

字段含义示例
id消息唯一 ID20250321102000-ab12cd
sender发送方,取值为claudecodexuserclaude
recipient接收方,取值为claudecodexallcodex
type消息类型task_request
title简短标题重构 /users 接口
content正文内容把列表接口改为分页...
task_id关联的任务 ID,无关联可为空字符串20250321102000-ab12cd
status消息状态open
created_at创建时间2025-03-21T10:20:00+0800
updated_at最近更新时间2025-03-21T10:30:00+0800
context可选的上下文信息,如目录、分支、改动文件{"cwd": "/workspace/project"}

示例消息如下:

{ "id": "20250321102000-ab12cd", "sender": "claude", "recipient": "codex", "type": "task_request", "title": "重构 /users 接口", "content": "将现有列表接口改为分页,并补充单元测试。", "task_id": "20250321102000-ab12cd", "status": "open", "created_at": "2025-03-21T10:20:00+0800", "updated_at": "2025-03-21T10:20:00+0800", "context": { "cwd": "/workspace/project", "branch": "feature/user-pagination", "changed_files": ["server/routes/users.py"] } }

status的生命周期建议保持简单:

open -> delivered -> done | failed | cancelled

发起方创建消息后,状态是open。接收方执行 pull 后,状态变成delivered,表示已经被看到。接收方完成任务后,通过 push 发送task_result,同时把原消息状态更新为donefailed

3.3 文件存储的读取策略和冲突处理

使用文件存储,最常见的问题是并发写。两个代理可能同时执行 push,或者一个 pull 一个 update 同时发生。为避免文件互相覆盖,可以采用“原子写”策略:先写一个临时文件,再通过os.replacePath.replace替换目标文件。

读取时不要依赖文件系统的修改时间做排序,因为消息 ID 已经包含时间信息,所以直接按文件名排序即可。pull 之后把消息标记为delivered,但不删除文件。这样后续可以通过 list 命令查看完整历史,也能避免误删导致追踪困难。

4. 用标准库实现本地桥:一个 Python 脚本完成存储与交互

4.1 脚本入口与目录初始化

实现采用 Python 标准库,不依赖第三方包。脚本bridge.py放在项目根目录,运行时会在自身目录下创建.bridge/messages/。如果希望把桥的目录放在别处,可以修改BRIDGE_HOME常量的定义。

#!/usr/bin/env python3 """bridge.py - a local bridge for Claude Code and Codex.""" import argparse import json import time import uuid from pathlib import Path BRIDGE_HOME = Path(__file__).resolve().parent / ".bridge" MSG_DIR = BRIDGE_HOME / "messages" def now(): return time.strftime("%Y-%m-%dT%H:%M:%S%z") def ensure_dirs(): MSG_DIR.mkdir(parents=True, exist_ok=True) def save_message(msg): ensure_dirs() path = MSG_DIR / f"{msg['id']}.json" tmp = path.with_suffix(".tmp") tmp.write_text(json.dumps(msg, ensure_ascii=False, indent=2), encoding="utf-8") tmp.replace(path) return path def load_messages(): ensure_dirs() result = [] for path in sorted(MSG_DIR.glob("*.json")): try: data = json.loads(path.read_text(encoding="utf-8")) except json.JSONDecodeError: continue result.append(data) return result

save_message中先用.tmp后缀写临时文件,再用replace覆盖正式文件。这样即使写过程中进程被中断,旧文件也不会被破坏。load_messages按文件名排序,读取时不改变消息顺序。

4.2 CLI 子命令:push、pull、update、list

接下来实现四个子命令。push创建一条消息,pull读取并标记当前代理的未读消息,update更新状态,list输出消息概览。

def create_message(sender, recipient, msg_type, title, content, task_id="", context=None): if sender not in ("claude", "codex", "user"): raise ValueError(f"unknown sender: {sender}") if recipient not in ("claude", "codex", "all"): raise ValueError(f"unknown recipient: {recipient}") msg = { "id": time.strftime("%Y%m%d%H%M%S") + "-" + uuid.uuid4().hex[:6], "sender": sender, "recipient": recipient, "type": msg_type, "title": title, "content": content, "task_id": task_id, "status": "open", "created_at": now(), "updated_at": now(), } if context: msg["context"] = context save_message(msg) return msg def cmd_push(args): context = {"cwd": args.cwd} if args.cwd else None if args.files: context = context or {} context["changed_files"] = args.files.split(",") msg = create_message( args.sender, args.recipient, args.type, args.title, args.content, args.task_id, context, ) print(json.dumps(msg, ensure_ascii=False, indent=2)) def cmd_pull(args): pulled = [] for msg in load_messages(): if msg["recipient"] not in (args.agent, "all"): continue if msg["status"] != "open": continue msg["status"] = "delivered" msg["updated_at"] = now() save_message(msg) pulled.append(msg) if pulled: print(json.dumps(pulled, ensure_ascii=False, indent=2)) else: print("no open messages") return 1 return 0 def cmd_update(args): updated = False for msg in load_messages(): if args.message_id and msg["id"] != args.message_id: continue if args.task_id and msg["task_id"] != args.task_id: continue msg["status"] = args.status if args.note: msg["note"] = args.note msg["updated_at"] = now() save_message(msg) updated = True if not updated: print("message not found") return 1 print("updated") def cmd_list(args): for msg in load_messages(): line = f"{msg['id']} {msg['status']} {msg['sender']}->{msg['recipient']} [{msg['type']}] {msg['title']}" print(line)

pull 命令是“读取并标记”的语义。它每次只处理open状态的消息,处理完以后状态变为delivered,不会重复拉取。这样即使代理在抓取到消息之后中途崩溃,任务仍然可以从delivered状态恢复。

4.3 可选的本地 HTTP 状态服务

CLI 命令已经足够实现双向协作。增加 HTTP 服务是为了方便人类开发者查看状态,或给其他工具提供只读接口。服务只监听127.0.0.1,不允许外部网络访问。

from http.server import BaseHTTPRequestHandler, HTTPServer class BridgeHandler(BaseHTTPRequestHandler): def _send_json(self, data, status=200): body = json.dumps(data, ensure_ascii=False, indent=2).encode("utf-8") self.send_response(status) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def do_GET(self): if self.path == "/status": messages = load_messages() summary = { "total": len(messages), "open": sum(1 for m in messages if m["status"] == "open"), "delivered": sum(1 for m in messages if m["status"] == "delivered"), } self._send_json({"bridge_home": str(BRIDGE_HOME), "summary": summary}) return if self.path.startswith("/messages"): query = self.path.split("?", maxsplit=1)[-1] agent = "all" if query.startswith("agent="): agent = query.split("=", maxsplit=1)[-1] messages = [ m for m in load_messages() if m["recipient"] in (agent, "all") ] self._send_json({"messages": messages}) return self.send_error(404, "Not Found") def cmd_server(args): httpd = HTTPServer(("127.0.0.1", args.port), BridgeHandler) print(f"bridge server on http://127.0.0.1:{args.port}") try: httpd.serve_forever() except KeyboardInterrupt: pass

/status返回消息总数和未读状态数量,/messages?agent=codex返回某个接收方的全部消息。HTTP 服务不用于处理创建和删除消息,只做状态查看。原因很简单:创建消息需要校验发送方和接收方,CLI 已经承担了这部分逻辑,再重复实现会增加维护成本。

4.4 主函数与命令行参数

主函数将子命令和对应处理函数绑定。所有参数都通过命令行传入,方便 Claude Code 和 Codex 在执行 shell 命令时展开变量。

def main(): parser = argparse.ArgumentParser(description="Local bridge between Claude Code and Codex") sub = parser.add_subparsers(dest="command", required=True) push = sub.add_parser("push") push.add_argument("--sender", required=True)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 10:27:59

Claude Code自动在Git提交中追加Session URL?一文读懂原因与关闭方法

最近不少用 Claude Code 做开发的团队碰到一个奇怪现象:git log里突然多了一行Claude Session URL: https://claude.ai/session/xxxx,PR 描述底部也被自动追加了同一条链接。有人在群里问:这是中病毒了,还是哪个插件在“夹带私货”…

作者头像 李华
网站建设 2026/9/6 10:48:19

x64dbg脚本编程:自动化逆向工程与调试分析实战指南

在逆向工程和软件分析领域,调试器是安全研究员和逆向工程师不可或缺的“手术刀”。面对复杂的二进制程序,手动跟踪每一条指令、每一个寄存器值,不仅效率低下,而且极易出错。你是否曾因反复执行相同的调试步骤而感到疲惫&#xff1…

作者头像 李华
网站建设 2026/9/4 7:40:45

计算机毕业设计之基于Java Web技术的课程试卷信息管理系统

当下社会,信息技术充斥社会各个领域,已融入人们生活的点滴,日常中人们管理信息、办理业务等等都可以网络线上进行,快速而又便利,特别是随着移动互联网时代的到来,更是让人们随时享受着网络给带来的前所未有…

作者头像 李华
网站建设 2026/9/5 7:49:22

python中的is、==和cmp()比较字符串

中的is、和cmp(),比较字符串平常写shell脚本就清楚, 用于字符串判断的是而非其他, 用于数字判断的是-eq等而非别种, 然而事实确实并非如此这般。所以要逐渐往用到写脚本去转变, 这些基本玩意儿得彻彻底底掌握在骨子里!在 中比较字符串最好是使用简单逻辑…

作者头像 李华
网站建设 2026/9/4 15:39:14

写回链路 插入 替换 批注 链接批注的实操

chayuan-wps 加载项支持把 AI 输出写回 WPS 文档。插入 / 替换 / 批注 / 链接批注。这一篇讲。 写回的几种方式 方式一:插入。在当前光标位置插入文字。 方式二:替换。替换当前选中的文字。 方式三:批注。给某段加一个批注(侧边的…

作者头像 李华