Every Program Has Infinite Bugs

Every program has infinite bugs. You can always invent a new expectation the code fails, and most of those failures will never cost anyone a dollar. Agent reviewers do not find them all, and they do not even think of them all. Run the same review twice and a new pile shows up. The job is ranking what would hurt, then merging.


An inspector standing in an infinite corridor of glowing red bug tickets that stretch past the horizon

The Set Never Empties

A bug is anything that compromises quality. Quality is the degree to which a system meets specified requirements or user expectations. Requirements include a pile of non-functional ones: performance, maintainability, documentation, style. User expectations have no ceiling. You can always invent a new expectation the program fails.

So a sum(a, b) that correctly adds two integers still has unlimited defects. It doesn’t handle overflow. It doesn’t sum doubles. It doesn’t cache. It isn’t object-oriented. Checkstyle would complain. You can keep going until you get bored, and you still have not exhausted the set.

Yegor Bugayenko wrote this up in 2017. Most of those “bugs” have no business impact. They will never be found by a user. They will never cost a dollar.

Reviewers Sample. They Do Not Exhaust.

Human reviewers already drowned in this infinity. They just hid it. A senior engineer reading a 200-line PR would notice three things, mention two, and merge. The rest of the infinite set stayed invisible. That looked like thoroughness. It was triage performed in silence.

Agent reviewers did not make the whole set visible. They made a sample visible. Point CodeRabbit, Bugbot, or Cursor review at the same PR and you get a pile of comments. Missing JSDoc. A helper that could be extracted. A log line that isn’t there. A name that could be clearer. An overflow that will never happen.

None of those comments have to be false. Look at what is missing. The agent did not think of the auth hole. Or it thought of the auth hole on the second run, after you already spent an hour resolving the first pile. Re-run the same review on the same diff and a new set appears. Different nits. Sometimes a real one that was invisible ten minutes ago.

Treating the first pile as complete is a mistake. Treating a long pile as a quality signal is a worse one. Teams stall resolving style while the thing that would actually hurt never gets named. Forward flow dies in that pile. You optimize for comment resolution, not for the software getting better.

This is also how you get the AI you ask for. Ask a reviewer agent to “find issues” and it will emit a sample, forever, a different one each pass. Ask it to name the findings that would actually hurt a user or the next change, and you get a review.

Review Is Triage

Infographic: infinite findings on the left, a merge bar in the center, only user-visible failures and next-change tax passing through

I have argued that code review is a dying practice and that product review is the better altitude. That still holds. This post is about the cases where you are still looking at code, human or agent: pull requests, Bugbot runs, Cursor review, a teammate’s diff. The infinite-bugs fact tells you how to do that job.

Review is triage. Completeness is not a criterion.

Three buckets. Everything that gets surfaced goes in one of them.

User-visible failure. Wrong behavior, data loss, a security hole, a test failure that means the specified contract broke. These block the merge.

Next-change tax. Coupling that will make the next edit expensive. A boundary violation. A secret read from the wrong layer. These also block, and they belong in deterministic architecture tests, not in a comment thread you re-litigate every Tuesday.

Everything else. Ignore it. Or, if the same nit keeps appearing, encode it as a linter rule or a test and stop discussing it. A finding that is not worth a gate is not worth a conversation.

The seven aspects of software quality are still the right vocabulary for arguing about bucket two. Use them as a ranking language for the change in front of you. You pick the aspects this change can actually damage, and you ignore the rest.

Your code is terrible. So is mine. So is the two-line sum(). That is the starting condition, not a merge blocker.

The Merge Bar

A change is ready when the specified behavior holds, the architecture rules hold, and nothing in the diff would surprise a user or the next agent. A change is not ready when the reviewer ran out of comments, or when you re-ran the agent and it invented twelve more.

That bar is finite on purpose. Specification sets the behavior you agreed to. Tests and gates enforce it. Review looks for the surprises the spec did not name. Then you ship, with the rest of the infinite set still in the program, the same way it sat in every program you ever shipped.

Rank what would hurt. Merge the rest.