How to use Claude Code and Codex together: setup lessons from my review gate
If you want to use Claude Code and Codex together, the first decision is who does what. In my setup Claude Code writes the code and Codex reviews it read-only, and a small set of bash scripts refuses the merge unless the exact commit has a test pass and an ACCEPT. Most guides stop at shared instructions and separate worktrees. This post is about the part after that: what I had to set up so a review and a test run actually mean something, and the places where the setup surprised me.
Use Claude Code and Codex together by giving each one job
Claude Code writes in its own git worktree. Codex reviews the result and ends its final message with a section that starts with exactly one Verdict: ACCEPT or Verdict: CHANGES REQUIRED line, followed by the findings. I give the writing to Claude Code because writing is where context piles up: the plan, the briefs, the worktrees and the gate logs. Review needs much less, mostly the diff, the rules and what the change was meant to do.
The reviewer never fixes anything. The review prompt in my free gate says "You review; you never fix" and asks for each problem to be described so the author can fix it in a new commit. That keeps every change on one side of the table, and the fix gets its own review.
The roles can be swapped. In the free gate, BUILDER=codex makes Codex the writer and Claude Code the reviewer.
Make read-only actually read-only
For Codex the review call is:
codex exec -s read-only --color never --output-last-message <file> -
The sandbox lets Codex read files and run read-only commands such as git diff, and it cannot write. Two things in your Codex config still come along for the ride, and the gate's docs/engines.md covers both:
- MCP servers configured in
~/.codex/config.tomlstart for the review too. A server that can change things outside the sandbox works against a read-only review, so setenabled = falseon it while reviewing. - Hooks also run under
codex exec. A Stop hook that asks Codex to continue after its review can replace the final message with an empty one. The gate then finds no verdict and exits 1. It keeps the diagnostic log but saves no verdict, so a hook meant to keep Codex working ends up with a review that says nothing at all.
When Claude Code is the reviewer, it gets Read, Grep and Glob as its only tools. It cannot run git, so the script puts the commits and the full diff into the prompt.
Keep the shared rules short
Both engines need the same house rules. The free gate ships two short snippets, one for the project's CLAUDE.md and one for its AGENTS.md. They tell the writer to commit one change and stop, to leave the .ai/ folder alone and never to merge, and they tell the reviewer to stay read-only.
Keep the Codex part short. Codex stops adding instruction files once their combined size reaches project_doc_max_bytes, which is 32 KiB by default, so rules at the end of a long AGENTS.md may never reach it.
Tie every review and test run to a commit
In my setup this is the core of the loop. Each piece of evidence is named after the commit it covers:
gate.shruns your test command on a clean checkout and writes a pass record named after the commit.review.shsends the commits and the diff to the other engine and saves the verdict under the commit it reviewed.merge-check.shrefuses unless the commit you are on has both, and the ACCEPT came from a reviewer that is not the builder.
A new commit starts with no evidence, so an ACCEPT for an older commit cannot cover code nobody reviewed. bash demo.sh in the gate repo walks through one change with a stand-in reviewer, so no model is called. Its staged bug is a sum.sh that skips the first argument, and its test only checks sum 0 4, which is the one kind of input where skipping a zero gives the right answer. The tests pass, the review asks for changes, and the fix commit has to earn its own pass and its own ACCEPT.
Commit before you check anything
All three scripts refuse a checkout with uncommitted or untracked files, because a test can pass on a file missing from the commit. The README asks you to add .ai/ to the project's .gitignore. Try it on a scratch repository and the edited .gitignore is itself an uncommitted change, so the first thing the gate refuses is the .ai/ line you added to .gitignore for the gate itself.
gate: REFUSED: uncommitted changes, untracked files or flagged files (or git failed); commit, remove or unflag them first
The check also runs after the tests. A test command that leaves a file behind fails the run even when every test passed:
gate: the run left changed, untracked or flagged files (or git failed)
If your suite writes snapshots or build output into the repo, point it at a folder git ignores and commit that .gitignore change.
The pitfall: a narrowed test command still passes
The gate records a pass when the test command exits 0 on a clean checkout and the test count check accepts the output. It does not know which tests the project has. Here is the case from the README, with Node 20.19: a scratch repository where add() returns the wrong result, a test for add() in test/add.test.js and a test for another function in test/format.test.js.
GATE_CMD="node --test test/"gavegate: FAIL (exit 1)and no pass record.GATE_CMD="node --test test/format.test.js"gavegate: PASS on <sha>and wrote a pass record for the commit, withadd()still wrong.
With default settings the built-in count check only fails a run with empty output or test summaries that add up to zero, and here one test ran. The pass record keeps the command on its Command: line, and merge-check.sh does not compare it with anything.
Two settings reduce the risk. Set GATE_COUNT_RE and set GATE_MIN_TESTS to the number of tests the project has:
GATE_CMD="node --test test/format.test.js" GATE_COUNT_RE='^# pass ([0-9]+)$' GATE_MIN_TESTS=2 two-engines-gate
gate: the output reports 1 tests, fewer than GATE_MIN_TESTS=2; set GATE_COUNT_RE or GATE_MIN_TESTS (README)
You have to raise that number when you add tests, and it compares the total the output reports, so it does not show which tests ran. The second layer is the review: the prompt asks the reviewer to check anything that weakens an existing check, which covers a test script narrowed in a commit. A command typed on the command line never appears in the diff, so the count check is the only thing that sees it, and a review can miss things anyway. Before you trust a pass record, read its Command: line.
Where to start
The free, MIT licensed two-engines-gate has the three scripts above, the two snippets and the demo. Its README lists what it does not do: it does not make the code correct, and it does not push or merge.
If you later want the merge itself handled, the paid Two Engines Kit adds a merge script pinned to the gated commit, a queue merge, one rulebook that both engines load and an installer with backups that uninstall.sh restores byte for byte. It is built on information gathered since the first Codex and Claude CLIs.