Claude Code 调用 Codex:一次性子进程与常驻 App Server

type
status
date
slug
summary
tags
category
icon
password
wechat_gate
“Claude Code 调用 Codex”听起来像一个功能,实际至少包含两种完全不同的进程模型。
第一种通过 codex exec 启动一次性子进程,投喂一段明确指令,等待文件或结构化结果生成后退出。第二种通过 codex app-server 建立常驻运行时,使用 JSON-RPC 管理 thread、turn、review 和 interrupt,适合需要状态延续的协作任务。
两者都能让 Claude Code 借用 Codex,但它们在启动成本、通信协议、权限控制、错误恢复和适用场景上差异很大。理解这些差异,才能避免在简单任务上过度设计,也能避免在复杂任务里使用无法续跑的一次性调用。

先给结论:不是两种命令,而是两种架构

维度
codex exec 一次性子进程
codex app-server 常驻服务
代表实现
baoyu codex-imagegen 后端
OpenAI Codex Plugin for Claude Code
进程形态
每次启动,任务结束后退出
服务进程常驻,会话内复用
通信方式
启动参数、stdin、JSONL 事件流
JSON-RPC 请求与通知
状态模型
单次运行,默认不依赖前一轮
thread 包含多个 turn,可 resume
权限姿态
示例实现使用 danger-full-access
review 固定只读,task 可切换 workspace-write
典型任务
出图、生成文件、单次确定性操作
代码审查、长任务委派、多轮续跑
主要风险
全权限子进程、每次冷启动
协议和生命周期更复杂
一句话判断:
  • 只需要“跑一次,拿到一个确定产物”,优先考虑 codex exec
  • 需要“持续协作、保留上下文、随时取消或恢复”,优先考虑 codex app-server

核验边界:版本信息必须分清

这次整理最先暴露的问题不是架构,而是版本口径。我原本沿用了原稿中的“本机当前版本”表述,继续检查安装记录后,才确认 marketplace 源码与当前激活插件不是同一快照。
本机命令与插件记录显示:
  • Codex CLI 为 0.140.0
  • OpenAI Codex Plugin for Claude Code 为 1.0.4,commit 为 807e03a
  • baoyu-skills 的 marketplace 源码快照为 2.5.1,commit 为 441ca30
  • 但 Claude Code 的已安装插件记录中,baoyu-skills 仍指向较早的 1.111.1 快照。
因此,本文关于 baoyu-codex-imagegen 的代码分析,准确表述应当是:
基于本机 marketplace 中的 baoyu-skills v2.5.1 源码快照,而不是声称当前激活插件已经升级到 v2.5.1。
这是一个很容易忽略的细节。插件市场源码、缓存快照和当前激活版本可能不是同一个提交。只看目录名或 changelog,很容易把“我读到的源码版本”误写成“系统正在运行的版本”。
notion image

路径一:codex exec,把 Codex 当一次性算子

它解决什么问题

baoyu-skills 的 baoyu-codex-imagegen 用途很明确:让 Claude Code 等非 Codex 宿主调用 Codex CLI 内置的 image_gen 工具,并把图片保存到指定路径。
这类任务有几个共同点:
  1. 输入边界清晰,通常是一段 prompt、图片比例和输出路径。
  1. 结果边界清晰,通常是一个文件和一行结构化状态。
  1. 不需要多轮讨论,也不需要恢复上一轮上下文。
因此,它没有引入常驻服务,而是直接启动:
如果存在参考图,还会追加一个或多个 --image 参数。

每个参数为什么存在

exec
用于非交互、脚本化运行。OpenAI 的 CLI 文档将它定位为适合自动化和 CI 的执行方式,运行完成后返回结果。
  • -json
把过程输出改为逐行 JSON 事件,也就是 JSONL。上游程序不需要解析终端展示文本,可以直接读取 thread、tool call、usage 和最终消息等结构化事件。
  • -sandbox danger-full-access
这个实现要求 Codex 从默认生成目录复制图片到调用方指定的任意目标路径,因此选择了完全文件权限。
这不是一个通用最佳实践。OpenAI 文档明确建议,自动化场景优先使用 workspace-write,除非运行环境本身已经隔离,否则应避免不必要的全权限。
  • -skip-git-repo-check
图片任务可能从临时目录或插件目录启动,而不是可信 Git 仓库。这个参数允许 Codex 在非 Git 目录运行。
末尾的 -
表示从 stdin 读取指令。封装层通过 child.stdin.write(instruction) 写入任务契约,再关闭 stdin。

真正重要的是任务契约

这条链路没有直接透传用户 prompt,而是包了一层严格指令,大意如下:
这是一种典型的“把子 Agent 当算子”设计:
  • 输入结构固定。
  • 允许使用的工具固定。
  • 文件副作用固定。
  • 最终输出格式固定。
  • 禁止项明确。
对于自动化链路,约束比语言表达更重要。上游需要的是可验证结果,不是一次开放式对话。

不相信自报结果:三级验证

这套实现最值得保留的工程细节,是它不会因为 Codex 回复“成功”就判定成功。
它会依次检查:
  1. JSONL 事件里是否存在 thread ID。
  1. $CODEX_HOME/generated_images/{threadId}/ 是否真的出现图片。
  1. 如果目录检查失败,tool call 中是否存在从生成目录复制到目标路径的 cpmv
  1. 目标文件是否真实存在,并且字节数大于零。
失败会转换为结构化错误,例如:
  • agent_refused
  • no_image_gen_tool_use
  • timeout
  • codex_not_installed
  • spawn_failed
这里的核心不是图片,而是一个通用原则:
Agent 的自然语言回复只能算声明,文件、事件和可重复检查才算证据。

适用场景与局限

适合:
  • 单次出图或文件生成。
  • 边界清晰的代码转换。
  • 一次性分析并返回结构化 JSON。
  • 不要求继承上下文的自动化任务。
局限:
  • 每次运行都有进程和模型冷启动成本。
  • 默认没有跨运行状态。
  • 如果使用 danger-full-access,信任边界非常宽。
  • 超时、取消和恢复通常由封装层自行实现。

路径二:codex app-server,把 Codex 当有状态服务

OpenAI Codex Plugin for Claude Code 没有为每个命令重新运行 codex exec。它启动 codex app-server,通过 JSON-RPC 管理持续会话。
OpenAI 官方文档把 App Server 的核心抽象定义为三层:
  • Thread:一段可持续的对话。
  • Turn:Thread 中的一轮用户输入与 Agent 执行。
  • Item:Turn 中的消息、推理、命令、文件修改等事件。
notion image

直连和 Broker

插件实现了两种连接方式。
第一种是直连:
客户端直接启动 codex app-server,通过标准输入输出发送逐行 JSON-RPC 消息。
第二种是 Broker:
插件通过 CODEX_COMPANION_APP_SERVER_ENDPOINT 保存 broker endpoint,让同一个 Claude Code 会话里的 review、rescue 和状态命令共享 Codex 运行时。
如果 broker 返回忙错误 -32001,或者连接出现 ENOENTECONNREFUSED,插件会放弃 broker,并直接启动一个 App Server 重试。
这比一次性子进程多了一层复杂度,但换来了:
  • 会话内运行时复用。
  • thread 持久化。
  • 后台任务管理。
  • 取消和恢复能力。
  • review 与 task 的权限隔离。

连接建立:先 initialize

App Server 连接建立后,客户端必须先发送 initialize,然后发送 initialized 通知。
插件传入的客户端身份是:
它还通过 optOutNotificationMethods 退订部分逐字增量事件,只保留对上游更有价值的结构化通知,减少噪音。

会话模型:thread 与 turn

插件使用的关键 RPC 方法包括:
方法
作用
thread/start
创建新线程
thread/name/set
设置线程名称
thread/resume
恢复已有线程
thread/list
查询历史线程
turn/start
在线程中启动一轮任务
review/start
启动代码审查
turn/interrupt
中断正在执行的 turn
这意味着 App Server 不是“把 prompt 送进去等结果”的单轮封装,而是一个可管理的会话运行时。

review 与 task 的权限不同

插件把两个动作分得很清楚。
review:
  • 固定使用 read-only
  • 使用临时 thread。
  • 通过 review/start 执行。
  • 只返回审查结论,不修改代码。
task:
  • 默认可以只读。
  • 传入 -write 后切换到 workspace-write
  • 可保存 thread。
  • 可通过 -resume-resume-last 延续上一次任务。
这比“所有任务都全权限运行”更接近工程系统应有的默认姿态:先按任务性质设定最小权限,再决定是否扩大写入范围。

Hooks 把 Codex 接入 Claude Code 生命周期

插件注册了三类 Claude Code Hook:
  • SessionStart:准备共享运行时。
  • SessionEnd:清理 broker 和会话资源。
  • Stop:可选的停止门审查。
启用 review gate 后,Claude Code 每次准备停止时,都可以让 Codex 检查上一轮是否存在需要阻断的问题。
这类机制的价值不是“多一个模型”,而是把第二模型放进交付流程:
但它也有成本。官方插件 README 明确提醒,review gate 可能形成长时间 Claude/Codex 循环,并快速消耗使用额度,因此不应无条件开启。

怎么选择

选择 codex exec 的条件

同时满足以下大部分条件时,使用一次性子进程更简单:
  1. 任务只有一轮。
  1. 结果可以用文件或 JSON 验证。
  1. 不需要恢复历史上下文。
  1. 冷启动成本可以接受。
  1. 调用方能够独立处理超时和重试。
典型例子:
  • 生成一张图片。
  • 把一份输入转换成固定格式。
  • 对一个文件执行单次分析。
  • 在 CI 中运行一次检查。

选择 codex app-server 的条件

出现以下需求时,常驻服务更合适:
  1. 需要多轮会话。
  1. 需要 thread 恢复。
  1. 需要后台运行和状态查询。
  1. 需要中断正在运行的任务。
  1. 需要区分 review 与 write 权限。
  1. 需要接入 Claude Code 的会话生命周期。
典型例子:
  • 对一个分支持续审查。
  • 委派长时间排查任务。
  • 让 Codex 修改代码后继续补测。
  • 在停止前自动进行第二模型门禁检查。

真实核验过程

这篇发布版没有只依赖原稿描述,而是重新做了一次最小核验。
我实际执行的步骤包括:
  1. 读取原稿,列出所有涉及版本、命令、RPC 方法和权限的事实性陈述。
  1. 运行 codex --versioncodex exec --helpcodex app-server --help,确认当前 CLI 的命令与参数。
  1. 检查 OpenAI 插件 manifest、安装记录、app-server.mjscodex.mjs 和 Hook 配置。
  1. 检查 baoyu marketplace 源码中的 spawn.tsmain.ts、版本文件和 Git commit。
  1. 对照 OpenAI Codex CLI、App Server、Codex Plugin 和 Claude Code Hooks 官方文档。
  1. 将“当前激活版本”和“实际阅读的源码快照”分开记录。

这次遇到的错误与教训

我最初把原稿中的 baoyu-skills v2.5.1 当成“本机当前版本”。继续检查后发现,本机确实存在 v2.5.1 marketplace 源码,但 Claude Code 的已安装插件记录仍指向较早的快照。
如果不检查安装记录,这个表述看起来合理,却不够准确。
这次修订得到的教训是:
分析本地插件时,至少要同时记录 marketplace HEAD、安装缓存路径、插件 manifest 和 commit。任何一个都不能单独代表“当前正在运行的版本”。

使用建议

一次性任务:把输出契约写死

不要只写“帮我生成图片”或“帮我检查代码”。自动化 prompt 至少应包含:
这样做可以降低 Agent 自由发挥带来的不确定性,也方便上游判断成功或失败。

长任务:续跑只发送增量

恢复 thread 时,只发送新的变化:
没有必要重新复制整段背景。重复上下文既增加噪音,也可能让模型误判任务边界。

审查任务:结论必须绑定证据

无论使用标准 review 还是对抗式 review,都应要求每个问题对应:
  • 实际查看过的文件或 diff。
  • 可复现的失败路径。
  • 明确的风险级别。
  • 事实、推断和待确认项的区分。
没有证据的“可能有问题”,很难进入工程决策。

权限:默认从最小范围开始

选择顺序应当是:
只有任务确实需要更大文件范围,并且运行环境可信时,才扩大权限。

总结

Claude Code 调用 Codex,不是一个统一的调用方式。
codex exec 的本质是一次性、无状态、容易封装的子进程。它适合边界清晰、结果可验证的单次任务。
codex app-server 的本质是有状态、可恢复、可管理的 Agent 服务。它适合代码审查、任务委派和需要持续协作的复杂工作。
真正的选型标准不是“哪个更高级”,而是:
  • 任务是否需要状态。
  • 结果是否能一次性验证。
  • 是否需要中断、恢复和后台管理。
  • 权限是否可以按动作分级。
  • 额外协议复杂度是否值得。
简单任务用简单进程,持续协作用有状态服务。把这条边界划清,系统会更容易理解,也更容易维护。

参考资料

相关阅读

以下作为可延伸阅读方向:
  • 如何为 AI Agent 设计可验证的输出契约
  • Claude Code Hook 的工程化使用方式
  • 多 Agent 代码审查中的权限与责任边界
 
 
2026.06.18 20:34 沪 · 赵巷
📌 声明:本文由 AI 辅助完成