Two AI models harden your plan before a line of code exists — then swap jobs to build it.
The plan that sounds finished usually isn't. In claudex-loop's first real run, a deeply-researched, interview-locked plan still contained one unbuildable subsystem and six designs that would have corrupted data — a rival model found all of them before any code existed.
Started as a fork of chaseai-yt/claudex-loop by Chase AI — an independent project since 2026-09-22. See Credits.
Why
AI-assisted coding fails in two places: the gap between you and Claude (do we agree on what to build?) and the gap between Claude and its own output (is the plan actually correct — and how would you even know?). The model that wrote the plan can't be trusted to grade it. That's an echo chamber.
Claudex-loop closes both gaps: Claude locks intent with you, then OpenAI Codex — a rival, cross-provider model — attacks the locked plan round after round until it can't find anything else wrong.
flowchart LR
A["🔍 RECON<br>codebase recon or<br>greenfield research"] --> L["📋 Assumptions Ledger<br>you confirm in one batch"]
L --> B["🎯 INTERROGATE<br>load-bearing questions<br>one at a time"]
B --> P["PLAN.md locked"]
P --> C["⚔️ PLAN REVIEW<br>the adversary attacks it<br>in a read-only sandbox"]
C -- REVISE --> R["the producer arbitrates<br>and revises"]
R -- same session --> C
C -- APPROVED --> S["✍️ You sign off"]
C -. MAX_ROUNDS:<br>you break the tie .-> S
S -. optional .-> D["🔨 BUILD<br>one model writes"]
D --> I["🔬 CROSS-INSPECT<br>the other model<br>grades the diff"]
I --> G["✅ You approve<br>the final diff"]
I -. default on,<br>a skip is logged .-> V["🧪 ACCEPTANCE GATE<br>dod · quality · security<br>+ docs · tests"]
V -. facing the network:<br>then it is required .-> X["🛡️ EXPOSURE PASS<br>own model, own effort<br>SAFE / UNSAFE"]
V --> G
X --> G
classDef producer fill:#d97757,stroke:#7a3a24,color:#fff
classDef adversary fill:#10a37f,stroke:#0a6b54,color:#fff
classDef human fill:#e8b93e,stroke:#8a6a14,color:#1a1a1a
classDef artifact fill:#3d3d3d,stroke:#6b6b6b,color:#fff
class A,B,R,D producer
class C,I,V,X adversary
class L,S,G human
class P artifact
You enter at four points only: confirming the ledger, answering the interview, signing off the converged plan, and approving the final diff if you build. Every reviewing step is read-only and never touches a file.
Orange is whoever produces, green is whoever grades — not Claude and Codex. The colours name roles, because here the actor behind each one is configuration (see The actor is configuration, not a name). In the delegation arrangement the boxes swap models without the diagram changing.
Dotted edges are conditional. Building is optional. The acceptance gate on the finished diff is on by default — it may be skipped (opt-out, or the Codex quota is out and no fallback is chosen), but only with a logged reason; when the change faces the network, it and its exposure pass are required and EXPOSURE: UNSAFE blocks the commit. A plan that ended at MAX_ROUNDS reaches the same path once you break the tie. Every resolved plan carries a claudex-gate: pending; due-after: <last plan step> marker, so a build in a later session still knows the gate is ahead of it — and after which step it is due. pending before that step is the normal state, not a debt: the gate compares finished work with the plan, so mid-build it can only answer INCOMPLETE. Solid edges always happen. audit, docs-backfill and setup are deliberately absent: they are not steps in this loop at all — see Beyond the plan.
The four phases
| What happens | What makes it different | |
|---|---|---|
| 🔍 0 — RECON | Claude scouts before asking you anything — explores the codebase and living docs, or on greenfield researches prior art, stacks, and known pitfalls (research depth is a gate you control, up to a multi-agent deep-research workflow) | Opens with an Assumptions Ledger: everything already resolved, batch-confirmed in one reply. The interview never wastes questions the code or research already answered |
| 🎯 1 — INTERROGATE | A visible decision map splits open decisions into load-bearing (asked one at a time) and cosmetic (batched, veto-by-exception) | Every question must justify its existence: why it matters, a committed recommendation, and what breaks if we guess wrong. Escape hatch: "accept all remaining recommendations" |
| ⚔️ 2 — REVIEW | Codex reviews PLAN.md in a read-only sandbox → VERDICT: APPROVED or REVISE with concrete flaws. Claude arbitrates (rejects bad critiques with logged reasons), revises — every passage that still states a superseded behaviour, acceptance criteria included, is changed or marked, so no revision says both — and resumes the same Codex session |
The reviewer remembers its prior findings and attacks its own accepted fixes. Bounded by MAX_ROUNDS — a flagged deadlock beats a fake "approved" |
| 🔨 3 — BUILD (optional) | You pick the builder. Codex builds (build, full write access) → Claude reads the entire diff like a contributor PR and runs the proof test itself. Claude builds → a fresh read-only Codex session cross-inspects the finished diff against the plan — on by default, findings arbitrated and logged. A finding that changes what must be built updates PLAN.md itself and reaches your gate as a post-approval plan change. The inspector gets its own prompt (acceptance criteria, every changed file in full, tests that only confirm the code) and judges the staged change: the index is pinned with git write-tree, and a commit whose tree differs from the inspected one is not committed as inspected |
The final code is always graded by the rival model, whichever one wrote it. Skipping the inspection requires an explicit, logged opt-out |
The invariant across all four: whoever made the thing never checks the thing. Plan by Claude → attacked by Codex. Code by Codex → reviewed by Claude. Code by Claude → inspected by Codex. No one grades their own work, in any path.
Two artifacts every run: PLAN.md (the what — including a Verification section: prove the change on the real output of the program that produces its input, and run old and new code over the same inputs when behaviour changes) and PLAN-REVIEW-LOG.md (the full round-by-round argument — the why).
Beyond the plan
Phase 3 closes with code-review, the acceptance gate on the finished diff — on by default, skippable only with a logged reason. It is also a skill in its own right, and so are the three below it: each runs on its own, on artefacts the loop never produces — a diff built without a plan, a codebase nobody reviewed, standing documentation debt, a repo not yet wired up.
| Skill | Judges | Verdict |
|---|---|---|
code-review |
the finished diff against the plan, in up to five dimensions — plus, for anything that faces the network, a separate exposure pass on its own model | DOD · QUALITY · SECURITY · DOCS · TESTS · EXPOSURE |
docs-backfill |
standing documentation debt, decoupled from any diff | DOCS: ACCURATE / INACCURATE |
audit |
a codebase nobody ever reviewed — no diff, no plan, no baseline | AUDIT: CLEAN / CONCERNS / CRITICAL per slice · EXPOSURE: SAFE / UNSAFE per exposed component |
setup |
nothing — it wires a repo up: wrapper, reviewer role, check catalogue, taboo scope, trust | a live read-only Codex call that must answer |
code-review gained docs and tests because a gate that never asks "is this documented" and "would the tests fail if the code were wrong" leaves the two cheapest defects in place. Add them whenever the diff changes behaviour.
With security in scope, Claude Code's own security-review runs first as a pre-filter (SECURITY_PREFILTER=on, since 2026-10-27): cheap, and it clears the obvious before Codex quota is spent. It never sets the SECURITY verdict and never counts as a pass — when Claude built the change it is the maker looking at its own work — and its findings are not shown to Codex, which reads the change cold.
Claude reviewers run without the session's tools (since 2026-10-27). A sub-agent without a tool list inherits everything the session has — measured: it found the write-capable claude.ai connectors (Notion, Gmail, Drive, …) through ToolSearch. When a reviewing role resolves to claude, it now runs as the plugin agent claudex-reader (tools: Read, Grep, Glob; measured: exactly those, no ToolSearch, no MCP); the analysts of the upcoming security hunt run as claudex-hunter with no tools at all. The main session keeps its own tools — the pre-filter and a Claude build run there, and that is stated, not hidden. Details: ROLES.md.
audit exists because code-review needs a diff and a plan, and an inherited repo has neither. It slices the repo, runs the deterministic tooling first so the model spends its attention where linters cannot reach, and produces a baseline — after which every later code-review only judges the delta instead of re-raising the same debt.
With SECURITY_HUNT=quick|standard|deep, audit adds an attack-class hunt after Cloudflare's security-audit method (since 2026-10-27; plan approved after seven Codex rounds). The skill is read from a byte-verified snapshot of one pinned commit, never from a live install. The hunters are claudex-hunter agents with no tools, working only on numbered excerpts of a frozen, redacted snapshot of the repository; target code is never executed. Every finding is then verified cold by the other model (security-verify, default Codex), bound to that record and that snapshot, with evidence only from the lines it was sent. Results go to their own report and verbatim ledger under docs/audit/, never into the shared log. What it does not do: the read-only verifier can still read the repository itself, as in every Codex review — a real read boundary comes with the container plan.
docs-backfill writes what the gates found missing. Claude writes, a fresh read-only session grades: a generator that emits docstrings and ships them has nobody checking whether they are true, and a confidently wrong docstring is worse than a missing one.
setup is the odd one out: it judges nothing, it installs. Per repo it wires the reviewer role, the project-specific check catalogue, the taboo scope (read-only means Codex doesn't write — everything it reads still goes to OpenAI), the trust entry, and the read-only wrapper below.
The actor is configuration, not a name
Upstream bakes the model into the skill name (codex-review reviews, codex-build builds). That reads fine until you want the other arrangement — then the name lies. Here the skills are named after the activity, and who performs it comes from .claudex.yaml:
roles:
plan: claude # Delegation: build: codex + code-review: claude
plan-review: codex # Dual draft: plan: [claude, codex] + plan-review: cross
build: claude
code-review: codex
exposure-review: codex # second grader of build — gpt-6.1-sol/medium by default, see ROLES.md
docs: claude
docs-review: codex
audit: codex
scripts/claudex_roles.py resolves it and refuses with a non-zero exit — not a warning — when an actor would grade its own work (producer_never_reviews) or a reviewing role would run with an open sandbox (write_access). Claude is the orchestrator, so a review role assigned to claude is rejected unless it enters as a fresh subagent. Full reasoning in ROLES.md.
One lesson worth stealing
Hand the reviewer numbered lines. The first real audit run produced sound findings at wrong addresses — citations landed on blank lines and unrelated statements, because the code went into the prompt unnumbered and the model had to count. Measured on the same 2,658-line slice: 5 of 23 citations on blank lines without numbering, 0 of 28 with it. A few percent more input tokens removes the entire class of error.
Receipts
From the first end-to-end greenfield run (a solo-creator CRM):
- 55 findings across 5 rounds — converging 26 → 15 → 12 → 2 → 0
- 1 fatal: an access-path architecture that could not be built as written (read as completely plausible)
- ~6 wrong models that would have shipped and corrupted data weeks later
- ~7 missing subsystems, including the homepage feature that had no backing data source
- What survived untouched: every product decision from the interview. The review only ever attacked how it would break — the phases genuinely divide the labor
And then this project turned the tools on themselves
That run is upstream's evidence for the plan loop. This project's own evidence is
harsher, because audit was pointed at the repo that ships it — a repo whose entire
product is controls:
-
89 findings resolved — 47 from the audit (7 read-only slices + 1 exposure session), 42 from four passes by a third reviewer over the fixes — CodeRabbit here, but the point is that it was neither of the two that produced them. Test suite 90 → 183.
-
Both headline controls could be walked around. The
PreToolUseguard letcodex_ro.py${IFS}&&<anything>through — bash splits control operators before it expands parameters, so it ran as two commands while the token no longer ended in the wrapper's name. And--allow-pathlet a call widen its own write confinement, turning an approved "read-only review" into an arbitrary delete. -
The fix for the first one had the same hole in a different spelling. A second reviewer found
$(...)and backticks doing the identical trick, because the substitution check ran after the recognition those forms defeat. -
A fourth pass, from a consumer repo, found two more CRITICALs in the wrapper on 2026-09-02 — and lifted a risk this audit had consciously accepted, correctly: the acceptance covered env prefixing and missed env inheritance.
-
And the worst one was not a hole at all: for three weeks the reviewer read nothing and said so nowhere. On Windows,
codex execwithout[windows] sandboxin the config selects no sandbox backend and refuses every shell call — reads included — while still exiting 0 with a fluent answer written from the prompt alone. This repo's owndocs/betrieb.mdhad recorded the symptom on day one and misattributed it to an untrackedPLAN.md; the workaround it prescribed (inline the plan text) made plan review work and thereby hid that no repo file was ever opened. It surfaced on 2026-09-16 in a consumer repo, after five rounds of plan-text-only review that every check had passed. Measured, corrected, and turned into a control: the wrapper pins the backend and exits 3 on a run that executed nothing.The lesson is not the bug. A control that fails loudly is a nuisance; one that fails silently while reporting success is worse than its absence, because it spends the trust it has not earned. Everything above this line was found by someone looking; this one had to be found by something watching.
-
A plan to close this exact blind spot went five rounds without approval. The
--expect-workdirdesign above is what came out: round 1 found a CRITICAL that inverted it — a selector flag, not an assertion, arriving unattended on the same prefix-matched allowlist entry as everything else the wrapper allows; round 3 found a fail-open the author introduced while fixing round 2 —os.path.realpath("")equals the cwd, so an unset$TARGETwould have confirmed itself.MAX_ROUNDSwas reached withoutVERDICT: APPROVED; the owner resolved it by explicit decision. The closingcode-reviewgate then ran and ended red after its permitted recheck: its first pass found theabspath()hole described above, the recheck confirmed the wrapper control closed, and its last finding — in the skills' contract tests — is fixed red-first but not yet re-verified by the reviewer. Written up as it is indocs/audit/2026-09-11-scope.md§10.
Three independent passes over the same 500 lines, each finding what the previous one
missed. That is the argument for the whole method, made against its own author.
Everything is written up with its verification in
docs/audit/2026-08-30-baseline.md — including
the findings that were rejected, and where a reviewer's advice was deliberately not
followed.
Upgrading to 2.6.1 — gpt-6.1-sol, codex-cli 0.162.1
2.6.1 changes the default model from gpt-6-sol to gpt-6.1-sol (effort stays medium), in the wrapper and in claudex_roles.py. It needs codex-cli ≥ 0.162.1; on 0.156.0 gpt-6.1-sol was answered with HTTP 400 (2026-10-10), and the wrapper refuses it on any older CLI (exit 2). gpt-6-sol keeps its own minimum (0.156.0) for anyone who pins it in .claudex.yaml. The wrapper is now measured against codex-cli 0.162.1 (preflight and acceptance of docs/betrieb.md section 8 passed 2026-10-12, log in docs/plans/2026-10-11-gpt-6.1-sol-wrapper-2.6.1/): six new default-on features are classified (browser_annotation_api, daemon_auto_start, in_app_voice switched off; ultrafast_mode, api_key_model_discovery, write_stdin_approval stay on), and three built-in MCP resource tools that 0.162.1 adds are measured empty under the isolation. The blind probe still ends in exit 3. Raise the CLI first (npm install -g @openai/codex@0.162.1), then update the copies of tools/codex_ro.py.
Upgrading to 2.6.0 — Codex 0.156.0, gpt-6-sol, an isolated reviewer
gpt-5.6-terra was superseded by gpt-6-sol on 2026-09-22; every Codex role now defaults to
gpt-6-sol/medium (a real plan-review round measured ~4:48 min). codex-cli 0.149.1 answers
gpt-6-sol with HTTP 400 on every call, so the CLI comes first:
npm install -g @openai/codex@0.156.0 # 2.6.0, 2026-09-30 (today: see 2.6.1 above)
What stops on purpose:
- An older CLI with
gpt-6-solis refused up front — exit 2, with the install line — instead of failing inside Codex. The wrapper asks the binary for its version once and shows it in its header. - A
.codex/config.tomlin or above the reviewed directory is refused — exit 2. Measured: in a repo trusted in the user config, Codex loaded that file and obeyed adeveloper_instructionsline in it; the repo under review steered its own review. - On Windows, Codex is no longer started through
codex.cmd. A batch file runs throughcmd.exe, which re-reads its arguments; the wrapper now starts what the npm starter would —node.exewith the package'scodex.js— directly, or acodex.exe, and refuses t