{"slug": "how-to-make-codex-review-claude-code-s-commits-done-means-house-rules-and-round", "title": "How to make Codex review Claude Code's commits: done means, house rules and round rules", "summary": "A developer has published a follow-up guide to a two-engine AI coding workflow in which Claude Code writes commits and Codex reviews them read-only, adding three mechanisms to stop review loops from running indefinitely: a line-by-line \"Done means\" definition of done, project-wide house rules, and round rules that cap iterations. The approach requires each new test to be run against the base commit to prove it fails before the change, and the free MIT-licensed gate script at github.com/IliyaOblakov/two-engines-gate refuses to merge unless both a passing test run and an ACCEPT verdict exist.", "body_md": "In [my last post](https://dev.to/iliyaoblakov/how-i-make-claude-code-and-codex-review-each-others-work-5dmh) I showed the basic loop: Claude Code writes in its own worktree, Codex reviews the commit read-only, the tests run on that same commit, and a script refuses the merge unless both pieces of evidence exist.\n\nThat loop has a weak spot. A reviewer that doesn't know what \"done\" means reviews against its own taste. It flags style, misses the one requirement that mattered, and finds something new every round. The loop never ends, or it ends because you got tired.\n\nThis post fixes that with three things: a definition of done the reviewer checks line by line, house rules it checks the same way, and round rules so the loop stops. I've been building this since the first Codex and Claude CLIs, and these three go into every review on my own projects.\n\nA review without a finish line is an opinion.\n\nClaude Code writes. It commits each change on a branch and stops. Codex reviews that commit in a read-only sandbox: it can read files and run read-only commands like `git diff`, but it can't write anything. The prompt tells it never to hand back a patch, so every fix goes back to Claude Code as a new commit, and the new commit gets a new review. The verdict is one line, `Verdict: ACCEPT` or `Verdict: CHANGES REQUIRED`, saved in a file named after the commit it reviewed.\n\nIf you want that part ready to run, the free gate is at [github.com/IliyaOblakov/two-engines-gate](https://github.com/IliyaOblakov/two-engines-gate) (MIT). Everything below sits on top of it, or on top of your own scripts.\n\nMost review noise starts before any code exists. If the only instruction Claude Code got was \"add CSV export\", the reviewer has nothing to check the result against except its own opinion.\n\nSo every task starts with a short brief. Mine has four parts:\n\n```\n# Brief: CSV export for the orders page\n\n## Done means\n1. A \"Download CSV\" button on /orders downloads the orders currently\n   shown, with the current filters applied.\n2. Columns, in order: id, created_at (ISO 8601, UTC), customer_email,\n   total (two decimals, no currency sign), status.\n3. A customer email that contains a comma or a quote is escaped, so\n   the file opens in a spreadsheet with the right columns.\n4. A new test covers point 3 and fails on the code before this change.\n\n## Must stay exactly as it is\n- The JSON response of GET /api/orders.\n- Existing filters and their URL parameters.\n\n## How it is checked\n- Point 1: the route and the button in the diff.\n- Points 2 and 3: the new test.\n- Point 4: the test run once against the base commit, output pasted\n  in the commit body.\n\n## Out of scope\n- Exports for other pages. Scheduled exports.\n```\n\nEvery line under \"Done means\" has to be something a reviewer can confirm from the diff or from a test. \"Export works well\" isn't a line. \"A customer email with a comma is escaped\" is.\n\nPoint 4 is worth stealing on its own. The engine that writes the code often writes the tests too, and a test that already passes on the old code proves nothing about the change. Ask the writer to run each new test once against the base commit and show that it failed there.\n\nWhere the brief lives matters. Commit it with the change, or keep it under `.ai/`. The free gate refuses to review a checkout with uncommitted changes or untracked files anywhere else, and it skips `.ai/` for exactly this kind of working file. If you add `.ai/` to your `.gitignore`, commit that change too, or the edited `.gitignore` itself gets the review refused.\n\nThe brief covers this task. House rules cover every task: how errors are handled, where config lives, what never goes into a log. The things you'd otherwise repeat in every review.\n\nWrite them like the brief, as things a reviewer can find in a diff:\n\n```\n## House rules\n- No new dependency without a line in the commit body saying why.\n- Config comes from environment variables read in config.ts only.\n  No process.env anywhere else.\n- Never log a full email address, token or request body.\n- User-facing strings go through t(), not hard-coded text.\n```\n\nPut them in the files both engines already read: CLAUDE.md for Claude Code, AGENTS.md for Codex. The writer follows them while writing, and the reviewer checks against the same text. Keep the AGENTS.md section short: Codex stops adding instruction files once their combined size reaches `project_doc_max_bytes`, 32 KiB by default.\n\nWrite the rule once. Check it every time.\n\nNow change what you ask for. Instead of \"review this diff\", ask for an answer per line:\n\n```\nThe brief for this change is in .ai/brief.md.\n\nFor each line under \"Done means\" and each house rule, answer one of:\n- met: give the file:line in the diff that shows it\n- not met: give the file:line and what is missing\n- not checkable: say what evidence would be needed\n\nTreat the commit message and the writer's summary as claims to verify,\nnot as evidence. If a section is empty, say \"not given\" instead of\nguessing what it should have said.\n\nFindings outside the brief and the house rules need a concrete failure:\nthe input or steps, and what goes wrong. Style preferences are not\nfindings.\n\nKeep the verdict rule: exactly one line, Verdict: ACCEPT or\nVerdict: CHANGES REQUIRED. ACCEPT only when every line above is met.\n```\n\nWith the free gate, add this to `review-prompt.md`, which exists to be edited with your project rules. With your own scripts, append it to whatever prompt you send Codex.\n\nThe reviewer now checks a list instead of free-styling. And a \"not checkable\" answer is a finding about your brief: you wrote a requirement nobody can verify.\n\nWithout round rules, a reviewer at high effort can find a new nit every round, and the change never lands. So each round gets one job:\n\nAdd the rules to the prompt:\n\n```\nRound rules. Round 1: list every finding. Round 2: confirm the fixes and\nadd only what the fixes broke. Round 3 and later: only blockers and\nsecurity findings may hold the merge; mark everything else as a nit for\nthe follow-ups file.\n```\n\nThe reviewer also needs to know which round it's in. Count rounds in a small file per change:\n\n``` bash\n#!/usr/bin/env bash\n# rounds.sh <change-name>: count review rounds for one change; exit 3 at the cap\nname=\"${1:?usage: rounds.sh <change-name>}\"; max=\"${MAX_ROUNDS:-3}\"\ncase \"$max\" in ''|*[!0-9]*) echo \"MAX_ROUNDS must be a whole number\" >&2; exit 1 ;; esac\nroot=\"$(git rev-parse --show-toplevel)\" && cd \"$root\" || exit 1\nf=\".ai/reviews/$name.rounds\"\nmkdir -p .ai/reviews\nn=0; [ -f \"$f\" ] && n=$(wc -l < \"$f\")\nn=$((n + 1))\nif [ \"$n\" -gt \"$max\" ]; then\n  echo \"last round reached for $name; a person decides\" >&2\n  exit 3\nfi\necho \"$n $(git rev-parse --short=12 HEAD) $(date -u +%FT%TZ)\" >> \"$f\"\necho \"round $n of $max\"\n```\n\nThen feed the count into the prompt. With the free gate, a short wrapper does it. Save it as `review-round.sh` in the root of your repository, in the same folder as `rounds.sh`, and run it from that root:\n\n``` bash\n#!/usr/bin/env bash\n# review-round.sh <change-name>: one counted review round with the free gate\nround=\"$(bash rounds.sh \"$1\")\" || exit\n{ cat path/to/two-engines-gate/review-prompt.md; printf '\\nThis is %s.\\n' \"$round\"; } > .ai/prompt.md\nREVIEW_PROMPT=.ai/prompt.md path/to/two-engines-gate/bin/review.sh\n```\n\nCommit both scripts, or list them in `.gitignore` and commit that change, so the gate does not refuse them. The `.rounds` file and `.ai/prompt.md` they write sit under `.ai/`, which the gate skips.\n\nA review that fails without any verdict still uses up a round. Delete the last line of the `.rounds` file before you rerun it.\n\nThe cap never turns into an ACCEPT. It only stops the loop and hands the decision to you.\n\nSometimes the writer pushes back on a finding, and sometimes it's right. Don't let the two engines argue it out over five rounds. One rule: a finding stands if it comes with a reproducible failure, and goes to follow-ups if it doesn't. Anything the brief doesn't settle, like scope or anything a user would see, is your call. That's one short question to you, not another round.\n\nEvidence wins. Opinions wait.\n\nNone of this makes code correct. A reviewer can mark a line \"met\" and be wrong, and tests only check what they test. What you get is a review that checks the things you said mattered, and a loop that ends.\n\nThe [free gate](https://github.com/IliyaOblakov/two-engines-gate) has the read-only review, the verdict named after the commit, the test pass record, and a merge check that refuses without both, plus an offline demo that needs neither CLI. It runs in both directions: `BUILDER=codex` makes Claude Code the reviewer. Add the \"Done means\" section, the house rules and the round rules to its `review-prompt.md`, put `rounds.sh` and the wrapper in your repository root, and you have everything in this post.\n\nIf you want the full workflow I use, the Two Engines Kit is a paid kit I made. It adds one rulebook both engines load, hooks and settings for both engines, a GitHub merge script that merges exactly the commit that was checked, and an installer that previews every change and can be undone byte for byte. $29 for the first 30 sales, then $39: [https://rayastats.com/en/two-engines-kit](https://rayastats.com/en/two-engines-kit)", "url": "https://wpnews.pro/news/how-to-make-codex-review-claude-code-s-commits-done-means-house-rules-and-round", "canonical_source": "https://dev.to/iliyaoblakov/how-to-make-codex-review-claude-codes-commits-done-means-house-rules-and-round-rules-2ili", "published_at": "2026-10-21 15:04:18+00:00", "updated_at": "2026-10-21 15:10:12.910904+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-products"], "entities": ["Claude Code", "Codex", "Anthropic", "OpenAI", "IliyaOblakov", "two-engines-gate"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/how-to-make-codex-review-claude-code-s-commits-done-means-house-rules-and-round", "markdown": "https://wpnews.pro/news/how-to-make-codex-review-claude-code-s-commits-done-means-house-rules-and-round.md", "text": "https://wpnews.pro/news/how-to-make-codex-review-claude-code-s-commits-done-means-house-rules-and-round.txt", "jsonld": "https://wpnews.pro/news/how-to-make-codex-review-claude-code-s-commits-done-means-house-rules-and-round.jsonld"}}