| name | codex-loop |
| description | Orient for a pair-programming loop where Claude builds and Codex is the adversarial validator. Use when the user wants to build/harden a feature with Codex as the cross-model reviewer ("pair with codex", "build-break-assess", "have codex review/break this in a loop", "harden X with codex as the gate"). Installs the scope governor that keeps adversarial review from becoming maximalism. |
Codex Loop — Build / Break / Assess
You build. Codex breaks. You triage. This skill is the governor that keeps that
loop converging instead of grinding to exhaustion.
The loop is load-bearing in all three seats — but the third seat (ASSESS) is the
one that's usually unstated and the one that fails. Codex sharpens every edge; it
has no opinion on whether an edge is in scope. That judgment is yours and the
user's, and it must be made explicit before round 1, not discovered at round 9.
Hard invariants (do not violate)
-
Cross-model only. The BREAK seat must be a different model — Codex via the
MCP driver. Never let Claude (including Claude subagents/workflows) adversarially
review Claude's own work. That blind spot is the entire reason for a combined front.
- BUILD seat: Claude (workflows fine).
- BREAK seat: Codex only (via
codex_break.py) — it cannot run inside a Claude Workflow.
- FIX seat: Claude, but fixes must address Codex's findings, not Claude self-review.
- VERIFY/run-tests: either model (mechanical, not judgment).
-
Iteration is not failure. On a critical/safety feature built from a skinny
spec, many rounds are expected — in a human org this might take 15–20 iterations.
Do not narrate worst-case exits or pre-offer "is this worth it / should we
defer" off-ramps while a sound loop is converging. It reads as anticipating
failure. Report the round's findings + what you're driving, and keep going.
Reserve a genuine "change course?" escalation for an actual stall (fixes
repeatedly spawning fresh same-class P1s with no convergence) — not routine depth.
-
Governed, not unbounded. Iterating without panic AND installing a scope
governor are complementary, not in tension. Triage out-of-model from round 1;
checkpoint with the human ~every 5 rounds.
Step 0 — Frame BEFORE round 1 (non-negotiable)
Write these down explicitly and confirm with the user. This is what was missing the
time this took 20 rounds.
- Product promise: what this feature is for, in one line.
- Threat model — what's IN scope: the user surface area — everything the tool
exposes via CLI / runtime behavior. Defend this.
- Threat model — what's OUT of scope: filesystem omnipotence. An actor who
can already write
$HOME / owns the data dir can destroy it directly — hardening
against every malicious local-FS arrangement is bunker cosplay, not safety.
- Definition of done = the exit criteria below, NOT "Codex can no longer invent
an attack."
Two modes — pick the center of gravity
The loop runs the same in both; what BUILD produces and how Codex BREAKs it differ.
Don't default to Mode B just because the examples are diff-shaped — pick the mode that
matches where the work actually is.
Mode A — Design audit (pre-build, no code yet) ← first-class, not a footnote
The subject is a plan / spec / architecture. Common maiden case: validate the design
before writing any code, so Codex attacks the idea, not a diff. This is also where
Step 0 pays off most — without a written frame, the adversary invents attacks against a
problem that may not exist (falsify your own premises by measurement first).
- BUILD = Claude drafts / refines the design or plan.
- BREAK = Codex read-only via the MCP driver (see "Running Codex"), fed the Step-0
frame so it attacks the real boundary. Write the frame+ask to a file, then run:
cat > /tmp/break.txt <<'EOF'
Adversarial design review — find where this PLAN fails; do not write code.
Product promise: <...>. In scope: <user surface>. Out of scope: <FS-omnipotence /
things we don't control>. Hunt for: silent data loss by design, states that don't
fail closed, invariants the design can't hold, complexity disproportionate to threat.
EOF
python3 ~/.claude/skills/codex-loop/codex_break.py \
--prompt-file /tmp/break.txt --cwd <repo> --effort medium --silence 120 --wall 420
- EXIT when the design has no silent-data-loss path, fails closed by construction, and
complexity is proportional. Then graduate to Mode B for the implementation.
Mode B — Build / break (a diff exists)
The subject is code. BUILD = implement/fix on a branch or worktree. BREAK = Codex
reviewing the diff via the MCP driver, framed as an exec prompt against an explicit
diff range. Do NOT reach for codex review --base <branch> "<prompt>": that form is
rejected by the CLI parser (see "Running Codex"), and --base <branch> on a worktree
diffs against the current branch tip, not the PR's branch point.
Compute the merge-base, write the frame, run the driver from the worktree:
cd <worktree>
MB=$(git merge-base HEAD <base-branch>)
cat > /tmp/break.txt <<EOF
You are the BREAK seat in an adversarial code review. Read-only; do NOT write code.
Inspect the diff with: git diff ${MB}..HEAD
Product promise: <...>. In scope: user surface via CLI/runtime. Out of scope: an actor
who already owns the data dir. Hunt for reasonable CLI/user paths that silently
destroy/corrupt data or leave a state that does not fail closed.
Report findings as: [P1|P2|P3] <one line> — <file>:<line>. Then stop.
EOF
python3 ~/.claude/skills/codex-loop/codex_break.py \
--prompt-file /tmp/break.txt --cwd <worktree> --effort medium --silence 120 --wall 420
Keep Codex in the BREAK seat in both modes — read-only, never let it fix.
Running Codex (operational — learned the hard way)
The loop
Step 0 FRAME (Claude + user) — product promise + threat model + done
│
├─► BUILD (Claude) — Mode A: draft design | Mode B: implement / fix
│
├─► BREAK (Codex via MCP) — both modes: codex_break.py against the diff/plan
│
├─► ASSESS (Claude) — triage every finding through the rubric
│
└─► loop until exit criteria hold; HARD CHECKPOINT at ~5 rounds
ASSESS — the triage rubric
Codex output is input, not a mandate. Bucket every finding:
- Correctness / safety defect (data loss, broken invariant, security on the
in-scope surface) → FIX. Non-negotiable.
- Design-direction opinion → weigh against product intent + bounded scope →
accept / defer-and-flag / decline. A real-but-out-of-current-scope pivot gets
flagged as a separate decision, not folded into a correctness branch.
- Out-of-model (theoretical attacker with
$HOME write; operator bypasses the
CLI to mutate files — unless the CLI caused it) → DOCUMENT / REJECT. Do this
from round 1.
- Volume / repetition of findings → a smell, not a checklist. Step back and
ask if the design is wrong; don't patch round after round.
- Taste / nits → batch or skip.
The scope governor
HARD CAP ~5 adversarial rounds. This is a brake, not a status report. At the
cap you stop by default and bring the human a checkpoint; continuing to round 6+
requires explicit user go-ahead, not operator momentum. "Keep going while
converging" (invariant #2) governs rounds within the cap — it is not a license
to ride past it because each round still improves something. If you find yourself at
round 6+ without having asked, the governor has already failed.
Checkpoint payload:
- remaining findings (each classified — see protocol below)
- accepted fixes
- rejected out-of-scope threats (and why)
- complexity delta — how much cleverness this added
- whether the feature still matches the original user promise
Checkpoint decision protocol (resolves disputed findings)
The exit criteria are judgment calls, so a determined adversary can keep reframing
"reasonable" while ASSESS keeps replying "out of scope" — a vibes stalemate. Break it
by forcing every remaining finding into exactly one bucket, with a named owner:
| Bucket | Meaning | Owner |
|---|
fix-now | in-scope correctness/safety defect | Claude (this round) |
prove-with-test | claimed risk on existing behavior → convert to a passing test | Claude |
document-boundary | out-of-model; record why and move on | Claude + user |
separate-decision | real but a product/scope pivot → its own ticket, not folded in | user |
Default stop rule: the loop exits when every in-scope P1/P2 defect has either a
landed fix or a red→green test, and no finding remains unclassified. A finding BREAK
calls "still exploitable" that ASSESS calls "out of scope" is not left open — it
resolves into document-boundary or separate-decision, with the user as tiebreaker
on the latter. Disagreement forces a classification, never another round.
Exit criteria (stop when these hold — NOT when the adversary runs dry)
- No reasonable CLI / user-path can silently destroy data.
- Unexpected states fail closed.
- Complexity stays proportional to the threat model.
Complexity-as-risk
Safety features become unsafe when they get too clever. A rising complexity delta
is itself a risk signal — each added cleverness spawns its own findings (a crash-safety
marker becomes a new attack surface; a clever cross-platform lock-bridge breaks the
very migrations it guards). When a fix is more intricate than the threat it answers,
the simpler design is usually the safer one. Prefer deleting cleverness over hardening it.
Anti-patterns (caught the hard way)
- Treating every Codex note as must-fix → 4+ wasted rounds and near-misses where a
design pivot almost folded into a correctness branch.
- Running to round 20 because "Codex can still invent an attack." Ghosts. Stop at the
exit criteria.
- Hedging about "trajectory" / pre-offering defer ramps 3 rounds in. Hold the line.
- Letting Claude review Claude's own work and calling it adversarial.
- Discovering the threat-model boundary informally at round 9 instead of writing it
at Step 0.
- Reading frustration into a neutral operational observation and pre-apologizing —
another face of anticipating failure. Take the observation literally, fix the
mechanism (e.g. the timeout), skip the emotional read.
After the loop
Record what was learned in continuity (continuity remember) — convergence point,
any threat-model boundary refinements, and complexity decisions worth carrying forward.