- name
- codex-debate
- description
- Run an automated codex⇄Claude debate to consensus — no round cap, no deadlock exit. Two explicit subcommands. `review` (also the bare/back-compat default) — codex (reviewer) critiques the current diff and a Claude subagent (author) fixes/disputes, looping until they agree. `answer` — Claude and codex each answer a freeform prompt in parallel, then cross-check until they agree, and a unified answer is returned. Use when the user types `/codex-debate`, asks to "have codex review this", "run the codex debate", "review this PR with codex", "argue this with codex until you agree", or passes a question to "have Claude and codex debate/answer until they agree".
- argument-hint
- review [<pr-number>] [--base <branch>] [--no-commit] [--no-comment] [--rationale <note>] [--context <note>] | answer "<prompt>"
# Codex ⇄ Claude debate
This skill runs an automated debate between **codex** and **Claude** that loops to
consensus with no round cap and no deadlock exit. It has **two modes**, selected by
an **explicit leading subcommand** — never by guessing from the argument's shape:
- **`review`** — codex reviews the current diff, a Claude author fixes/disputes,
round after round until they agree, and the trail is **committed + posted to the
PR** (a mutating, outward-facing mode). This is everything from
[Review mode](#review-mode) down.
- **`answer`** — Claude and codex **each answer a freeform prompt in parallel**,
then **cross-check each other** until both agree, and a **unified answer** is
returned to you (read-only; plus a saved transcript). See
[Answer mode](#answer-mode).
The two modes have **different side-effect contracts** (review mutates + writes to
the PR; answer is read-only), so the mode is chosen **explicitly**, not inferred
from whether the argument looks like a PR number or like prose. Inferring a
mutating action from prose shape is exactly the coupling this design avoids.
## Mode detection (do this first)
Look at the **first whitespace-delimited token** of `$ARGUMENTS`:
- **`answer`** → **answer mode**. The prompt is everything after the `answer`
token. Jump to [Answer mode](#answer-mode); the review-mode steps do not apply.
- **`review`** → **review mode**. The remaining args are the review grammar
(`[<pr-number>] [--base …] [--no-commit] [--no-comment] [--rationale <note>]
[--context <note>]`). Continue with [Review mode](#review-mode).
- **No args, OR the first token is a number (a PR number) or a `--flag`** →
**review mode** (the backward-compatible bare alias for the original
`/codex-debate [<pr>] [flags]`, so existing callers like `/be-review` keep
working). Continue with [Review mode](#review-mode).
- **Anything else** (freeform prose with no recognized subcommand) → **ambiguous**.
Do **not** guess — ask the user to pick an explicit mode and stop, e.g.: "Did you
mean `/codex-debate answer \"<your prompt>\"` (read-only) or `/codex-debate review
[<pr>] [flags]` (mutating)?" Only the safe, backward-compatible review grammar
auto-routes; prose never silently triggers a mode.
Both modes require Claude Code's **`Workflow` tool** (the engine). Under
codex/opencode runtimes the skill is inert.
<a id="review-mode"></a>
# Review mode — Codex ⇄ Claude review debate
Automate the back-and-forth you'd otherwise courier by hand: **codex** (the
reviewer) critiques the current change, a **Claude subagent** (the author)
fixes what it agrees with and disputes what it doesn't, codex re-reviews, and so
on — round after round, **until they reach consensus**. codex reviews from a
**warm session**: round 1 cold-starts the reviewer, and every later round
*resumes that same codex session* (`codex exec resume`), so codex carries its own
prior review and reasoning forward instead of reconstructing it from the diff +
rebuttal each round — when Claude disputes a finding, codex argues from its
original rationale. There is no round cap and
no "deadlock" surrender: a debate that quits without agreement defeats the
purpose, so the two sides keep arguing until one concedes. You stay out of the
middle: each round lands as its own commit whose
message carries the debate context (codex's findings + Claude's dispositions) so
the PR history reads as the debate, and the summary is **posted to the PR** as a
comment at the end.
## Why this shape
The two sides are asymmetric, and that asymmetry is the whole design:
- **codex** is CLI-invokable headlessly (`codex exec`, authed via ChatGPT), so it
runs from a shell command.
- **Claude on a Max plan is *not* headless** — `claude -p` doesn't work with Max
auth. But the **Workflow tool's `agent()` spawns Claude subagents through the
harness**, not `claude -p`, so it works. That subagent is the author side.
So the debate runs as a Workflow: `agent()` is Claude, a Bash-invoked
`codex exec` is the reviewer, and the script couriers structured verdicts
between them and decides when they agree. Both sides are forced to emit
schema-constrained JSON, so consensus is detected in code, not by vibes.
**This skill requires Claude Code's `Workflow` tool** (it is the engine). Under
codex/opencode runtimes the skill is inert.
## Arguments
A leading `review` subcommand token, if present, is consumed by mode detection;
what remains is `[<pr-number>] [--base <branch>] [--no-commit] [--no-comment]
[--rationale <note>] [--context <note>]` (the bare alias passes the whole argument
string through unchanged). Parse:
- **`<pr-number>`** (optional): a PR to debate. If given, `gh pr checkout <n>`
first and default the base to that PR's base branch. If omitted, debate the
**current branch's** working-tree diff.
- **`--base <branch>`**: ref to diff against. Always a **remote-tracking ref**, never
a stale local branch. Default: `origin/<PR base>` when a PR number is given, else
the repo default branch as `git symbolic-ref --short refs/remotes/origin/HEAD`
(e.g. `origin/master`) — used **as-is**, NOT stripped to local `master` (which
can lag the remote). Fallback `origin/master`. Step 1 runs `git fetch origin`
first so the ref is current. The workflow then resolves this to the **merge-base**
of `base` and HEAD and diffs against that, so commits `base` gained since the
branch forked aren't reviewed as part of this change.
- **`--no-commit`**: don't commit per round — leave all agreed changes
uncommitted in the working tree for you to commit yourself. Default is to
**commit each round** (see below).
- **`--no-comment`**: don't post the debate summary to the PR. By **default**, when
a PR exists, the debate summary IS posted as a PR comment (see step 3). Pass
this to suppress the outward-facing write and report in chat only.
- **`--rationale <note>`** (optional): the author's note on **deliberate** design
decisions. Threaded into **both** sides — codex's round-1 review prompt (so it
doesn't flag intentional choices as defects; its warm session carries the note
across later rounds) and the Claude author's prompt **every round** (so it
*disputes*, rather than "fixes", a finding that contradicts a deliberate choice).
Mirrors `/lens-debate`'s `rationale`. Pull it from the PR/issue description, or the
caller (`/be-review`) passes the change rationale straight through.
- **`--context <note>`** (optional): the **main-agent context** the Claude author
should **inherit** — what this change is FOR (its task/intent and key decisions the
orchestrator already holds). Injected into the author's prompt **every round** so it
no longer reconstructs intent from the diff alone (`agent()` is one-shot and can't
be resumed the way codex is, so re-injection is how it inherits at all). Given to
the **author only**, not codex — codex stays an independent reviewer of the code,
not the author's narrative. `/be-review` passes the task context through.
## Steps
### 1. Resolve context
- Determine `repoPath` (the worktree root, normally the cwd).
- **`git fetch origin`** so remote-tracking refs are current — the base is an
`origin/...` ref, and a stale one would diff against the wrong tree.
- Resolve `base` per the rules above (a remote-tracking ref like `origin/master`).
- If a PR number was given, `gh pr checkout <n>` and confirm the branch.
- Confirm there is a non-empty diff: `git diff --stat <base>`. If empty, tell the
user there's nothing to review and stop.
- **Preflight codex**: `codex login status`. If not logged in, stop and tell the
user to run `codex login` (suggest the `!` prefix to do it in-session).
### 2. Run the debate Workflow
Invoke the **`Workflow` tool** pointing at this skill's committed script, passing
context through `args`:
```
Workflow({
scriptPath: ".claude/skills/codex-debate/debate.workflow.js",
args: {
repoPath: "<worktree root>", // also the per-worktree scratch dir root
base: "<base branch>",
commit: <false only if --no-commit>,
skillDir: ".claude/skills/codex-debate",
context: "<main-agent context the author inherits; omit/'' if none>",
rationale: "<author's note on deliberate decisions; omit/'' if none>"
}
})
```
The workflow runs in the background and notifies you when it completes. It
alternates `codex:roundN` and `claude:roundN` agents under a **Debate** phase —
the user can watch live via `/workflows`. Each Claude round edits the working
tree and (unless `--no-commit`) **commits exactly that round's changed files in
the same session** — one commit per round, with a message embedding the round's
codex findings and Claude's dispositions — never pushing or merging. (The commit
is no longer a separate `commit:roundN` agent: the author already has the tree
open, so it commits its own round.)
Ephemeral scratch (verdicts, the debate ledger) lives under the gitignored,
per-worktree `<repoPath>/.codex-debate/`, so **parallel debates in different
worktrees never collide** and the scratch never shows up in the diff codex
reviews. It returns:
```
{ status: "consensus" | "commit-incomplete" | "section-incomplete" | "reviewer-error",
rounds, base, finalVerdict, filesChanged, commitGaps, sectionGaps, transcript,
commentHeader, // the comment's small deterministic header (badge + round count + effort + base)
workDir, sectionGlob } // where the per-round section files live; cat them under the header (step 3)
```
(each `transcript[]` round also carries a `commit` SHA when that round committed;
`commitGaps` lists the round numbers whose author edited files but returned no
commit SHA — empty unless `status === "commit-incomplete"`; `sectionGaps` lists the
round numbers whose author left no disposition section file — empty unless
`status === "section-incomplete"`.)
There is one more, **earlier** terminus the workflow can return **before** the
debate loop even starts — `merge-base-error`. If `git merge-base <base> HEAD`
fails (a missing/typoed/stale base, or unrelated history), the diff scope can't be
trusted, so the workflow **aborts up front** rather than review the base branch's
drift as if this change made it. It returns a **different, smaller shape** — no
`commentHeader`, `workDir`, or `sectionGlob`, because no debate (and so no section
files) ever ran:
```
{ status: "merge-base-error", base, rounds: 0, transcript: [], finalVerdict: null,
note } // human-readable: which base failed and how to fix it (e.g. `git fetch`)
```
The debate is recorded as small Markdown section files under `<workDir>` — **two
per round**: `section-NNN-1-codex.md` (codex's verdict + findings) and
`section-NNN-2-claude.md` (the author's per-finding dispositions), zero-padded so
the `section-*.md` glob sorts in chronological order. **Each file is written by the
party that owns its content**: a Haiku writer renders codex's small *structured*
verdict to disk, and the **author writes its own dispositions directly** — the same
way codex writes its verdict to a path. That author-writes-its-own-file design is
deliberate: it keeps the author's *structured* return a **minimal ack**
(`filesChanged`/`commitSha`/`done`), so a big multi-finding narrative can never
overflow the structured-output encoding (the failure that used to crash the debate
on large diffs). The author's disposition file does triple duty — it's the author's
cross-round **memory**, the **rebuttal** codex reads next round (`codex-review.sh`
cats it straight into codex's prompt), and the **published comment** (step 3 cats
the section files under `commentHeader`). So the comment is still a **deterministic**
render — a shell `cat`, never re-improvised through an agent, nothing weak ever
retyping a large blob. codex is *not* a memory reader — it keeps its own warm
session and only ever reads the one rebuttal file.
- **consensus** — every finding codex raised is resolved (any severity — Claude
fixed it or codex conceded the dispute). This is the *only* way the debate ends
*normally*: it keeps running rounds until codex and Claude agree on every point,
with no round cap and no deadlock exit. (The harness's own
per-workflow agent backstop is the sole hard ceiling; if you ever need to stop
a debate by hand, interrupt it via `/workflows` or `TaskStop`.)
- **commit-incomplete** — the debate *converged* (codex approved, nothing open),
but a round's author edited files yet returned **no commit SHA**, so its
in-session commit didn't land and the "one commit per round" contract broke for
the round(s) in `commitGaps`. The edits are **not lost** — they stay in the
working tree and the next reviewer diffs them against the base — but this is
**not** a clean consensus: a human must reconcile the uncommitted round(s)
(e.g. commit the outstanding tree) before relying on the per-round history. Do
**not** report it as a plain consensus (see step 3).
- **section-incomplete** — the debate *converged*, but a round's author **skipped
or under-filled its disposition section file** (`section-NNN-2-claude.md` missing,
empty, or missing a backticked marker for an open finding, for the round(s) in
`sectionGaps`). That file is the hole-free trail everyone draws on —
the author's memory, the rebuttal codex reads next round, and part of the posted
comment — so a miss means the published record has a gap. The code guards against
feeding an empty rebuttal to codex (it warns and keeps the prior pointer), and the
tree edits are still present, but this is **not** a clean consensus: a human must
fill in the missing round(s) before trusting the per-round record. Do **not**
report it as a plain consensus (see step 3).
- **merge-base-error** — an *up-front* abort, **before** any debate round runs:
`git merge-base <base> HEAD` failed (missing/typoed/stale base, or unrelated
history), so the review scope can't be trusted. The return carries a human-readable
`note` (which base failed, how to fix it — e.g. `git fetch`) and **none** of the
comment-assembly fields (`commentHeader`/`workDir`/`sectionGlob`), because no
section files were ever written. Report the scope failure and **skip comment
assembly/posting entirely** (see step 3); fix the base ref and re-run.
- **reviewer-error** — the one *abnormal* terminus: codex itself failed to
produce a verdict (broken/unavailable CLI), so the workflow synthesized an
error verdict and aborted rather than spin forever on a dead reviewer. This is
**infrastructure failure, not a debate outcome** — `finalVerdict.summary`
carries the failure detail (including how many attempts were made). Do **not**
treat it as consensus (see step 3). **Transient failures are retried first:**
`codex-review.sh` retries the `codex exec` invocation with linear backoff
(default 3 attempts; tune via `CODEX_REVIEW_RETRIES` / `CODEX_REVIEW_BACKOFF`)
and only synthesizes the reviewer-error verdict once every attempt comes back
empty — so a single codex hiccup no longer sinks the round.
### 3. Present the result
**First branch on `status`.** If `status === "merge-base-error"`, the workflow
**aborted before any debate ran** — `git merge-base <base> HEAD` failed, so the
review scope couldn't be trusted. This return has **no** `commentHeader`,
`workDir`, or `sectionGlob` (no section files exist), so there is **nothing to
assemble**: do **not** run the posting block below. Report the scope failure —
surface the return's `note` (it names the failing base and the fix) — tell the
user to repair the base ref (e.g. `git fetch`, fix a typo'd/stale ref) and re-run,
and **skip the rest of this section**.
If `status === "reviewer-error"`, the debate did
**not** reach consensus — codex never produced a real verdict. Report it as a
**failure**, not a success: surface `finalVerdict.summary` (and the workflow log)
so the user sees codex was broken/unavailable, and tell them to fix codex (e.g.
`codex login`, check the CLI) and re-run. Do **not** post a consensus badge or a
`## Codex ⇄ Claude debate` PR comment for this path — there is no agreement to
report. Skip the rest of this section.
If `status === "commit-incomplete"`, the debate converged but at least one round
left its edits **uncommitted** (the round numbers are in `commitGaps`). Report it
as **converged-but-not-clean**: assemble and post the comment (see the posting
block below — `commentHeader` already shows a `⚠️` badge, not the consensus
check), then tell the user which round(s) are uncommitted and that the outstanding
tree must be committed before the per-round history can be trusted. Do **not** call
it a clean consensus.
If `status === "section-incomplete"`, the debate converged but at least one round's
author **skipped or under-filled its disposition section file** (missing, empty, or
omitting a marker for an open finding; round numbers in `sectionGaps`).
Report it as **converged-but-not-clean**: assemble and post the comment as usual
View on GitHub