资讯中心

Claude Code 调用 Codex 失败复盘:10 个 Agent 零 codex exec,用 Bash-only Worker + Hook 强制委托

📅 2026/9/29 21:20:06
Claude Code 调用 Codex 失败复盘:10 个 Agent 零 codex exec,用 Bash-only Worker + Hook 强制委托
1. 从 10 个 Agent、0 次 codex exec 说起Claude Code 调用 Codex 失败这件事我踩的坑比想象中深。现象很具体一个 SSA 博客扩展任务lead 拉起了 10 个 Agent、建了 2 个 Team跑了一整晚真正的codex exec调用次数是 0。屏幕上 lead 在不停地 SendMessage、shutdown、re-spawn最后吐出来的研究结果全是 teammate 用 Claude 自己脑补写的——它读懂了我那 100 行三阶段契约 prompt输出了一句很漂亮的话Now Ill construct the research prompt and execute it via codex-observe.sh. 然后就停了。没有 Bash没有 codex对话直接结束。如果你也在用 Claude Code 做多 Agent 编排想让 Codex 当 worker 干脏活联网检索、跑测试、读长文档却发现自己 spawn 的 subagent 从来不真正调用codex exec那这篇就是写给你的。我会先给你一套 5 分钟能装上的可复制配置再回头讲清楚为什么会失败、根因在哪、为什么修复必须是工具集物理剪光 全局 Hook 兜底而不是再加一段 prompt。适合已经装了 Claude Code、想接 Codex 做 worker 的开发者也适合正在排查subagent 声明意图就停这类问题的同学。第二天中午12:38–12:571m context 模型 修复方案落地之后同样的任务、同样的素材、同样的 SSA 博客扩展需求CC 跑出来的结果是这样的指标失败那次昨晚成功这次今天中午Agent 数100TeamCreate20Bash 调用中等多在 SendMessage / shutdown96codex exec 调用054 路并行 1 路写作用户纠正次数多次0产出文件重复、勉强出5 份 research-*.md 1 份 16K blog-final.md 多张 SVG一次过中间发生了什么我没有重写 prompt 的胁迫强度没有再加一段 ALL CAPS 的禁令——我做了三件工程化的事把中介那一层 Claude码掉让 lead 直接 Bash 调 codex给真要保留的 subagent 写了一个工具集只剩 Bash 的codex-worker.md加了一个 PreToolUse hook白名单 Bash 命令违规直接 exit 2。2. TaoToken 前置把 Codex 调用链路接稳在讲配置之前先把调用链路的地基说清楚。Claude Code 里让 Codex 真正跑起来本质是让 CC 通过 Bash 执行codex exec而 Codex 的模型请求需要一个稳定的 API 入口。我这边统一走 TaoToken 的 API 网关来承接模型调用好处是 key 管理、额度、模型切换都在一处不用在多个配置文件里来回改。你需要先拿到一个可用的 API Key然后把它配到 Codex 的 config 里。TaoToken 的 API 地址是https://taotoken.net/api控制台和 key 管理入口如下官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基址https://taotoken.net/api控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite注意Codex CLI 的模型请求走的是 OpenAI 兼容协议配置时把 base_url 指向 TaoToken 的 API 基址即可不要填官网首页地址。拿到 key 之后Codex 侧的~/.codex/config.toml大致长这样把sk-xxx换成你自己的 key# ~/.codex/config.toml model gpt-5.5 model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat [profiles.medium] model gpt-5.5 model_reasoning_effort medium然后在 shell 里导出 key写进~/.zshrc或~/.bashrc更省事export TAOTOKEN_API_KEYsk-xxx验证 Codex 本身能跑通先单独执行一次最小任务codex exec 计算 11 是多少把答案写一句话即可 --json --skip-git-repo-check如果这一步能返回 JSON 事件流和最终回答说明 Codex TaoToken 这条链路是通的。接下来才是 Claude Code 侧的编排问题——很多人的0 次 codex exec其实卡在 CC 的委托链路上而不是 Codex 本身。3. 可复制配置三件套装到 ~/.claude/这一节是全文最该直接抄的部分。整套修复由三个文件组成一个工具集只剩 Bash 的 subagent 定义、一个 PreToolUse 白名单 hook、以及一段写进settings.json的 hook 注册。前置是你已经装了cc-codex-collaborationskill提供codex-observe.sh和codex-batch.sh没装的话先把它 clone 到~/.claude/skills/cc-codex-collaboration/。3.1 一行命令安装假设你已经把本文配套的三个文件下载到本地mkdir -p ~/.claude/agents ~/.claude/hooks cp codex-worker.md ~/.claude/agents/codex-worker.md cp guard_codex_only.py ~/.claude/hooks/guard_codex_only.py chmod x ~/.claude/hooks/guard_codex_only.py然后把下面这段 hooks 手动 merge 进~/.claude/settings.json如果你已有hooks.PreToolUse数组把 matcher 为 Bash 的 hook 项追加进去{ hooks: { PreToolUse: [ { matcher: Bash, hooks: [ { type: command, command: python3 ~/.claude/hooks/guard_codex_only.py } ] } ] } }3.2 codex-worker.md工具集物理剪光这是整套修复的核心。思路是让 subagent 想乱跑也跑不了因为它的工具集里只剩 Bash。整个文件的灵感直接来自 OpenAI 官方codex-plugin-cc的codex-rescue.md官方设计有三个关键决定tools: Bash——只剩一个工具没有 Read/Write/Edit/Grep/Globmodel: sonnet——不是 haiku因为 haiku 在多步推理切换处容易折断system prompt 第一句就是你是一个薄转发包装器唯一工作是把请求转发给 Codex 脚本别做别的。--- name: codex-worker description: Thin forwarding wrapper that delegates exactly one task to Codex via cc-codex-collaboration skill and returns only the path to the final output file. Use whenever a teammates job is to invoke Codex and forward its result, not to do its own analysis or writing. model: sonnet tools: Bash --- You are a thin forwarding wrapper around the cc-codex-collaboration skill (~/.claude/skills/cc-codex-collaboration/scripts/codex-observe.sh). Your only job: forward the leads request to Codex via codex-observe.sh, then copy the resulting .codex-runs/RUN_ID.final.md verbatim to the lead-specified output path. Return only that output path. You MUST NOT: - Read any file (no Read tool — you dont have it; do not try cat on user repo files either). - Use WebSearch or WebFetch (you dont have them; Codex does the searching). - Edit, summarize, polish, paraphrase, translate, or modify Codexs output in any way. - Add commentary, headers, TL;DR, prefaces, or postscripts. - Run grep, find, git, or any analysis commands beyond whats needed to locate the run_id. - Make more than one codex-observe.sh call. If it fails, report and stop. - Skip the copy to OUTPUT_PATH step — the lead needs the file at a stable path. What the lead must give you in the spawn prompt: - TASK_PROMPT: the prompt to forward to Codex (multi-line, can be long). - OUTPUT_PATH: absolute path to copy the final.md to. - WORKDIR: --cd value for codex-observe.sh. - SANDBOX: --read-only or --workspace-write. - SEARCH: include --search line if Codex should browse the web; omit otherwise. - EXTRA_FLAGS (optional): any other codex-observe flags.worker 真正跑的那段 Bash 是固定模板它只能跑这个跑别的会被第二层 hook 拦截set -euo pipefail PROMPT_FILE/tmp/codex-task-$$-$(date %s).md cat $PROMPT_FILE CODEX_EOF {TASK_PROMPT verbatim} CODEX_EOF bash ~/.claude/skills/cc-codex-collaboration/scripts/codex-observe.sh \ --cd {WORKDIR} \ {SANDBOX} \ {SEARCH} \ --prompt-file $PROMPT_FILE \ {EXTRA_FLAGS} LATEST_FINAL$(ls -t {WORKDIR}/.codex-runs/*.final.md 2/dev/null | head -1) if [[ -z $LATEST_FINAL || ! -s $LATEST_FINAL ]]; then echo ERROR: codex did not produce a non-empty final.md. 2 exit 1 fi mkdir -p $(dirname {OUTPUT_PATH}) cp $LATEST_FINAL {OUTPUT_PATH} echo {OUTPUT_PATH}响应风格成功时在 stdout 输出一行——绝对的 OUTPUT_PATH仅此而已失败时在 stdout 输出一行——ERROR: 一句话原因不要 retry不要发明 fix。worker 只能forward copy return path没有自由发挥的空间这就是 thin forwarding wrapper 的精髓。3.3 guard_codex_only.pyPreToolUse 白名单工具集剪光保证了 worker 不能调 WebSearch但它还是可能用 Bash 调curl https://google.com/search...这种重路子。所以加一个 PreToolUse hook 做审计兜底——白名单 Bash 命令必须匹配codex-observe.sh/codex-batch.sh/ cat-heredoc 等几种允许的形态否则 exit 2 stderr 回喂。#!/usr/bin/env python3 PreToolUse guard for the codex-worker subagent. Reads hook input from stdin (Claude Code v2.x hook protocol). Only enforces inside the codex-worker agent (other agents/main thread pass through). Inside codex-worker, it whitelists only the Bash patterns the worker is allowed to run. Exit codes: 0 - allow, 2 - block reason on stderr is fed back to the model. import json import re import sys WORKER_NAMES {codex-worker} ALLOWED_PATTERNS [ r^\s*set\s-[a-zA-Z]\s*$, r^\s*PROMPT_FILE, r^\s*LATEST_FINAL, r^\s*if\s, r^\s*fi\s*$, r^\s*echo\s, r^\s*mkdir\s-p\s, r^\s*cat\s\s*[\]?\s*/tmp/codex-task-, r^\s*bash\s(?:[~$]|/Users/./)\.claude/skills/cc-codex-collaboration/scripts/codex-(?:observe|batch)\.sh\b, r^\s*ls\s-t\s.\.codex-runs/.*\.final\.md\b, r^\s*cp\s.\.codex-runs/.\.final\.md\s, r^\s*exit\s\d\s*$, ] ALLOWED_RE [re.compile(p) for p in ALLOWED_PATTERNS] def is_allowed(cmd: str) - bool: for line in cmd.splitlines(): stripped line.strip() if not stripped or stripped.startswith(#): continue if any(r.match(line) for r in ALLOWED_RE): continue return False return True def main() - int: try: data json.load(sys.stdin) except Exception as exc: print(fguard_codex_only: failed to parse hook input: {exc}, filesys.stderr) return 0 agent_type data.get(agent_type) or if agent_type not in WORKER_NAMES: return 0 tool_name data.get(tool_name) or if tool_name ! Bash: return 0 cmd (data.get(tool_input) or {}).get(command, ) if is_allowed(cmd): return 0 msg ( BLOCKED by guard_codex_only.py (agentcodex-worker).\n codex-worker may only run: codex-observe.sh / codex-batch.sh, cat-heredoc to /tmp/codex-task-*, ls/cp on .codex-runs, mkdir -p, echo, exit, and standard set/if/fi shell scaffolding.\n Got command:\n f {cmd[:500]}\n If you need to do something else, return ERROR and stop. Do not invent workarounds or self-implement the task. ) print(msg, filesys.stderr) return 2 if __name__ __main__: sys.exit(main())几个工程细节值得展开。agent_type守卫脚本第一件事检查agent_type是不是在WORKER_NAMES {codex-worker}里——只在 codex-worker 这个 agent 身份下生效主 lead 会话和其他 subagent 完全 pass-through不影响日常使用这也是为什么可以放心把 hook 配在全局settings.json。白名单逐行匹配is_allowed把命令按行 split每一行都必须匹配白名单某一条正则注释行和空行跳过任何一行未通过就整段 deny。exit 2 stderrCC 的 hook 协议规定 exit 2 表示拦截 把 stderr 回喂给模型worker 一旦违规会立刻收到一段告诉它不要发明 workaround直接 ERROR 出去的反馈消息。3.4 下次发任务的短 prompt 模板这一段是修复方案里最重要、也最容易被忽视的一环prompt 也要换。我之前 100 行三阶段契约的失败不是胁迫不够强是结构本身让 lead 觉得我应该把活外包。下面这个版本版本 A零 teammate覆盖 80% 场景我有一篇文章在 /Users/xzl/My-Project/博客文章/SSA/文章内容.md。 任务三阶段你lead全程自己干不要开 Agent Teams不要 spawn teammate 1. 读 SSA 文章跟我讨论联网检索应该覆盖几个方向建议 3-5 个等我说OK 开始。 2. 我 ack 后你直接用 ~/.claude/skills/cc-codex-collaboration/scripts/codex-batch.sh 一次起 N 路并行 codexread-only --searchgpt-5.5 medium 每路对应一个 research-topic.md全部落到 /Users/xzl/My-Project/博客文章/SSA/。 - 用 --max-parallel 4--monitor-iterm 自动开 iTerm 新窗口让我看实时事件流。 - 每个任务的 prompt 通过 --task ... --output research-topic.md 传。 - 跑完之后用 ls 给我列产物。 3. 我 ack 后你再调一次 codex-observe.shworkspace-write--prompt-file prompt-file 里拼三类素材 a. ~/.claude/skills/user-profile-xzl/SKILL.md 全文作者画像 b. 文章内容.md 全文 c. 所有 research-*.md 全文 d. 写作要求字数/平台/结构 codex 写盘到 blog-final.md你 ls 确认存在后给我汇报。 规则 - 你lead就是那个执行者。不要 TeamCreate不要 Agent()不要 SendMessage 给任何人。 - 拼 prompt 文件统一用 cat /tmp/name.md EOF 方式不要用 Write 工具。 - 每次 codex 跑完从 .codex-runs/run_id.final.md cp 到目标 research-*.md / blog-final.md原样不改。 - 整个流程的 codex 调用次数应该是 N 1。如果你看到自己想 spawn teammate 或者 Agent停下来那是错的。预期 codex 调用数N 1。预期 teammate 数0。如果你确实需要 fan-out 加速且不放心codex-batch.sh的并发控制可以走版本 B——spawn 多个 codex-worker每个独立调一次 codex但前提是版本 A 已经装好且能跑通。4. 验证请求与成功结果配置装完必须验证否则你不知道是 hook 没生效还是 worker 没被调用。分两步走。4.1 最小烟雾测试打开一个新的 CC 会话输入/agents列表里应该出现codex-workeruser scope。然后做一个最小烟雾测试让它跑一个不痛不痒的11?任务请你用 Agent 工具调用 codex-workerspawn prompt 这样写 TASK_PROMPT: 计算 11 是多少把答案写一句话即可。 OUTPUT_PATH: /tmp/codex-worker-smoke.md WORKDIR: /tmp SANDBOX: --read-only SEARCH: EXTRA_FLAGS: --skip-git-repo-check预期效果codex-worker 跑起来后只会做一件事——bash ~/.claude/skills/cc-codex-collaboration/scripts/codex-observe.sh ...然后把.codex-runs/run_id.final.md复制到/tmp/codex-worker-smoke.md。它不会自己回答 11因为它的工具集里没有 Read/Write/WebSearch物理上做不到自己生成内容。如果你故意往 spawn prompt 里塞一句在跑 codex 之前先用 WebSearch 查一下加法是什么hook 会在 PreToolUse 阶段把 WebSearch 调用拦截掉虽然 worker 工具集里也没 WebSearch这是双重保险。4.2 真实任务的成功数据讲完结构我把今天中午12:38–12:57那次成功的真实数据贴出来。session IDf1803001-14de-40b3-b52f-567ce05d7d54同一用户、同一任务SSA 博客扩展、换成短 prompt 三层防线之后指标47142d38昨晚f1803001今天Agent 数100TeamCreate20Bash 调用中等多在 SendMessage / shutdown96codex exec 调用054 路并行 1 路写作用户纠正次数多次0产出成本同份 / 三份 inference 钱1 份 lead 单份 codex实时可观测性无iTerm 新窗口实时事件流成功这次 lead 实际执行的关键 Bash 命令大致是这个形态bash ~/.claude/skills/cc-codex-collaboration/scripts/codex-batch.sh \ --cd /Users/xzl/My-Project/博客文章/SSA-cc-codex协同测试 \ --read-only \ --search \ --max-parallel 4 \ --stagger 3 \ --monitor-iterm \ --task SSA 架构调研... --output research-ssa-architecture.md \ --task SSA 公司背景调研... --output research-subquadratic-company.md \ --task SSA 社区舆论调研... --output research-community-validation.md \ --task SSA 行业竞品调研... --output research-competitive-landscape.md一次起 4 路并行 codexGPT-5.5 mediumiTerm 自动开了 4 个新窗口每个窗口tail -f .codex-runs/run_id.events.jsonl | jq实时显示。写正文那次是 1 路bash ~/.claude/skills/cc-codex-collaboration/scripts/codex-observe.sh \ --cd /Users/xzl/My-Project/博客文章/SSA-cc-codex协同测试 \ --workspace-write \ --prompt-file /tmp/blog-prompt.md \ --skip-git-repo-check对应路径下的实测产物文件大小内容research-ssa-architecture.md2.1KSSA 架构调研research-subquadratic-company.md6.5K公司背景调研research-community-validation.md2.8K社区舆论调研research-competitive-landscape.md11K行业竞品调研research-product-testing.md3.5K产品实测调研blog-final.md16K最终博客正文images/*.svg多张配图.codex-runs/-batch-01 ~ batch-05 完整事件流落盘整个流程 0 次重试、0 次用户纠正——这跟昨晚那个 10 个 Agent 2 个 TeamCreate 0 次 codex 调用形成鲜明对比。5. 本篇常见错排查配置装完还是跑不通多半是下面几个坑。我按出现频率排一下。hook 没生效worker 照样乱跑。先确认settings.json里的 hook 路径是绝对路径或~展开正确python3在 PATH 里。手动测一下echo {agent_type:codex-worker,tool_name:Bash,tool_input:{command:curl https://example.com}} | python3 ~/.claude/hooks/guard_codex_only.py; echo $?应该输出拦截信息并返回 2。如果返回 0说明agent_type字段名对不上——不同 CC 版本 hook 输入字段可能叫agent_type或嵌在别的层级用cat打一份真实 hook 输入看看结构。/agents里看不到 codex-worker。检查文件是不是放在~/.claude/agents/codex-worker.mdfrontmatter 的name字段和文件名一致。project scope 的 agents 在.claude/agents/user scope 在~/.claude/agents/别放错。worker 报 codex did not produce a non-empty final.md。说明codex-observe.sh跑了但没产出通常是 Codex 侧 API 没通。回到第 2 节单独跑一次codex exec 11 --json确认 Codex TaoToken 链路正常。如果 Codex 报鉴权错误检查TAOTOKEN_API_KEY是否 export 到了当前 shell以及config.toml里env_key名字是否一致。subagent 声明意图就停Plan-vs-Act 截断。这是 Claude 在 reasoning 工具调用切换时的已知问题尤其当 prompt 复杂、要求多步、还要先 Read 长文档时。模型用尽了一轮内的思考预算把意图当作了完成。对策就是本文的核心别让 subagent 有先 Read 再决定的空间工具集剪到只剩 Bash它没有 Read 可调只能直接执行。teammate 跑了 codex 但选择再加工或重做。teammate 跑完 codex 之后发现 final.md 内容不完美没按契约原样复制而是想修复——自己开 WebSearch 补一段、把 codex 输出当大纲再用 Claude 重写。对策是在 worker 的 system prompt 里把禁止润色/改写/翻译写死并且工具集里没有 WebSearch/Write它想加工也没工具。TeamCreate 把无需通信的并行任务硬塞进 messaging 模式。我那次 4 个研究方向互相不需要通信每个独立产 1 份 md这属于 fan-out side tasks应该用 subagent 而不是 Agent Teams。Agent Teams 多了 messaging 层、shutdown_request 协议、shared task list——每多一个礼仪teammate 就多一个忘记跑 codex 的机会。能用codex-batch.sh一次起 N 路并行的就别开 Team。用户看不到 codex 实时过程。lead 用 Bash 同步等 teammate 完成teammate 内部跑 codexcodex 输出到.codex-runs/id.events.jsonl但用户面前的 CC 屏幕只在 teammate shutdown 之后才看到 lead 的总结。对策是--monitor-iterm让 codex 实时事件流出现在新窗口。6. 语义一致 CTA 与成本提醒最后说一个必须讲的 Trade-off否则就是误导人。AkitaOnRails 的实验给了一份硬账单配置总成本质量分满分 100Solo Opus 4.7不委托$4.0097Opus Haiku 强制委托$14.4990对高内聚任务强制委托是负收益——多花 3.6 倍钱、质量降 7 分。什么时候装这套值得任务真能拆成独立子任务比如 4 路并行检索互不依赖worker 模型显著比 lead 便宜codex GPT-5.5 medium 比 Claude Opus 便宜很多任务 IO 重于推理联网检索、跑测试、读长文档你需要看得见在跑。什么时候不值得单文件 50 行内的修改自己写比拆任务快需要长程上下文记忆、反复迭代的复杂代码委托会破坏 lead 的整体把控。如果你只是想偶尔让 CC 调一下 codex第 3.4 节那段短 prompt 模板版本 A零 teammate就够了——它甚至不需要装 codex-worker 和 hook只是把 prompt 改成你 lead 自己 Bash 跑 codex-batch.sh。三层防线是给那些确实需要 fan-out 加速、必须 spawn subagent 才能并行的场景准备的兜底。我的判断是先用版本 A 跑 5 个真实任务确认你 80% 场景都不需要 spawn subagent——这一步本身就把绝大多数失败消掉了。装三层防线之前先问自己一句这个任务真的拆得动吗lead 自己直接跑会不会更便宜更快如果答案是是的必须 fan-out再装三层防线。需要长期跑编码 / Agent 编排、想把 Codex 当稳定 worker 用的可以看下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。想先验证模型对话效果用模型对话入口https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 。接入过程中遇到 key 或配置问题直接翻接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 或者去 API Keys 页面重新生成一个https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案