Back to Blog

The Loop Already Running in Your Claude Code PR Reviews

A prompt, a check, and a decision to run again: the three parts of a loop, sitting in the PR review workflow most Claude Code repos already have running.

Sean Robb
7 min read
#ai

The sub-agents post was about delegating work to sub-agents so the main thread’s context stays clear for deciding. This one starts somewhere more mundane. Claude reviewing your pull requests is just a thing that happens once you’ve turned on Claude Code’s GitHub integration, and that arrangement is already a loop. You just haven’t called it that.

There isn’t much to it. Claude reads the diff and leaves a comment on the PR. But the part worth staring at is what happens next: push a fix to the branch, and it reviews again on its own, without me asking twice.

The System

Two things run behind the integration, doing different jobs. One watches the pull request itself, opening it or pushing to it, and posts a review back as a comment. The other listens for you talking to it directly: mention @claude in a comment or a review, and it answers.

The loop already running on every push
Me GitHub Claude Code on every push open PR PR opens → run review post review comment push a fix push lands → run review again post updated comment

That’s the whole system. No orchestrator, no queue, no separate service watching for state changes. GitHub already runs a job on every push and already gives that job a place to write its output. The loop was built into the platform before anyone called it a loop.

The Loop

Three things make something a loop.

A prompt is the instruction for what to do. A check is something the machine can run and get an answer from itself, not something it has to ask a person about. A decision to run again is what happens with that answer, keep going or stop.

A one-shot has the first part. A chain has the first two, wired in a fixed order decided up front. A loop has all three, and the third part, the decision, is what actually makes it a loop instead of a longer one-shot.

The PR review has all three. The prompt is the review plugin’s instructions. The check is the review posted back on the PR, judged by a script instead of a person reading it aloud. The decision is what happens on the next push: a new commit lands, and GitHub decides to run the review again on its own.

Anthropic put a name on the general shape in June: “We define loops as agents repeating cycles of work until a stop condition is met.” The PR review had already been running that shape for months. It just didn’t have anyone calling it that.

This particular loop works because the check was never invented for it. CI, tests, linters, and code review were verifiers long before “loop” became a word people argued about online. I’d already seen a narrower version of this shape when a dev server MCP let Claude Code check its own screenshots and logs instead of relaying them through me. Wire an agent’s output into a thing that already tells you pass or fail, and you get a loop almost for free. Blake Crosley, a practitioner who traced these claims back to the original talks instead of repeating blog summaries, has the framing I keep coming back to: loops win wherever verification is cheap, and they lose wherever a human still has to read the diff and decide if it’s good.

That’s the whole trick, and also the whole limit. A loop is only as good as its check, and the checks are weaker than people assume.

The /goal documentation admits the limit directly: “The evaluator judges your condition against what Claude has surfaced in the conversation. It doesn’t run commands or read files independently.” The second model grading the work is reading the first model’s transcript, not the repo. Anthropic’s harness-design research found agents asked to grade their own work “tend to respond by confidently praising the work,” even when the work is obviously mediocre. SpecBench, published in May, measured the gap between real quality and self-reported quality growing “by 28 percentage points for every tenfold increase in code size” as tasks get longer. The longer a loop runs, the more room it has to convince itself it’s done.

That same loops post undercuts a lot of what got people excited about it: “Not all tasks require complex loops; start with the simplest solution and use these patterns selectively.” One loop with one cheap check, a linter or a test suite or a PR review, covers most of what a reader actually wants an agent to do unattended. It stops covering it the moment the check is taste instead of pass or fail. A subjective read isn’t the same kind of answer as a green CI run, and one loop asking one question isn’t enough to get an honest verdict out of a check like that.

Try It Yourself

Set a /goal and Claude keeps working across turns on its own, without you prompting each step, until a small, fast model reads the conversation so far and rules the condition not yet met, met, or impossible. The friction is in what you hand it. A vague condition reads like a feeling, and there’s no script that can tell you when a feeling has been reached.

The review this post has been describing runs after you push, with GitHub deciding to run it again. Set a goal before you push and you’re the one deciding to run it again, same three parts, earlier in the process:

/goal review the working diff until the reviewer comes back with nothing left to flag, or stop after 10 turns

A green build follows the same shape, with a script standing in for the reviewer:

/goal every test in tests/checkout/ passes and npm run lint exits 0

A migration isn’t one check, but it’s still something you can count down to zero:

/goal every call site of the old auth helper is migrated to the new one and the project compiles

Each one names an end state and a way to check it that doesn’t depend on Claude’s opinion of its own work: a passing suite, a clean lint run, a number that hits zero. Whatever you’re checking, remember the evaluator only sees the transcript, not your repo. If the condition can’t be proven by something Claude actually shows, a screenshot or a passing command, it isn’t specific enough yet. None of this changes your permission mode on its own either. Run it in auto mode if you want the turns to go unattended.

It stops working in the same place the rest of this post does. The moment the check is taste instead of pass or fail, one loop asking one question isn’t a real answer, whether the one asking is a person or a second model.

Here’s a prompt that turns a real task into a condition worth setting:

Help me turn a real task in this repo into a /goal condition instead of an open-ended request. Ask me these one at a time, in order: 1. What's the end state? Push me until it's a single condition I could point to and answer yes or no on, not a sense of "better" or "cleaner."
Help me turn a real task in this repo into a /goal condition instead of an open-ended request. Ask me these one at a time, in order: 1. What's the end state? Push me until it's a single condition I could point to and answer yes or no on, not a sense of "better" or "cleaner." 2. How would I check that end state myself, right now, without you? A test command, a build step, a count, a file that should or shouldn't exist. 3. Is there anything that has to hold true the whole way through, even mid-fix? A file you shouldn't touch, a behavior that can't regress, a scope you shouldn't wander outside of. 4. Should this be bounded, a number of turns or a time limit, in case the condition turns out to be unreachable? Once I've answered, don't summarize the goal back to me. Write the exact /goal line I should type, built from my end state, my check, and my constraints. If any answer is too vague for another model reading only this conversation to judge pass or fail, say which one and ask again instead of guessing.

Write a condition the evaluator can check against what Claude actually shows, and it runs unattended. Write one it can only guess at, and you’re back to reading every diff yourself.