A Ralph loop is three things: a task, a verifier, and a budget. That's the whole pattern. Geoffrey Huntley named it after the Simpsons character who just keeps going regardless of whether anything is working, and the analogy holds. The risk isn't that Claude stops too early. The risk is that it declares success on a broken state, burns your token budget, or both. This post covers how to define each of the three elements precisely, pick the right implementation for your situation, and stop the loop safely when it's actually done. The agentic development workflow is a separate concern, this post is scoped strictly to iteration until a condition holds.
Define the Goal, the Verifier and the Budget
Before you write a single command, write three sentences. What does "done" look like? How will you check that mechanically? And how many iterations are you willing to pay for?
The goal needs to be machine-checkable. "The code is good" is not a goal. "All tests in npm test pass and git status is clean" is a goal. The difference matters because a verifier can only evaluate what it can observe. If your completion condition relies on a human looking at something, you don't have a verifier; you have a review step, and those two things shouldn't be inside the same loop.
The verifier is not Claude's own opinion. This is the part most people get wrong. As one Hacker News commenter put it, "if the AI thinks things work, it will say COMPLETE even if you wouldn't think it's complete." Model-generated DONE is a signal, not a verifier. A real verifier is an external process: your test runner, your linter, a curl against a live endpoint returning the expected status code. It runs after each iteration and produces a deterministic true/false. Build that first.
The budget is your safety valve. Pick a number you're actually comfortable burning. The frankbria/ralph-claude-code community implementation requires both heuristic completion indicators (two or more) and an explicit EXIT_SIGNAL: true in the model's RALPH_STATUS block before it exits. That dual-condition design exists precisely because single signals are unreliable. Your --max-iterations should be conservative for the first run. You can raise it once you've seen how far the loop actually gets.
To put it plainly, your setup before running anything should answer:
- What shell command returns exit code 0 only when the task is genuinely complete?
- What's the maximum number of iterations I'll allow?
- What file or log will record iteration outcomes so I can inspect them later?
Choose the Loop Implementation
Claude Code 2.1 ships three built-in primitives that cover most Ralph use cases without any plugin. The Awesome Claude guide documents all three:

/goal, keeps working across turns until a condition is verified. Uses a separate smaller model (Haiku by default, per Ranjan Kumar's write-up) to read the session transcript after each turn and answer one question: has the goal been met? If no, Claude takes another turn. If yes, the loop clears./loop, re-runs a prompt on a fixed or self-paced interval. Esc to stop. Useful for polling tasks./batch, spreads one large change across 5 to 30 parallel worktree agents. A different beast; not what you want for a single bounded task.
Then there's the plugin path, and the raw bash loop. Here's where they differ in a way that actually matters for your work.
The plugin (ralph-wiggum@claude-plugins-official or the community fork) runs inside a single session using a stop hook. When Claude tries to exit, the hook intercepts it and feeds the prompt back. Context accumulates across iterations. That's convenient, but it means by iteration 15 the context window is carrying the residue of every prior attempt, which can degrade the quality of each new turn.
The raw bash approach, piping PROMPT.md into claude -p inside a while loop, spawns a completely fresh process every time. As Steve Kinney notes, "Each claude -p invocation gets a completely clean context window. This is the entire point of the technique, avoiding context rot by deliberately starting fresh." The tradeoff: you lose implicit memory of what was tried, so your PROMPT.md and task-state files have to carry all the context explicitly between iterations.
Which should you pick? If your task is short (under 10 iterations expected) and the context stays manageable, /goal with a turn cap is the lowest-friction option. If the task is long or context quality is a concern, the fresh-context bash loop is more reliable. The tests and AI coding tools post covers how to structure your test suite so the verifier can actually run cleanly either way.
Run One Task with Bounded Iterations
Here's an illustrative setup for a bounded task using the /goal primitive:
/goal All tests in npm test pass and git status is clean, or stop after 20 turns
That single line gives Claude a verifiable exit condition and a hard cap. The Haiku evaluator reads the transcript after each turn and checks both conditions. The or stop after 20 turns clause is your budget guardrail.
For a bash-loop approach, the structure from the Geocodio team is a good model to follow. They use a JSON file (a simple prd.json ) where each task has a "passes": false field. Each iteration finds the highest-priority story with passes: false , implements it, runs the verifier, and flips the flag to true on success. The while loop exits when every story has passes: true. Their write-up is worth reading for the acceptance-criteria structure alone.
The numbered flow for a fresh-context bash loop looks like this:
- Write
PROMPT.mdwith the current task, the verifier command, and the pass/fail log path. - Start the
whileloop, pipingPROMPT.mdintoclaude -p. - Claude reads instructions, does one unit of work, commits to git.
- The verifier runs. Exit code 0 means pass; anything else means fail.
- Log the result (iteration number, pass/fail, token cost if available) to a file.
- If all tasks pass, write the stop signal and break. Otherwise, loop back to step 2.
Keep each iteration to one unit of work. Trying to do too much per loop is how you end up with a half-finished state that the verifier can't evaluate cleanly.
Detect No Progress and False Completion
Two failure modes are far more common than infinite loops: the loop makes no progress across iterations, and the model declares success on a broken state.
No-progress detection requires comparing something concrete across iteration N and iteration N+1. A git diff is the simplest signal. If git diff HEAD~1 is empty after an iteration that didn't produce a passing verifier, the loop is spinning. You should surface that immediately rather than burning three more iterations hoping something changes.
False completion is trickier. The model will produce DONE , COMPLETE , or EXIT_SIGNAL: true in circumstances where your actual verifier would return a non-zero exit code. The frankbria implementation's dual-condition check (two-plus heuristic indicators and the explicit signal) is a reasonable mitigation. But the sharper fix is: never let the loop exit based on the model's output alone. The verifier script runs regardless of what the model says, and the loop continues if the verifier fails, full stop.
Watch out for a related trap: the verifier itself returning a false positive. If your test suite has flaky tests that sometimes pass without the underlying bug being fixed, you'll get a false completion that the model didn't even cause. That's the next section.
Handle Flaky Tests and Failed Verifiers
Flaky tests are the enemy of any automated loop. A test that passes 80% of the time will eventually trigger a loop exit on the 20% where it shouldn't. And because each iteration costs tokens, a false exit followed by a re-run is expensive.
The mitigation isn't complicated, but it requires some upfront work:
- Run your verifier command three times in a row before trusting a pass. If it fails once out of three, treat it as a fail.
- Separate your "is the work done" tests from your "does the environment work" tests. Network-dependent tests, timing-sensitive assertions, and anything that requires external state should not be in the verifier that gates the loop exit.
- Log every verifier run with its output. If the loop stops and something looks wrong, you want the full verifier output from the final iteration, not just the exit code.
If the verifier itself fails (crashes, times out, or returns an unexpected error code that isn't a test failure), treat that as a loop halt, not a loop continue. Attempting to iterate through a broken verifier will just produce iterations that can't be evaluated.
Claude Code hooks let you attach scripts at specific points in the session lifecycle. The Claude Code hooks guide covers the mechanism in detail. For Ralph loops, the relevant hook is the one that fires when Claude attempts to exit: intercept it, run your verifier, and only allow the exit if the verifier passes. If you're using the plugin path, this is already wired up. If you're on the bash loop, the exit decision happens in your shell script rather than a hook.
Record Cost and Stop Safely
You should know what each iteration costs before the loop finishes. You don't need exact numbers in real time, but you do need a log file that records iteration number, verifier outcome, and enough token information to estimate spend after the fact.
The Alibaba Cloud community post on Ralph loops identifies three stop conditions worth building into any implementation:
- Success: the verifier returns 0 and all tasks have
passes: true. - No-progress halt: two or more consecutive iterations with no git diff and no verifier improvement.
- Budget exhaustion: the iteration counter hits
--max-iterationsregardless of verifier state.
Budget exhaustion is not a failure mode, it's a designed stop. When it triggers, the loop should write a summary of what passed, what didn't, and what the last iteration attempted. That gives you a clean handoff for a manual review or a fresh loop with a revised prompt.
One thing to build explicitly: a distinction between "loop stopped because it succeeded" and "loop stopped because it hit the cap." If you come back to a terminal and see "loop stopped at iteration 20," you need to know which of those it was. A single flag in the log file, STOP_REASON: BUDGET_EXHAUSTED versus STOP_REASON: SUCCESS, is all it takes.
If you're doing this kind of work at scale or want a managed setup rather than hand-rolled scripts, the agentic engineering work covers what a production-grade loop setup looks like with proper observability.
FAQ
Is `/goal` actually a Ralph loop, or is it something different?
They share the same principle (iterate until a condition holds) but differ in architecture. /goal runs inside a single persistent session; a classic Ralph bash loop spawns a fresh process each iteration. As Ranjan Kumar documents, /goal uses a separate Haiku model to evaluate the transcript after each turn. The bash loop evaluates nothing, your shell script does the checking. Both are valid; pick based on whether context accumulation is a problem for your task.
Can I use the model's own `DONE` output as the verifier?
No. The model's output is a signal you can use as one input, but it cannot be the sole exit condition. A model will output completion markers when it believes the task is done, which is not the same as when it actually is. Pair any model-generated signal with an external command that checks the real state of the system.
What happens to the loop if Claude Code compacts the context mid-run?
In a persistent-session loop (plugin or /goal), compaction happens automatically when the context grows long, and the quality of that compaction is inconsistent. One Hacker News commenter observed that "Claude Code compactions are so low-quality that it's basically the same as clearing the history every few turns." The fresh-context bash loop sidesteps this entirely because each iteration starts clean. If you're on the plugin path and running long jobs, watch for degraded output quality after compaction points.
How small should each iteration's unit of work be?
As small as you can make it while still being meaningful. One task per iteration is the right mental model. The Geocodio approach (one story from prd.json per loop) and the Hacker News commenter's description ("it picks the most important task, completes it, and ends its loop") both point to the same answer: small, verifiable chunks are far more reliable than big ambitious iterations.
Do I need a plugin at all?
For most tasks, no. The built-in /goal primitive with a turn cap covers the common case. Reach for the plugin ( ralph-wiggum@claude-plugins-official or frankbria/ralph-claude-code) when you specifically want the stop-hook interception behaviour or the dual-condition exit logic that those implementations provide. Don't install a plugin just because you saw it in a tutorial.
The sharpest caveat in this whole pattern: a model-generated completion signal is not a verifier. Build the external check first, before anything else, and let every other decision follow from that.