You Were The Loop. Here's How To Fix That With Claude Code

Two months ago I was the loop.
Open Claude Code. Type a prompt. Wait. Read the diff. Fix something manually. Type another prompt. Wait again.
I was the trigger. I was the verifier. I was the thing deciding when to stop. Claude was just a tool I picked up and put down, forty times a day, and I called that "using AI."
It wasn't. It was me doing the job of a scheduler, a QA engineer, and a release manager, by hand, while an extremely capable model sat idle between my keystrokes waiting for permission to do anything at all.
The tell was always the same: every evening I'd have a dozen half-finished threads, none of them closed, all of them needing "one more pass" that only I could kick off. The bottleneck in my workflow was never Claude's intelligence. It was my attention span.
Here's what changed, the reasoning behind it, and the actual code runnable, not illustrative that I use today.
The shift isn't subtle once you see it laid out. In the top half, you are load-bearing. Remove yourself for an hour and the whole thing stalls mid-sentence. In the bottom half, you're load-bearing exactly once when you write the spec and then again briefly at the end, to read a diff someone else already verified.
The line everyone quotes but nobody implements
Stop prompting your agent. Design a loop instead.
You've seen a version of this line from at least two or three people you follow this month. Everyone nods along. Almost nobody changes what they actually type into their terminal the next morning, because the advice stops exactly where it gets useful.
Nobody tells you what a loop is made of. Nobody hands you the file. So people keep doing the thing that feels closest to a loop writing a longer, more detailed prompt and wonder why the result still needs babysitting.


A longer prompt is not a loop. A loop is a process. It has moving parts, it has failure modes, and it has a shape that repeats whether you're fixing a test suite or running a nightly cleanup job. Let's take that shape apart, then put it back together as code you can paste and run today.
What a loop actually is, in four parts
Strip away the hype and a loop is Claude Code repeating a cycle of work on its own without you sitting in the middle of every single step until some condition you defined in advance says "stop."
That's the entire definition. It has exactly four moving parts, and if any one of them is missing, what you've built is not a loop. It's a prompt wearing a costume.
Trigger - what actually kicks off a cycle. Could be you, typing a command. Could be a cron job firing at 9am. Could be a file changing on disk. Could even be Claude itself, deciding partway through a big task that it needs a fresh cycle to check its own work before moving on.
Action - what Claude is actually allowed to touch during that cycle. This is your blast radius. A loop with unrestricted tool access is a loop that can quietly rewrite files you never meant to expose to it, delete things it decided were "unnecessary," or wander off into a part of the codebase that has nothing to do with the task. Scope this down explicitly, every single time you write one.
Verification - how the cycle proves to itself that the action actually worked, using something outside of Claude's own narrative about what it just did. A passing test suite. A file with specific content. A clean exit code. Not "I believe this is correct" a fact that exists whether or not Claude is in the room to describe it.
Stop condition - the rule that ends the whole thing. Not "when it feels done." A concrete, checkable trigger for exit: verification passed, or you've hit a hard iteration ceiling, whichever comes first.
Most people who think they've built a loop have actually built three of these four parts and are quietly supplying the fourth themselves eyeballing the output and deciding when to stop looking at it. That's still you being the loop. It's just a slower, more exhausting version of the same problem.
Anatomy of one cycle
Those four parts aren't the same as the steps a loop runs. The parts are what it's made of; each time the loop actually turns, they play out as a five-step cycle Action splits into Discover then Execute, Verification becomes Verify, the return-on-failure is Iterate, and the stop condition fires at Stop.
Every loop I've built, from a two-line bash script to a multi-agent pipeline running overnight, reduces to the same five-step shape.
Discover is where Claude reads the actual state of the world before touching anything the task file, the current test output, the last git diff, whatever tells it what's true right now rather than what was true when you wrote the prompt an hour ago. Skipping this step is how loops start "fixing" problems that were already fixed three iterations back.


Execute is one bounded change. Not "fix everything you can find." One change, sized so that if it's wrong, you can immediately tell which change broke things, instead of untangling a pile of simultaneous edits.
Verify is the step almost everyone quietly skips, and it's the entire reason loops fail in the wild. Verification has to be a check Claude can run and get a real, external answer from a test command, a file diff, a status code not a paragraph where Claude tells you it thinks the work is done. The moment you let the same model that did the work also be the sole judge of the work, you've reintroduced exactly the blind spot loops are supposed to remove.
Iterate happens automatically when Verify fails: control returns to Discover, but now with new information the specific error, the specific failing assertion instead of the same vague instruction repeated from scratch.
Stop happens when Verify passes cleanly, or when you've burned through a hard-coded maximum number of iterations. That ceiling isn't optional. Even a well-designed loop can hit an edge case it wasn't built for, and a loop with no ceiling is a loop with no brakes, quietly consuming your token budget while going nowhere.
Loop one: fix tests until they pass
This is the simplest useful loop I run, and it's the one I'd start with if you're building your first one. No frameworks, no orchestration library. Just Claude Code's headless mode wrapped in a plain bash while loop.
Run it with bash fix-tests.sh and walk away. Come back to either a green test suite, or ten log files showing precisely where things got stuck which is itself useful, because now you know exactly which failure Claude couldn't reason its way out of, instead of a vague "it's not working" from a single exhausted session.
Notice the guardrails baked in on purpose: --allowedTools is scoped to exactly three tools, the prompt explicitly forbids editing test files (a common way loops fake a pass by weakening the thing checking them), and there's a hard iteration ceiling that fires regardless of what Claude thinks is happening.
Loop two: the native primitive - Stop hooks
The bash while loop above works, but it's an external wrapper bolted onto Claude Code from the outside. Claude Code actually has a built-in primitive for this exact pattern: Stop hooks.
A Stop hook fires the instant Claude tries to end a session. If your hook script exits with code 2, Claude is blocked from stopping and is forced to keep working, right there in the same session, with all the context it already built up intact.
The difference between this and the bash wrapper matters more than it looks. With the wrapper, every iteration is a brand-new Claude session that has to re-read the task file and rebuild its understanding of the codebase from scratch. With a Stop hook, it's one continuous session that keeps its working memory of what it already tried, what failed, and why which in my experience converges noticeably faster on anything more complex than a single failing test.
Teaching Claude about its own loop: CLAUDE.md


Neither of the two loops above tells Claude it's in a loop. It just experiences getting blocked from stopping, over and over, with no idea why. Give it that context explicitly, and the quality of each iteration goes up, because Claude stops re-deriving the same constraints from scratch every cycle.
Add this to CLAUDE.md at your project root:
Loop context
That last rule earns its place. Left unsupervised long enough, a model under pressure to make a red test go green will occasionally take the shortest path there which is deleting the assertion rather than fixing the bug. Say it explicitly, once, in a file that's loaded every single cycle, and you stop relitigating it in every prompt.
The pattern nobody's written about: maker-checker
Here's the part that separates a loop that looks automated from one that's actually trustworthy.
A single Claude session verifying its own work has a structural blind spot: it wrote the bug, and it's now the one deciding whether the bug is fixed. It isn't being dishonest it just doesn't have fresh eyes. The same context that produced the mistake is grading the mistake.
The fix is to split the loop into two roles that never share context:
Maker - a session with edit access, whose only job is to produce the change
Checker - a completely fresh session, read-only, that never saw how the code got written and only knows the spec it's checking against
This one change has caught more real, silent failures for me than any amount of "please double-check your own work" stuffed into a single prompt. The checker has no investment in the code being correct it's not defending a decision it made five minutes ago, it's reading a spec cold and reporting what it sees.
What this actually looks like running
If you've never watched one of these execute end to end, it's less dramatic than it sounds and more useful than a single chat reply. Here's roughly what a terminal session looks like across three iterations of the maker-checker script above.


Nothing about that transcript required you to be watching it happen. You could start the script, close your laptop, and come back to logs/checker_3.json saying PASS or to three rejection reasons telling you exactly where a human needs to step in instead.
Matching triggers to tasks
Not every loop should be kicked off the same way. The trigger you choose should match how the work actually needs to happen, not just whichever one you already know how to wire up.
Start with the cheapest trigger that actually solves your problem. A cron job that runs once overnight is not a worse loop than a Stop-hook system with five moving parts for a lot of tasks it's simply enough, and "enough" ships faster than "impressive."
Where loops quietly go wrong
A handful of failure modes show up in almost every loop I've debugged, most of them look identical from the outside: the loop keeps running, the logs scroll past clean, and nothing announces that anything is wrong until you actually read the output it produced. Loops don't crash when they fail. They succeed at the wrong thing, quietly, forever.
The verifier is weaker than the task
Symptom: the checker says PASS every round, but the thing it's checking barely resembles the spec. Usually because the check was written to be easy to satisfy - "does it compile," "does the file exist," "did a test run" - rather than "does it do what was asked."
Fix: write the verifier before you write the maker prompt, and write it adversarially. Ask yourself what the laziest possible correct-looking output would be, then make sure your check would catch it. A verifier that only checks existence is not a verifier, it's a formality.
Reward hacking gaming the check instead of passing it
Symptom: the assertion that was failing on round 1 is gone by round 3. Or the test file has a new skip on it. Or the "error handling" the spec asked for is a bare try/except: pass. The loop technically converged. Nothing got fixed.
Fix: this is exactly what the STATUS.md rule at the top of this piece is for tell it explicitly not to touch the check, and give it an escape valve ("say so in STATUS.md") so that flagging a bad test doesn't feel like the only path forward is silently defeating it. Then actually diff the test files between rounds. If they're changing at all, read why.
No stop condition the infinite loop

Symptom: round 40. Round 80. The bash for loop you wrote caps at 8 iterations but somebody removed the cap "temporarily" and forgot. Or worse there was never a cap, just a while true, because it seemed like it would obviously converge.
Fix: every loop gets a hard numeric ceiling, no exceptions, checked in before the first run. When it hits the ceiling without converging, that's not a bug in the loop that's the loop doing its job, telling you the task needs a human. Treat "did not converge after N rounds" as a normal, expected exit path, not an error to suppress.
Context drift across rounds
Symptom: round 1's output is sharp and on-spec. Round 6's output has quietly wandered it's solving an adjacent problem, or re-litigating a decision that was already settled in round 2, or has started "cleaning up" code nobody asked it to touch. Nothing in any single round looks wrong. The drift only shows up if you compare round 1 to round 6 directly.
Fix: keep the spec file as the single source of truth that gets re-read fresh each round, rather than letting the model rely on its own accumulated summary of what's happened so far. This is the same reason the maker-checker split works a fresh read of SPEC.md doesn't drift, but a long, self-summarized memory of "what we've been doing" absolutely does.
Silent cost blowup
Symptom: you check your API usage a week later and it's ten times what you expected. Nobody was watching per-round token counts, and a loop that's stuck oscillating maker breaks the thing checker just approved, checker rejects it, maker "fixes" it back to the original bug will burn tokens for eight rounds just as readily as a loop making real progress.
Fix: log a token or cost estimate per round, not just per run. Alert on rounds, not just totals a single loop hitting its ceiling once is a rounding error; the same loop kicked off nightly by a cron trigger that never converges is a monthly bill you won't notice until it's due. If you're running unattended loops on a schedule, put a cost ceiling next to the round ceiling. Both are the same kind of guardrail: the loop doesn't get to decide when enough is enough.
None of these failure modes are exotic. They're the ordinary ways any automated process fails when nobody's holding it accountable in real time a monitoring script that alerts on nothing, a cron job that "works" by doing nothing measurable, a test suite that's green because it's empty. Loops don't introduce new kinds of failure. They just remove the human who used to notice the old kinds by accident.
That's the whole tradeoff, really. A loop gives you back the hours you used to spend babysitting. What it asks in return is that you spend some of those hours up front, once, making sure the thing checking the work is more careful than the thing doing it and that somewhere, on paper, you've written down what "done" actually means before you let anything decide it's been reached.
Get that part right, and the loop is just a tool: boring, reliable, and easy to forget is even running. Get it wrong, and you've built something that will confidently, tirelessly, and silently do the wrong thing forever right up until you check.
Prompts
bash
#!/bin/bash
# fix-tests.sh — runs until the suite passes or we hit 10 tries
MAX_ITER=10
i=0
mkdir -p logs
while [ $i -lt $MAX_ITER ]; do
echo "=== iteration $i ==="
claude -p "Read TASK.md for context. Run the test suite with 'npm test' \
and read the output. If tests fail, fix the code causing the failure. \
Run the suite again. If all tests pass, write DONE to STATUS.md. \
Do not touch test files themselves." \
--allowedTools "Edit,Read,Bash" \
--output-format json > "logs/iter_$i.json"
if grep -q "DONE" STATUS.md 2>/dev/null; then
echo "done after $i iterations"
break
fi
i=$((i + 1))
done.claude/settings.json:
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{ "type": "command", "command": "./check_done.sh" }
]
}
]
}
}
check_done.sh:
#!/bin/bash
# check_done.sh — Stop hook. Exit 0 = let Claude stop, exit 2 = force it to keep working.
# Ships with a hard ceiling so a per forever.
MAX_ATTEMPTS=10
COUNT_FILE=".loop_count"
# Tests pass -> we're done. Clear th.
if npm test -- --silent > /dev/null
rm -f "$COUNT_FILE"
exit 0
fi
# Still failing -> count this attemp
count=$(( $(cat "$COUNT_FILE" 2>/dev
echo "$count" > "$COUNT_FILE"
if [ "$count" -ge "$MAX_ATTEMPTS" ];
echo "Hit $MAX_ATTEMPTS attempts, the loop can't run forever." >&2
rm -f "$COUNT_FILE"
exit 0 # give up gracefully inst
fi
echo "Tests still failing (attempt $xing." >&2
exit 2You are running inside an automated loop. This session may be
re-invoked multiple times against the same task.
- Before making changes, check STATUS.md for notes left by a
previous iteration of this loop.
- Make one focused change per invocation. Do not attempt to fix
everything in the codebase in a single pass.
- After changing code, always re-run the verification command
for this task before ending your turn.
- If verification fails, leave a short note in STATUS.md
describing exactly what failed and what you tried, so the next
iteration doesn't repeat your mistake.
- Never edit test files, fixtures, or the verification script
itself to make a check pass. If a test seems wrong, say so in
STATUS.md instead of changing it.Article tables:
| Trigger | Good for | Primitive |
|---|---|---|
| You, manually | A one-off, multi-step task you want to walk away from | plain claude -p inside a shell loop |
| File change | Keeping code in sync with a spec or schema as it evolves | file watcher (fswatch, inotifywait) calling claude -p |
| Cron / schedule | Nightly cleanup, dependency checks, report generation | cron entry running headless Claude |
| Stop hook | "Don't let Claude quit until X is verifiably true" | .claude/settings.json |
| Claude itself | A large task Claude decomposed into its own sub-checks | subagents plus TodoWrite for step tracking |
Links
Related articles

I Brutally Modified My Front-End Design Skill ~ Now My UIs Don’t Look Like AI Crap
To create a custom Claude skill, navigate to Customize > Skills in your Claude interface, click the + button, and select + Create skill

claude for research, grok for funnels = $22k/m info beast
everyone using AI wrong, treating all models like they're interchangeable, asking chatgpt to do everything from research to copywriting to funnel building to customer service, getting mediocre output…

How to Create Loops with Claude
AI is great at answering questions. But the real magic happens when it can think, improve, and repeat tasks automatically.