The user's intended division of labor:
- Claude Code (orchestrator): plans, dispatches, reviews, decides PASS/FAIL
- Codex (executor): implements, runs tests, reports done. Also spawns its own isolated sub-agent for self-review when asked, on a separate turn.
- Cross-review: when stakes are high (production launch, accounting, state machines), TWO independent staff-engineer sub-agents (one Claude-side, one Codex-side) review the same diff in fresh isolated contexts and converge.
Codex.app maintains a single feature thread for one feature's lifecycle (so Codex keeps full context across many turns). Reviews can be:
- In-thread sub-agent re-review (preferred for production gate): Codex spawns an isolated sub-agent in the same thread, fresh context, no fork of the conversation history. Result lands in the same timeline so future fix-execution turns see the review verdict.
- Fresh-thread review (lighter, when continuity isn't needed): use
bridge.py review --workspace <ws>to start a fully separate thread.
Default to in-thread sub-agent for cross-review. Use fresh-thread only for ad-hoc one-off audits.
1. Claude plans the change (no Codex call yet)
2. Codex turn 1 — IMPLEMENT ONLY
prompt: "fix items 1..N + run tests. When done, send a brief summary
message and STOP. Do NOT spawn any review sub-agent yet."
▼
Codex writes code, runs tests, reports done. Turn ends.
Claude receives wait completion notification.
3. ── parallel ──
a) Codex turn 2: b) Claude sub-agent (Agent tool):
prompt: "Now spawn an prompt: "Staff Engineer re-review
isolated Staff Engineer of the diff. Fresh context. No
sub-agent to re-review the reading prior reviews. file:line
diff. Same scope as turn 1. refs throughout. Verdict:
Verdict: ALL_CLEAN_SHIP_IT ALL_CLEAN_SHIP_IT or BLOCKED."
or BLOCKED."
4. Both verdicts arrive. Claude cross-compares.
- Both ALL_CLEAN_SHIP_IT → notify user, archive review threads if any.
- Either BLOCKED → send the disagreement to the *other* side, ask for
confirm/refute on each unique finding, converge to consensus must-fix
list, then go to step 2 (next round).
Wrong:
fix items 1..N + after fixing, spawn isolated Staff Engineer sub-agent
to re-review and report verdict.
This serializes Codex's fix → sub-agent inside ONE turn. Claude can only detect "Codex done" after both finish. The Claude-side sub-agent re-review that should run in parallel is now blocked behind Codex's full sequence. Wall-clock cost: easily 2x.
Right: see step 2 above. Fix and re-review go in separate turns.
A single side's review has predictable blind spots. Real example from this project: Claude's sub-agent passed all 7 fixes as ALL_CLEAN_SHIP_IT. Codex's sub-agent caught a critical issue Claude missed — SQLite/D1 syntax error in the new CTE-DML accounting statements. Cross-validation against actual SQLite 3.51.0 confirmed Codex was correct. Without two-sided review the bug would have shipped.
Default: Staff Engineer. Sets the strictness filter ("what would actually
break in production?"). Don't rely on context — sub-agents are fresh.
Other valid framings if the situation calls for it:
- Principal Engineer — cross-system architecture / long-term evolution
- SRE Lead — observability / oncall load
- Security Engineer — attack surface / credential / data leakage
- Performance Engineer — latency / throughput / resource use
Lines like "do not read any prior code-review/ notes directory" are required. Prior review
files bias the sub-agent toward their findings (and away from new ones).
The whole point of a fresh sub-agent is independent perspective.
Don't write "re-review the same way you reviewed before." Sub-agents are fresh and don't share the previous instructions. Spell out: scope, role, deliverable format, severity grading, "do not soften", file:line refs, verdict format. Every time.
When the change touches DB queries (CTEs, joins, RETURNING, transactions),
the review must verify dialect compatibility. Mocked unit tests
(db.run mocks that string-match SQL) prove nothing about whether the
SQL parses on real D1/SQLite. Use real SQLite (sqlite3 CLI on macOS,
or better-sqlite3 in Node) to test the actual statement.
Save to a file (e.g. /tmp/codex-task.md) and pass via --prompt-file:
工作目录: <repo path on disk>
任务: <one-paragraph plan from Claude>
第一步要做的事 (具体到文件/函数):
- <step 1>
- <step 2>
注意:
- 改完每一步用 git diff 自查一遍再继续
- 遇到不确定的设计决策不要拍脑袋,先打住等我确认
- 只动这次任务相关的文件,不要顺手做别的清理
- 跑完 npm test + npm run typecheck 全绿才算这一步完成
这一轮请只做修复,不要在这同一个 turn 里 spawn re-review sub-agent。
修复清单:
1. <file:line> — <一句话描述> — <具体修法>
2. ...
测试要求:
- npm test 全绿
- npm run typecheck 全绿
- 必要时新增 <X> 集成测试
修完先停下来发一条简短总结,标志这一 turn 结束。
不要自己开 sub-agent,等我下一条 message 再 spawn re-review sub-agent。
现在 spawn 一个 isolated Staff Engineer sub-agent 对刚才你修的代码做 re-review。
硬性要求:
- Fresh isolated context,不 fork 当前对话历史
- 不读任何先前 review 文档(会引入 bias)
- 直接读 git diff,不靠 helper 名字推测
- Do NOT soften findings
- 每条 finding 必须 file:line + reproducer + 具体修法
逐条验证以下修复(对每条:trace 真实代码路径,判断是否真关掉了原始 bug,
以及修复本身是否引入新 bug):
1. <fix item 1>
位置:<file:line>
验证:<具体验证目标>
2. ...
新增/修改的测试也要逐条验证:
- <test file:line> — <claim 覆盖的 finding>
- 对每条:测试是否真覆盖目标?如果 mock 了 SQL,mock 行为是否忠实反映生产?
每条 deliverable 格式:
- Status: FIXED / PARTIAL / NOT_FIXED / NEW_BUG_INTRODUCED
- Trace: 走代码路径 (file:line) 说清楚为什么 fix 真的关掉原始 bug
- New concerns: 是否有 fix 自身的 bug 或暴露的旧问题
最终 verdict:
- ALL_CLEAN_SHIP_IT 如果所有条都干净
- BLOCKED + 剩余 must-fix 列表 (file:line + 具体修法)
不要软化结论。这是 production launch 前最后一道关。
(并行的 Claude staff engineer sub-agent 也在做同样的 re-review。
我会在拿到你的独立结果之前不告知 Claude 那份,避免锚定。)
两份 staff engineer re-review 出现分歧。下面是对方独立发现、你之前没列出的
N 条。请逐条 confirm 或 refute,理由带上具体代码 trace,不要软化。然后产出
两份合并后的最终 must-fix 清单(只列 launch 前必修的)。
A) [对方判 H/M/L] <issue 简述>
- <file:line>
- 场景:<reproducer>
- 修法建议:<concrete fix>
- 你确认这是真问题吗?
B) ...
最后,合并 must-fix 清单格式要求:
- 只列 launch 前必修的 (High + 影响业务的 Medium)
- 每条:位置、一句话描述、具体修法
- 排序按修复优先级
BRIDGE=~/.claude/skills/codex-bridge/bridge.py
WS=your-workspace-name # the workspace label as shown in Codex.app's sidebar
REPO=/absolute/path/to/your/repo
python3 $BRIDGE attach
python3 $BRIDGE list-workspaces
# Round 1, turn 1: implement
python3 $BRIDGE new --workspace "$WS" --prompt-file /tmp/codex-task-r1.md
python3 $BRIDGE wait
python3 $BRIDGE read --since-user --json > /tmp/codex-r1-out.json
# Round 1, in parallel:
# (a) ask Codex turn 2 to spawn its review sub-agent
# (b) launch Claude's own review sub-agent (Agent tool, run_in_background:true)
python3 $BRIDGE send --prompt-file /tmp/codex-rereview-r1.md
# (Claude background sub-agent launched separately via Agent tool)
python3 $BRIDGE wait # Codex sub-agent done
# Cross-compare
python3 $BRIDGE read --since-user --json > /tmp/codex-rereview-r1.json
# (Claude background sub-agent notification arrives via task system)
# If both ALL_CLEAN_SHIP_IT: done
# If BLOCKED on either side: write next-round fix prompt → next turnAfter each round, before deciding PASS/FAIL, Claude must check:
- Diff sanity:
git diffshows ONLY files relevant to the task. If Codex touched unrelated files, send a "revert these unrelated changes" prompt. - Test alignment: did the change break something that already worked? Run the test suite; verify pass count.
- SQL dialect (if applicable): if changes touch DB queries, run the
actual statements through
sqlite3(or whatever runtime is target) to confirm syntactic validity. Don't trust mock-based tests. - Both reviewers' verdicts: at least one disagreement = converge first via cross-check turn before any further fix dispatch.
- Edge cases: at least one edge case neither reviewer mentioned but the diff might miss. Force articulation before allowing PASS.
PASS only if all five green. Otherwise feed specifics back into feature thread.
- Codex hits a design decision the spec didn't cover (e.g. error-handling convention, naming, public API shape)
- The diff size grows beyond the task scope and Codex appears to be "tidying up" — confirm with user before more rounds
- Tests start failing in a way that suggests a deeper issue (incorrect assumption in the plan)
- Two reviewers disagree on something that requires runtime verification (SQL dialect, race condition timing) — ask whether to verify locally or trust one side
These are why bridge.py looks the way it does. Don't undo them.
Symptom: a single multi-line prompt creates multiple Codex threads, each with truncated content; first Codex reply says "your message got clipped."
Cause: agent-browser keyboard type <text> types each character including
\n as an Enter key event. Codex.app's ProseMirror binds Enter to "submit
message" in the empty/new-thread state, so each \n submits the buffer as
a new turn / sometimes a new thread.
Fix in bridge.py: use agent-browser keyboard inserttext (single
beforeinput event, ProseMirror inserts the whole block as paragraphs without
firing Enter).
Symptom: long prompt only inserts the first ~3000 chars into the editor.
Cause: WebView's document.execCommand('insertText', ...) has an undocumented
size cap on Codex.app's build. Streaming via CDP Input.insertText doesn't.
Fix in bridge.py: _insert_prompt uses agent-browser keyboard inserttext,
not execCommand.
Symptom: prompt fully inserted but Enter key just adds a paragraph break; nothing submits.
Cause: ProseMirror binds Enter to "insert paragraph" when the doc has multiple blocks; submit is gated on a different shortcut.
Fix in bridge.py: _click_send_button finds the rounded-full circular
send button at the bottom-right of the composer (size-token-button-composer rounded-full classes) and clicks it directly. Falls back to Meta+Enter
(macOS Cmd+Enter) if the button isn't located.
Symptom: wait returns "done" but git log shows no new commits, and read
returns Codex's mid-turn narration ("我现在先提交...") rather than a real
completion summary.
Cause: when Codex.app needs to run a sandboxed command (e.g. git add,
git commit, rm), it pauses and shows a numbered dialog asking the user
to approve. While the dialog is up, the stop button DISAPPEARS — Codex
isn't running anything. So wait's stop-button signal goes false and
declares the turn done. But Codex is actually frozen waiting for a click.
Fix in bridge.py: wait now detects the approval UI and treats it as a
busy signal. 2026-07 UI: the approval card is [data-codex-approval-surface]
with plain-text 允许一次 / 拒绝 buttons (old builds: a dialog with
aria-label="是" — kept as fallback). The card blocks wait until the user
clicks it OR wait auto-clicks 允许一次 via --auto-approve. Note the
composer's 替我审批 (default: only risky ops ask) vs 请求批准 (external file
edits + network always ask) mode chip decides which actions trigger the card
at all.
Pattern when running long automated workflows:
- Use
--auto-approvewhen you trust the dispatched prompt's command set (e.g. you instructed Codex to commit on a feature branch with norm). - Skip
--auto-approvewhen manual oversight is desired — the dialog appears in Codex.app's UI and the user clicks themselves.
Symptom: wait returns "done" but read returns Codex's preamble narration
(short), not the substantive final answer.
Cause: "正在思考" / "Thinking…" text only shows during model-token-generation phases. During tool calls (file reads, command runs) the text disappears. A 2-second quiet window across multiple model→tool→model transitions in one turn fooled the wait into declaring done while Codex was still working.
Fix in bridge.py: cmd_wait uses stop button visible as the canonical
busy signal. The stop button stays up for the WHOLE turn including tool
calls. Default --quiet-for=5.0 (was 2.0).
Symptom: read returns ~3000 chars, missing the final 5000-char answer.
Cause: not a transport-layer truncation. agent-browser via CDP can return
5KB+ strings cleanly. The "truncation" was wait declaring done early
(see above), so when read ran, only the early Codex narration block was
visible — the substantive answer hadn't been emitted yet.
Fix: fix wait. Don't add clipboard hacks to read. Verified by direct
agent-browser eval that 5313-char messages round-trip without loss.
Symptom: even after wait works correctly, read returns a short close-out
line, not the substantive output. Codex emits multiple markdown blocks per
turn (preamble narration + status updates + final answer); last block may
be a 200-char closing remark, not the 5000-char review.
Fix in bridge.py: added read --since-user mode. Returns ALL assistant
markdown blocks since the last user message, joined with \n\n---\n\n.
Use this for review/cross-check turns. Default read (last block only)
remains for short Q&A use.
Symptom: bridge.py send --prompt "...含 \"...\" 的内容..." exits with
unrecognized arguments or sends a truncated/garbled prompt.
Cause: bash interprets the embedded quotes as terminating the outer string.
Fix in bridge.py: added --prompt-file <path> to both new and send.
Use this for any non-trivial prompt; reserve --prompt for short literals.
Symptom: clicking the stop button stops the current generation, but Codex auto-restarts because there were queued user messages (from a prior fragmented prompt) waiting in the thread.
Workaround: archive the thread (sandbox may block this for the agent — ask the user to do it manually). Or accept the abandoned thread and start a fresh one.
Prevention: don't send fragmented prompts in the first place — use
--prompt-file and the fixed _insert_prompt.
Symptom: round-1 review found H1 (budget double-count), round-2 review done by Codex's sub-agent didn't catch a similar-severity issue, because the round-2 prompt forgot to re-state "Staff Engineer" role and rigor requirements.
Fix: ALWAYS spell out the role + rigor reqs in EVERY review prompt. Sub-agents don't share context across runs.
- Workspace must exist in the sidebar already. This skill doesn't create project folders. User has to add the repo as a Codex 项目 once.
- Single Codex.app window. Multiple threads in the same workspace are
fine — switch with
open-thread— but multiple windows aren't supported. - Auth piggybacks on the user's ChatGPT login. Token consumption hits their account; rate-limited == all bets off until reset.
- Archive is sandboxed. The agent's bridge can click stop but
bridge.py archivemay be blocked from external write rules. User archives manually for now.