DEV Community

Iliya Oblakov
Iliya Oblakov

Posted on

How I make Claude Code and Codex review each other's work

I build software for paying clients with Claude Code and Codex. Both are good at writing code. Neither is good at judging its own work, because the session that wrote a change is the worst place to decide whether that change is finished. It already believes it is.

So I split the jobs. Claude Code writes. Codex reviews, read-only. My own tests run on the same commit. A small script decides whether anything merges, and it only looks at evidence saved to disk.

Here is how to set it up. You need git, the Claude Code CLI, the Codex CLI and a test command.

Step 1: one rulebook, two readers

If the writer and the reviewer follow different rules, the reviewer flags things the writer was never told, and you end up arbitrating between two models.

Keep one file, say rules/RULES.md, with your real standards. Then point both engines at it:

  • Claude Code reads CLAUDE.md, which can import other files with an @ line. Put @rules/RULES.md in it and you are done.
  • Codex reads AGENTS.md. Generate it from the same source rather than editing it by hand:
#!/usr/bin/env bash
# sync-rules.sh: build AGENTS.md from the shared rulebook
set -euo pipefail
{
  echo "<!-- generated from rules/RULES.md, do not edit -->"
  cat rules/RULES.md
  [ -f rules/codex-extra.md ] && cat rules/codex-extra.md
} > AGENTS.md
Enter fullscreen mode Exit fullscreen mode

Keep engine specific lines few. Everything that defines "good work" belongs in the shared file.

Step 2: write rules a reviewer can check

This is where most setups go wrong. "Write clean, maintainable code" sounds sensible, but a reviewer cannot check a diff against it, so you get opinions instead of findings.

A good rule names something visible in a diff, says what to do instead, and says how to tell it was done. Here is one example rule you can copy into your own rulebook. I wrote it for this post:

## Public signatures

When a change renames, removes or reorders the parameters of an
exported function, class or API route:

1. Update every caller in the repository in the same commit.
2. Add a line to the commit message body starting with
   "Signature change:" that names the old and new signature.
3. If a caller lives outside this repository, do not make the change.
   Add a new function next to the old one and mark the old one
   deprecated in a comment.

Reviewer check: search the diff for changed exported signatures.
For each one, confirm the callers were updated and the
"Signature change:" line exists. If either is missing, the verdict
is CHANGES REQUIRED and the finding quotes this rule.
Enter fullscreen mode Exit fullscreen mode

Notice the last paragraph. Each rule tells the reviewer what to look for. That turns review from taste into a checklist.

Step 3: let Claude Code work in its own worktree

Never let the writing agent touch your main checkout. A git worktree gives it a separate directory on a separate branch, sharing the same repository:

git worktree add ../task-login-fix -b task-login-fix
cd ../task-login-fix
claude
Enter fullscreen mode Exit fullscreen mode

Give Claude Code the task there. If it goes wrong, git worktree remove throws the whole attempt away and your main branch never saw it.

Step 4: Codex reviews read-only, with a forced verdict

The reviewer must not be able to edit files. A reviewer that can fix what it finds is a second author, and nobody reviewed that second author. Run Codex with a read-only sandbox.

Give it three things: the rulebook (it already has it through AGENTS.md), the diff against the base branch, and a prompt that ends with a strict output format. A minimal version:

#!/usr/bin/env bash
# review.sh: ask Codex for a verdict on the current commit
set -euo pipefail
base="${1:-main}"
sha="$(git rev-parse HEAD)"
mkdir -p .ai/reviews
git diff "$base...HEAD" > ".ai/reviews/$sha.diff"

codex exec --sandbox read-only "Review the diff in .ai/reviews/$sha.diff
against the rules in AGENTS.md. List each problem with the rule it
breaks, quoted. Do not edit any file. The last line of your answer
must be exactly ACCEPT or exactly CHANGES REQUIRED." \
  > ".ai/reviews/$sha.md"

tail -n 1 ".ai/reviews/$sha.md"
Enter fullscreen mode Exit fullscreen mode

Two details matter here.

First, the verdict file is named after the commit hash. If Claude Code adds one more commit after the review, the hash changes and the old ACCEPT no longer applies. You cannot accidentally merge code nobody reviewed.

Second, the verdict is a fixed word on the last line. Scripts can read that. They cannot read "mostly looks good, a few small things".

If the verdict is CHANGES REQUIRED, hand the review file back to Claude Code in the worktree, let it fix the findings, commit, and review again.

Step 5: run your tests on the same commit

The review checks the rules. Your tests check the behaviour. Run them on the exact commit that was reviewed and record a pass only when they pass cleanly:

sha="$(git rev-parse HEAD)"
mkdir -p .ai/tests
if npm test; then
  echo "pass $(date -u +%FT%TZ)" > ".ai/tests/$sha.pass"
fi
Enter fullscreen mode Exit fullscreen mode

Swap npm test for whatever your project uses. Add .ai/ to your git ignore so none of this ends up in the repository.

Step 6: a dumb gate decides the merge

The final step should involve no judgement at all. A script checks that both pieces of evidence exist for the current commit, and refuses otherwise:

#!/usr/bin/env bash
# gate.sh: refuse to merge without a review and a test pass for HEAD
set -euo pipefail
sha="$(git rev-parse HEAD)"
review=".ai/reviews/$sha.md"
tests=".ai/tests/$sha.pass"

[ -f "$tests" ] || { echo "No test pass for $sha"; exit 1; }
[ -f "$review" ] || { echo "No review for $sha"; exit 1; }
[ "$(tail -n 1 "$review")" = "ACCEPT" ] || { echo "Review did not accept $sha"; exit 1; }
[ -z "$(git status --porcelain)" ] || { echo "Working tree not clean"; exit 1; }

echo "Gate passed for $sha"
Enter fullscreen mode Exit fullscreen mode

Run it before every merge. "Reviewed and tested" stops being something you remember and becomes something a script checks.

What this does not do

It does not make the code correct. Codex can miss things, and so can your tests. The gate only proves the steps happened on the commit you are about to merge. That is still a big improvement over trusting the same session that wrote the code to tell you it is done.

A few habits that help:

  • Send routine work to cheaper models and keep the expensive reasoning for design, hard debugging and the review itself.
  • When a review keeps flagging the same thing, turn it into a rule with a reviewer check.
  • Read the review, even when it says ACCEPT. The gate is a floor, not a replacement for you.

If you want the full version

Everything above is enough to start. If you would rather install a finished setup, I packaged the workflow I use on client work as the Two Engines Kit: a shared rulebook for both engines, cross review, gate and merge scripts (the merge script can be set to refuse without a saved ACCEPT), hooks and settings for both engines, a skill for long tasks, an optional shared memory module, and GUIDE.md on why each piece exists. The installer previews each change and asks before applying it, and uninstall.sh restores your old files byte for byte. It needs both Claude Code and Codex, and runs on WSL2, Linux or macOS (tested on WSL2 with Ubuntu 24.04).

It is $29 for the first 30 sales, then $39: https://rayastats.com/en/two-engines-kit

Top comments (0)