What my review gate does when Codex crashes, gets cut off or writes two verdicts

When Codex crashes, gets cut off or writes two verdicts while it reviews Claude Code's commits, my gate saves no verdict for that commit. With no saved verdict the merge check refuses the commit, so the only review that counts is a final message with exactly one valid Verdict line.
two-engines-gate is the free, MIT licensed version of how I make Claude Code and Codex check each other. I wrote about the review loop, the commit hash records and the dirty working tree refusal before. The post right before this one covers what the gate does about weakened or deleted tests. This post is about the review that ends without a usable answer.
Three exit codes
review.sh ends in one of three ways:
0: the reviewer answered ACCEPT, and the verdict is saved.2: the reviewer answered CHANGES REQUIRED, and the verdict is saved.1: there is no verdict, and nothing is saved.
merge-check.sh passes a commit only when its saved verdict has a single Verdict: ACCEPT line. With no saved verdict it stops with "no review verdict for ; run review.sh".
The old verdict goes first
Before it calls the reviewer, review.sh deletes any verdict already saved for the commit, and stops if it can't. When you rerun a review and the rerun crashes, an ACCEPT from the earlier run can't stay behind under the same hash.
Six checks after the reviewer stops
After the reviewer exits, review.sh runs these checks in this order. The first one that fails ends the run with exit 1.
- The reviewer exited 0. Otherwise it prints
REVIEW FAILED: codex exited 1; no verdict for 04bf3eea7ffd (log: .ai/reviews/04bf3eea7ffd.log). - HEAD is still the commit under review. Otherwise it prints
REVIEW VOID: HEAD moved during the review. - No file outside
.ai/changed. Otherwise it printsREVIEW VOID: files changed during the review (or git status failed). - The final message has something in it. Otherwise it prints
NO VERDICT: codex wrote no final message. - The final message has exactly one line starting with
Verdict:. Otherwise it printsNO VERDICT: the final message needs exactly one Verdict: line, found 2, or found 0. - That line says ACCEPT or CHANGES REQUIRED. Otherwise it prints
NO VERDICT: the Verdict: line must say ACCEPT or CHANGES REQUIRED.
Only after all six does the script write the report, with the full commit hash, the base, the builder, the reviewer, the date and the final message. It writes to a .tmp file first and then moves it into place, so a crash during the save can't leave a half-written verdict under the real name.
What each failure looks like
I ran each case on 23 October in a scratch repo with the public scripts. The ACCEPT, CHANGES REQUIRED, crash and two-verdict answers came from the stand-in reviewer in examples/. The empty, cut-off and misspelled answers came from a small fake codex I wrote for the test.
| What the reviewer did | What review.sh printed | Exit | Verdict saved |
|---|---|---|---|
| Answered ACCEPT | verdict file: .ai/reviews/04bf3eea7ffd.md |
0 | yes |
| Answered CHANGES REQUIRED | verdict file: .ai/reviews/04bf3eea7ffd.md |
2 | yes |
| Exited with an error | REVIEW FAILED: codex exited 1; ... |
1 | no |
| Wrote an empty final message | NO VERDICT: codex wrote no final message |
1 | no |
| Stopped before the Verdict line | NO VERDICT: ... found 0 |
1 | no |
| Wrote two Verdict lines | NO VERDICT: ... found 2 |
1 | no |
Wrote Verdict: ACCEPTED |
NO VERDICT: the Verdict: line must say ACCEPT or CHANGES REQUIRED |
1 | no |
The stand-in cases are also in tests/run.sh, including two checks that a failed run leaves no verdict file behind, so you can rerun them yourself.
Why two verdicts count as none
The prompt asks for the Verdict line "exactly once and nowhere else in your final message". An answer with ACCEPT near the top and CHANGES REQUIRED at the bottom holds two answers, and the script has no way to know which one the reviewer meant. Taking the first, the last or the friendlier one would be a guess, so it takes neither. merge-check.sh runs the same count on the saved file and wants exactly one Verdict: line that says ACCEPT.
A cut-off answer fails the same count. The prompt puts the Verdict line at the start of the last section, so a reply that stops before that section has a count of 0.
The verdict comes only from the final message
Codex runs with --output-last-message, which saves its final answer to a file. review.sh reads the verdict from that file and nowhere else. The rest of the run goes to a log next to the verdict for you to read, and the script never searches the log for a Verdict line. The prompt Codex gets contains Verdict: ACCEPT itself, as the example of the format, so a search of anything that repeats the prompt could find an ACCEPT the reviewer never gave.
Two Codex settings to check
Both are in docs/engines.md in the repo.
- A Stop hook that asks Codex to continue after its review can replace the final message with an empty one. The review then ends at "wrote no final message" and nothing is saved. Switch the hook off for reviews, or add a line to
review-prompt.mdasking Codex to repeat its complete review if anything asks it to continue. - MCP servers in
~/.codex/config.tomlalso start for the review. A server that can change things outside the sandbox works against a read-only review, so setenabled = falseon it while reviewing.
What the gate does not do
- It sets no time limit. A reviewer that hangs keeps the script waiting until you stop it.
- It doesn't retry. After a NO VERDICT or a REVIEW FAILED you read the log and run
review.shagain. - It doesn't grade the review. A short ACCEPT with one Verdict line passes every check above, and how good the review was is for you to read in the saved file.
The same rule in the kit
The paid Two Engines Kit (currently v1.0.2) works the same way. Its cross-review.sh takes the verdict from the reviewer's final message only, and its guide lists a crash, a cut-off answer and a refusal among the cases that mean exit 1 and no verdict file. With REQUIRE_REVIEW=1 its merge script refuses any commit whose verdict file for that exact commit doesn't exist or doesn't say ACCEPT. I added that after one night in October when I found four test-only commits that had never been reviewed. One was already merged and three were in the merge queue, and the reviewer confirmed all four before the queue resumed, so nothing broke. The Two Engines Kit is built on information gathered since the first Codex and Claude CLIs.
Try it
Clone two-engines-gate and run bash demo.sh to watch one full loop with a stand-in reviewer, so no model or account is used. To see a missing verdict yourself, link examples/stand-in-reviewer.sh as codex in a folder at the front of your PATH, then run review.sh in a scratch repo with STANDIN_ANSWER=two or STANDIN_ANSWER=fail.