Review rounds do not always converge
A developer on r/codex called it "the real wall: non-convergence in my code review workflow". Mine did the same thing. I fixed every finding a reviewer gave me, pushed, asked for another round. It found seven more. Fixed those, asked again. Seven more. Then nine. Rounds 3, 4 and 5 on one pull request, and the count stopped coming down.
TL;DR: Seven rounds on one pull request, finding counts 19, 7, 7, 9. Across five pull requests that ran four rounds or more, not one final round came back clean. A hard round cap does not fix that, because it never asks what the remaining findings are. A stopping rule written before the round runs does.
Three questions people actually type, and where each one is answered here. How many rounds should I run? That is the wrong variable. Why does it keep finding new things? Because it is reading your last fix. Is a hard cap good enough? No, and this is what to use instead.
The counts that were supposed to go down
Every one of those five arcs ended because I decided it should, not because the reviewer ran out of things to say. That sounds like a failure. It is the finding, and it changed how I decide when to merge.
Here is what I actually measured, pulled fresh from the commit history rather than from my notes. Every one of these is a real pull request you can read.
PR #582, seven rounds, merged 22 August 2026. The commit messages state the counts themselves:
round 2 19 findings 10 internal + 5 fugu + 4 deepseek
round 3 7 findings two HIGH, both introduced by round 2's fix
round 4 7 findings on the round-3 code
round 5 9 findings on the round-4 code
round 6 (no total stated in the commit)
round 7 (no total stated in the commit)
Nineteen, then seven, then seven, then nine. Round 5 found more than round 4 did. Round 7 found a defect in a comment I had written in round 6, sitting directly above the one code branch I had left unfixed.
PR #583 was a documentation change and it ran seven rounds too. Round 2 found fifteen. Round 5 found twelve. Round 7 found ten. PR #618 also ran seven, and its round 7 found four things serious enough to block. PR #589 ran six rounds. A docs pull request in a separate repo ran four rounds, and every serious finding in it sat in material that same pull request had added, never in the content it existed to fix. Its round count is in the commit messages; its total of thirty findings is not, and comes from my own notes, so treat that one number as weaker than the rest.
My knowledge base recorded PR #582 as "45 findings." Re-adding it from the commit bodies this week, four rounds alone account for 42, and two more rounds are unaccounted for. The stored total does not reconcile with its own per-round breakdown. I am publishing the per-round sequence instead of a total, because the sequence is what I can source line by line and the total is not.
That correction matters more than the arithmetic. The interesting quantity was never how many findings there were. It is whether the count declines. I have been caught by a stored number once before, which is why I went back and audited my own correction-rate measurement rather than quoting it again.
What the published material actually says
I went looking for anyone who had published a round count. The honest answer is more interesting than "nobody has."
The loudest version of the question is a post in r/codex titled "After 5 months of AI-only coding, I think I found the real wall: non-convergence in my code review workflow" (102 points, March 2026, title and score verified 30 August 2026; Reddit blocks automated readers, so I could not open the body and am not quoting it).
On GitHub, discussion #189767 is titled "Copilot Code Review generates new comments on every push, creating an endless fix-push-review loop." It was opened in March 2026 and was still marked Unanswered when I read it on 30 August 2026. One commenter there: "Endless loop...If not in one attempt, then two to three should be enough. It is endless."
And that thread contains the single most useful external number I found. The author logged their own rounds on one pull request:
| Round | Copilot, one PR (GitHub #189767) | My PR #582 |
|---|---|---|
| 1 | 10 comments | (folded into the initial commit) |
| 2 | 6 new comments | 19 findings |
| 3 | 4 new comments | 7 findings |
| 4 | 2 new comments | 7 findings |
| 5 | 2 more, author gave up | 9 findings |
| 6 | - | still finding |
| 7 | - | still finding, criterion stopped it |
Their counts fell or held every round and still did not reach zero in five. The causes are not the same as mine either, and the difference is worth naming: they describe Copilot re-scanning unchanged code and flagging things it had not flagged before, while my later rounds were finding defects my own previous fix had introduced. Different mechanism, same ending, which is that a human decided when to stop.
Now the part where I have to correct my own starting assumption. I went in believing nobody had published a measured convergence, and that is false. Zylos published a research note on multi-model review convergence dated 1 March 2026 (read 30 August 2026) reporting Claude Code as fixer plus OpenAI Codex as reviewer over an eight-round loop on an SDK codebase, with findings at 7 in round 3, 4 in round 4, 2 in rounds 5 and 6, 1 in round 7, and 0 in round 8. They call it a textbook trajectory, and they also recommend "hard caps prevent infinite loops regardless of convergence state." That number is self-reported by its authors and I have no way to check it, so take it as their account rather than as an independent measurement.
So the round counts exist, and I was wrong to think they did not. Now put the three sequences next to each other, because the shapes are the whole argument:
zylos 7, 4, 2, 2, 1, 0 falls, holds once, reaches zero
Copilot 10, 6, 4, 2, 2 falls, holds once, never reaches zero
mine 19, 7, 7, 9 falls, holds, then GOES BACK UP
Neither published sequence falls strictly. Both flatten for a round in the middle, so "it plateaus" is not the distinguishing feature and I was wrong to reach for it. What neither of them ever does is go back up. Mine does: round 5 found more than round 4 did.
That rise is the difference, and it is the only one of the three shapes that tells you something a round counter cannot. A sequence that falls and holds might just need one more round. A sequence that rises is producing its own next round, and no number of rounds fixes that. I am not claiming nobody has hit it: the r/codex post is titled "non-convergence in my code review workflow," so somebody plainly has. What I have not found published anywhere is per-round numbers for a series that rises.
The reviewer is reading my last fix
After round one, the reviewer is mostly not reading the change any more. It is reading the code I wrote to satisfy the previous round.
This is the pattern under the three arcs where I recorded where the findings sat, and it is the thing a hard cap cannot see. I did not track that for PR #589, so it is not evidence here either way. On PR #582, round 3's two most serious findings were both introduced by round 2's fix. On another pull request, both rounds confirmed the actual one-line change was correct, and every single finding lived in the tests and logging I had bolted on around it. On the four-round docs arc, three of my own fixes became the next round's finding, including a CI edit that inserted a new step block and left the old one, so three steps ran twice.
A small correct change carrying a growing tail of scaffolding, where each round audits the tail rather than the change.
- The shape worth recognising
Once you see it that way, a clean round stops being good news. A clean round on a pull request whose last two rounds each found a serious defect in freshly added code is evidence about the reviewer's remaining budget, not about the code. This is the same trap I hit when my hooks fired 574 times and blocked nothing: the instrument reported quiet and I read quiet as safe.
It is also worth saying plainly that a reviewer is an instrument, not an authority. In one round the reviewer told me an unclosed-fence check was a false positive. It was not. Per the CommonMark spec a fence at least as long as the opener and carrying no info string closes the block, so the file really was malformed. I refuted that finding in the commit message rather than "fixing" a check that was right. Refusing a finding is part of running the loop, and I get to that habit the same way I got to sending designs to a non-Claude model first.
Closed or open-generative
The rule that actually ends a loop is not a number of rounds. It is a question about the defect class.
Ask whether the class of defect is finite and enumerable. If it is, keep going, because the rounds will converge on it. If it is not, no number of rounds closes it and the shape of the artifact has to change.
PR #582 is the clean example of the second branch. I had a guard that scanned source code for expressions that looked like they could leak a secret into a log. Three rounds in a row found a site it missed. The guard cannot be completed, because any expression that produces a string can reach a log, so the list of things to look for has no end. The replacement was not a better guard. It was moving the redaction to the point where the log gets written, one function per writer, so no call site can leak and there is nothing left to enumerate.
The docs pull request is the same decision in a different costume. Four rounds each found a real behaviour defect in a JavaScript snippet meant to be pasted into a browser page I have never seen. Round 3's version passed a seven-case behaviour suite with a mutation control, and round 4 still found a variable that was never bound, because my test rig supplied the exact binding the artifact lacked. So I deleted the snippet and shipped a specification with six named constraints instead. The file now ships zero executable JavaScript. Confident code that has never touched the real page is worse than no code, because it gets pasted and fails silently, and silent failure is the exact thing the document existed to prevent.
The tell in both cases is the same one I keep meeting: the instrument carries the defect it is looking for. A review round that introduces the errors the next round finds is that pattern one level down.
Write the stopping rule before the round runs
Here is the mechanism. It is a few sentences written before you press go.
Before you kick off round N, write down what round N would have to return for another round to be worth running. Write it where you cannot quietly edit it: a commit message, a note to whoever you report to, a knowledge entry with a timestamp. Then run the round.
A rule written after you see the result is not a rule. That is the whole trick, and I learned it by watching myself rationalise. Every individual finding looks like a small fix in the moment, so in the moment the decision to stop always loses. A sentence written twenty minutes earlier does not lose, because it was written by someone who could not yet see the findings.
Mine look like this. The categories matter more than my specific thresholds:
BLOCKS THE MERGE
- any HIGH-severity finding in what the change actually does
- any test that cannot fail for what its docstring claims
- any finding showing the new guard misses a class it claims to catch
DOES NOT BLOCK
- presentation: exit-code labels, message wording, human-readable prints
- comments, docstrings, naming
- anything that would be a follow-up item on a green pull request
Two things I got wrong and would tell you up front. First, the criterion is a floor, not an autopilot. On one arc round 4 technically met my own bar for running a round 5, and I stopped anyway and said why in writing, because the findings had migrated entirely into my own scaffolding and another round would have measured my patience. Second, a divergence between two reviewers is not a tie for you to break by weighing them. Run the experiment the disagreement implies. I have a longer version of that habit in why done isn't done.
The other thing that ends a loop is refusing to run it. On PR #618, the seven-round one, the largest finding class was present in every round and was always the same shape: a fix applied to the reported site and not to its symmetric position. Every instance was findable with grep. So the commit that ended those rounds was not a fix. It was a test asserting the two positions stay in sync, and I validated it by replaying the five historical defects against it rather than by watching it pass. Four were caught immediately. The fifth was missed because my check searched for a function name inside the file that defines it, so the definition itself satisfied the check and the test could never fail. Fixed that, re-ran the replay, five of five.
When the reviewer keeps finding the same grep-able shape, you are using it as a linter, and you should ship the linter. Same reasoning as self-correcting workflows with PreToolUse hooks.
The round that answered ship
The rule is not only a brake. Once it told me to stop reviewing and merge, and that was the right call.
On a small open-source pull request I wrote the criterion before round 2 returned: findings in the original change get a round 3, findings only in the scaffolding get fixed and shipped, and a finding that contradicts a measurement loses to the measurement. Round 2 returned four findings, zero serious. Under the rule that was neither branch cleanly, so I resolved each on its own evidence instead of re-running the whole loop.
One of the four was real and worth the round: I had fixed a stale path in two files, said in the commit message that I had closed it, and missed a third file. A fix applied to two of three sites looks exactly like a fix, and the commit message claiming closure is what makes the miss invisible.
One I refuted by running it. The reviewer argued from the local plugin docs that a path placeholder is documented for hook commands and slash commands but not for skill text, so my two loader files might hand Claude an unresolvable literal while the install still reported success. Well-formed argument, real source, and wrong. One command with the plugin loaded printed the resolved absolute path and the first heading of the file it read. Two official doc pages, one narrower than the other, and only the experiment separates them. If you are building one of these, how to build a Claude Code plugin walks the same packaging path, and skills vs plugins vs MCP vs subagents covers which surface you are actually loading.
Four findings, no round 3, merged. The rule bought a decision, not a delay.
What I do now
- Write the stopping rule before the round runs. Somewhere you cannot quietly revise. This is the whole intervention and it takes minutes.
- Judge the defect class, not the round number. Finite and listable means keep going. Open-ended means change the artifact's shape, because no round count closes it.
- Watch where the findings live. When two rounds running find defects only in code your last fix added, that is the signal to split the change or stop, not to go again.
- When the same grep-able shape keeps appearing, write the check. A mechanical guard that runs every time beats a reviewer who might notice. Validate it by replaying the defects it should have caught, not by watching it pass.
- A clean round is not the goal, and a hard cap is not a criterion. Stopping at 5 because 5 tells you nothing about whether the remaining findings matter.
The reviewer I use for these rounds is Claude Code's own /code-review, run at the repository the change lives in, plus non-Claude models for anything load-bearing. If you are still deciding how much review to run at all, six Claude Code failure modes and how I orchestrate parallel sessions cover the surrounding workflow.
Honest scope
n=1 developer, six pull requests across two repositories, my own review setup. Small sample, and the arcs are not independent, because the same person wrote every fix that the next round then found.
The per-round counts I quote are the ones the commit messages state in words. Rounds 6 and 7 of PR #582 state no total in their bodies, so I have not given one, and I have given no total for PR #589's rounds at all. The thirty-finding breakdown on the four-round docs pull request comes from my knowledge base rather than from a commit body; unlike the "45" it does reconcile with its own per-round list, but it is a weaker source than the rest and I am marking it as one.
Four of the five long arcs had a criterion written before the deciding round. PR #589 did not, and I have not gone back to reconstruct one, so "a rule ended it" is a claim about four of them and not about all five.
I could not open the r/codex post body, only its title and score, so nothing from that thread is quoted here beyond the title. Dominic Elm's "7 pain points where code review breaks down in the AI era" (posted 13 March 2026, linked via his post because the article URL itself would not load for me) is the fourth place this question shows up, and I am linking it rather than quoting it, because the sentence usually attributed to it lives in the article itself and the article would not open for me.
What I have not measured: whether writing the criterion in advance actually shortens loops on average, as opposed to making the stopping decision cheaper when it arrives. I only have the cases where I did it, with no matched cases where I did not. That comparison is open. PR #618 is the awkward case for me here, because it ran seven rounds with a criterion in place.
This post got the same treatment it describes. Three non-Claude models attacked the finished draft on evidence. Four findings survived checking and are fixed above: I had quoted the zylos sequence two different ways in adjacent sentences, dropping a round in one of them; I had called my own series flat when 19 to 7 is plainly a drop; I had framed the Copilot loop as the same shape as mine when the underlying cause is different; and I had put a notes-derived number inside a paragraph promising commit-derived ones. One finding I refuted rather than accepted: a lens argued the zylos numbers were an illustrative example rather than a real run, which the page does not support, and that lens had no way to open the page.



![Claude Code Hooks Fail in Runs: 76% vs 18% [2026]](/_next/image?url=%2Fblog%2Fclaude-code-hooks-fail-in-runs-hero.webp&w=3840&q=75)