← Back to Blog

How to Write Loops with Claude Code

I read the first 182 replies to "can you share 'loops' you regularly use?" One linked a loop you could actually run. Here are seven.

In July, Gergely Orosz asked X: can you share 'loops' you regularly use? I read the full thread across July 13-14: 182 replies at that point (it has kept growing since), about thirty on topic, and exactly one that linked a loop you could actually run, a config.ts in a repo. Everyone else described theirs in prose, down to the odd /loop one-liner, or dismissed the term ("a fancy word for cron jobs," 56 likes). The sharpest reply summarized the discourse: "Someone posts about loop engineering. Another developer asks what is it? They don't explain it. Repeat."

The question existed because in June, Boris Cherny, the creator of Claude Code, said the thing that launched a thousand blog posts: he doesn't prompt Claude anymore. He writes loops, and the loops prompt Claude. Within a week the idea had a name (loop engineering), a viral tweet past eight million views, and a wave of "complete guides" that were mostly the same two quotes reworded. Orosz later wrote his thread up himself, "based on ~210 replies, mostly from X and LinkedIn". It is a survey, not a build manual: it maps what people say they run, its loop lines arrive as quotes (Huntley's bash, OpenAI's /goal example, one engineer's flaky-test /loop), and half the analysis sits behind the paywall.

This post is the artifact that thread was missing: seven loops in the order to learn them, with the exact invocation, the check that decides done, the kill switch, and what survives your session. Then the unflattering parts: cost (a real $6,000 receipt), failures (with incidents), security. Everything comes from official docs, harness source, or my own transcripts; Method says which is which.

TL;DR
  • A loop is a trigger, a prompt, and a termination check.
  • Claude Code ships six primitives, differing on two axes: what fires the next iteration, and what survives your session (only committed files; Routines, per-account in the cloud, are the exception).
  • Learn them as a ladder; a seventh loop composes the six: /goal/loop → bounded bash → Stop hooks → dynamic workflows → routines → the stack.
  • The termination check is the whole game: an agent graded on its own claims lies its way out, so verification must be a command that runs, not a vibe.
  • Cost is your wake interval versus the cache window. Unattended loops run in a container with hard caps. The only loop your team can inherit is the one you commit.
Type your first loop in the next 60 seconds

Open Claude Code in any repo with tests and type:

/goal all tests pass and lint exits 0; or stop after 20 turns

Claude keeps taking turns; a second model grades the condition after each one; /goal clear kills it. That is a loop. The other six are what it grows into, and this table is the menu:

I want to…Reach for
watch a loop work while I approve each step/goal <condition>Loop 1
re-check something on a timer while I work/loop 15m <prompt>Loop 2
leave it building unattended, boundedbash loop.sh (Ralph)Loop 3
make "done" mean the tests actually rana Stop hookLoop 4
fan one task out across many files or agentsa dynamic workflowLoop 5
hand my team a loop they can runa dynamic workflow saved as /<name>Loop 5
run it while my laptop is closed/schedule (a Routine)Loop 6
compose all of it into one machinethe stackLoop 7

One provenance note, because it says something about this wave: the famous quote circulates in three wordings pinned to the wrong videos, so I checked the captions of all three recordings. It was said once, at Acquired Unplugged on June 2, at 11:42:

"I don't prompt Claude anymore. I have loops that are running. They're the ones that are prompting Claude and kind of figuring out what to do. My job is to write loops."
Boris Cherny, creator of Claude Code

A month earlier, at Sequoia's AI Ascent, he had described the loops themselves: one babysitting PRs, one keeping CI healthy, one clustering Twitter feedback every 30 minutes. Five days after the June quote, Peter Steinberger's paraphrase became the eight-million-view tweet; most people are quoting a paraphrase of a clip.

What a loop actually is

Cherny tells it as abstraction levels: you wrote code in an IDE, then you prompted agents (five or ten in parallel if you were aggressive), and now a small piece of automation decides what to tell the agent next until a condition holds. Code, then agent, then loop. Now the deflation: the technique is nearly a year older than the buzzword, and in its purest form it is one line of bash. July 2025, Geoffrey Huntley, the Ralph technique:

while :; do cat PROMPT.md | claude ; done

Feed the same prompt to the agent forever. Context resets every pass; the only memory is the files and git history the last pass left behind. Huntley delivered an MVP for a $50k contract on $297 of tokens this way, and documented everything that goes wrong:

So: Huntley's bash (July 2025), the harnesses productizing it by spring 2026 (Codex's Goals in April; Claude Code's /goal in May), and Cherny, Steinberger, and Addy Osmani giving it a name in June 2026. You are not learning an invention; you are learning a technique that got a marketing department. Osmani's definition is the cleanest: loop engineering is replacing yourself as the person who prompts the agent.

The map: where each loop lives, and what dies with your session

Here is why that thread had nothing to paste. A skill is one file; commit it and your team has it. A loop is seven things in a trench coat: trigger, prompt, verifier, memory, budget, guardrails, and a running instance, living in different places with wildly different lifespans. Most die with your session.

You runNext iteration starts whenSurvives your session?How your team gets it
/goal <condition> a separate model rules the condition not yet met no; dies with the session nothing to share; rebuild it as a Stop hook
/loop 15m <prompt> the timer fires no; 7-day expiry at best committing .claude/loop.md (the prompt, not the loop)
a Stop hook the agent tries to stop and your check fails yes; every session, forever committing .claude/settings.json + the script
"use a workflow to…" the script's own while loop decides yes; the script is a file saving to .claude/workflows/ → it becomes /<name>
/schedule (a Routine) cloud cron fires, or a GitHub event lands yes; in the cloud, laptop closed it can't; routines are per-account
bash around claude -p your for loop iterates yes; everything is files + git committing PROMPT.md + loop.sh

Read the last column twice; it is the entire sharing story. Session-scoped loops restore on --resume at best and hard-expire in seven days. What persists is files.

Persistence is not a property of the loop engine. It is a property of committing the definition.

That is why the thread produced descriptions instead of links: the loops exist as muscle memory in sessions long since garbage-collected. The ecosystem is competing to fix this (RALPH.md packages, loopy's LOOPS.md catalogs, an eleven-field "Loop Contract"); none has won. Inside Claude Code the practical answer already ships, and it is Loop 5 below.

Every loop is the same three organs

Strip any harness, official or homegrown, and you find the same three organs: a trigger (what starts the next iteration: a bash while, a cron schedule, a Stop hook refusing to let the session end, a GitHub event), a prompt (usually a file, so you tune it without touching the loop), and a termination check, the hard part, which gets its own section. Everything else is a bolt-on.

Plus a bloodstream: state. The conversation is disposable and the repo is the memory: a plan file with checkboxes, a progress log, a commit per iteration. Osmani has the line: "The agent forgets; the repo doesn't."

trigger prompt agent done check the hard part not done: go again done state: plan.md · progress.md · git commits the agent forgets; the repo doesn't force multipliers: hooks: drive+guard subagents: checker skills: lean ctx MCP: reach worktrees: parallel

The force multipliers, one line each:

Seven loops, in the order to learn them

The six primitives from the map, plus a seventh that composes them. Each level hands over more autonomy. For every loop: the invocation, who verifies, how to kill it, what survives.

Loop 1: /goal, your first loop in sixty seconds

autonomy: supervised · survives: this session only · v2.1.139+

/goal all tests in test/auth pass and npm run lint exits 0;
do not modify any other test file; or stop after 20 turns

Type that and the loop starts immediately, condition as first instruction. After each turn, a separate small model (Haiku by default) reads the transcript and rules yes or no; no starts another turn seeded with the reason, yes clears the goal. Bare /goal shows status; /goal clear kills it.

Condition rules, learned from watching them fail:

You get 4,000 characters; spend them on constraints, because the loop optimizes whatever you leave unpinned.

The fine print that decides whether it works: the evaluator cannot run commands or read files. It judges only what Claude surfaced in the conversation. "All tests pass" works because the test output lands in the transcript; "the code is production ready" gets waved through. Write conditions the transcript can prove.

By default you still approve every tool call: perfect training wheels. Auto mode (Shift+Tab, or --permission-mode auto) makes the turns hands-off. And it works headless: claude -p "/goal <condition>" runs the whole loop in one terminal command.

Loop 2: /loop, the interval loop

autonomy: semi-supervised · survives: 7 days max, this session · prompt shareable as loop.md

/loop 15m check the release PR; if CI is red, diagnose the failure
and push a minimal fix; if everything is green, do nothing

/goal re-fires on a condition; /loop re-fires on a clock. Three modes: fixed interval, self-paced (/loop <prompt>; Claude picks each delay, one minute to an hour), and bare /loop, which runs a built-in maintenance prompt or yours. Esc kills the pending wake-up.

What most people miss: the prompt above carries its own "do nothing" branch. an interval loop has no condition checker, so the exit ramp lives in the prompt. And the lifespan: session-scoped, restored by --resume only if unexpired, hard 7-day expiry. The shareable piece is .claude/loop.md, your team's committed default for bare /loop; the running loop never travels. One warning for later: an interval loop against a big warm session is a metronome hitting the prompt cache. The $6,000 story below is exactly this.

Loop 3: the bounded bash loop (Ralph, grown up)

autonomy: unattended · survives: everything; it is all files and git

The loop I would actually leave alone with a repo: Huntley's one-liner grown exactly four guardrails, each earned by a documented disaster we will meet later.

#!/usr/bin/env bash
# loop.sh: fresh context each iteration; the repo is the memory
MAX_ITERATIONS=15

for ((i=1; i<=MAX_ITERATIONS; i++)); do
  echo "=== iteration $i/$MAX_ITERATIONS ==="

  result=$(claude -p "$(cat PROMPT.md)" \
    --max-turns 40 \
    --max-budget-usd 3.00 \
    --allowedTools "Read,Grep,Glob,Edit,Write,Bash(npm test:*),Bash(git add:*),Bash(git commit:*)" \
    --output-format text 2>&1) || true

  echo "$result" | tail -5
  git add -A && git commit -m "loop: iteration $i" --allow-empty --quiet

  if [[ "$result" == *"<promise>ALL_TESTS_GREEN</promise>"* ]]; then
    echo "done after $i iterations"
    break
  fi
  sleep 2
done

The four guardrails:

Three files, all in the repo root: loop.sh (run with bash loop.sh from a normal terminal; Ctrl-C stops it between iterations), PROMPT.md, and plan.md, the checkbox task list the prompt reads:

# plan.md (the run below started with all five unchecked)
- [ ] parse(): support days ('d') as a unit
- [ ] parse(): reject malformed input (trailing garbage, unit-first forms) with a clear error
- [ ] implement format(ms) in src/format.js: compact form like '1h30m', omit zero units, RangeError on negatives
- [ ] round-trip: parse(format(parse(s))) equals parse(s) for mixed-unit inputs
- [ ] full suite green: npm test exits 0

The prompt file matters more than the script:

# PROMPT.md
Read plan.md and pick the SINGLE highest-priority unchecked task.
One task only. Do not touch anything else.

Before writing code, search the codebase to confirm the task is not
already implemented. Do not assume it is missing.

Implement it completely. No placeholders, no stubs, no TODO comments.
Run the test suite. If anything fails, fix it before moving on.

When the task is done and tests pass:
1. Check it off in plan.md
2. Append one line to progress.md: what you did, what the next
   iteration should know

Output <promise>ALL_TESTS_GREEN</promise> ONLY if every task in
plan.md is checked off AND the full test suite exits 0. Never output
it otherwise, even if you believe you are stuck.

Every line traces to a failure mode. "One task only" is Huntley's rule; without it the agent goes off the rails. "Search before implementing" kills the re-implementation spiral, where each fresh context assumes the feature is missing. "No placeholders" is the anti-reward-hack line; Anthropic's compiler harness runs on the same file-based discipline, down to agents claiming tasks by writing lock files into the repo. And progress.md is the relay baton: iteration 13 has no memory, so this is how iteration 12 warns it about the flaky test (the loop creates it; commit the other three files). That makes this the first loop a teammate can clone and run. For templates, read snarktank/ralph (21k stars) and ralph-playbook.

Receipts, because the standard above demands them. I ran this exact script (two disclosed tweaks: --output-format json to log per-pass cost, and Opus pinned as the model) against a small greenfield library, exactly where Ralph belongs, with the five-task plan.md above and a suite starting at 3 of 10 passing. The run: 5 iterations, 7 minutes, $3.56, 77 agentic turns, 10 of 10 green, promise tag emitted exactly once, loop exited at 5 of the 15-iteration cap. Per-pass cost: $1.05, $0.64, $0.71, $0.63, $0.54 (rounded to the cent; the totals come from the raw log, which is why they differ from summing these by a penny). Three things the numbers don't show:

One unprompted delight: before emitting the promise, the final iteration audited the suite for vacuous tests ("'the suite is green' is exactly the claim a stub or vacuous test would also produce"). The termination paranoia the next section teaches showed up on its own.

Interlude: how the loop knows it is done

All three loops so far verify on the honor system. Loop 3 trusts a tag the agent prints; Loop 1 is better (a second model rules) but the judge never leaves the bench. "Am I done" is the question the agent has an incentive to answer wrong, and both failure modes are documented.

The honor-system bypass. The official ralph-wiggum plugin terminates on an exact promise-tag match; its only defense against lying is a pleading admonition: output the promise "ONLY when statement is TRUE - do not lie to exit!" Nothing verifies the claim, so an agent that prints <promise>DONE</promise> on its first response, no work done, walks free. The reverse failure is on the record twice: finished work whose tag missed the exact match, looping forever (whitespace, #52405; control characters, #68665). Hence max iterations.

The judge who cannot leave the bench. /goal wraps a Stop hook: every time the agent tries to finish, its evaluator rules on the condition. But the evaluator "does not call tools, so it can only judge what Claude has already surfaced in the conversation." It cannot run the tests. If the worker says the suite is green, the judge rules done.

maker writes code, wants out checker fresh context, different instructions, RUNS the tests exit only through checker "done!" verified rejected: back to work, with reasons

The root cause has a name: reward hacking. The model optimizes the success signal, not the work. Huntley saw placeholders chasing "it compiles" in 2025; a Hacker News commenter opened an AI-written suite and found tests that "ran some random functions, did nothing to test their correctness, and just printed 'Test passed!' regardless of the result"; ImpossibleBench measured that a visible test loop raises cheating from 33% to 38%.

The loop does not just fail to prevent gaming. It trains it.

So termination engineering is a ladder, and each rung buys back some trust:

1. promise tag: agent grades itself trivially gamed 2. /goal: separate judge, reads transcript only believes the worker 3. command Stop hook: actually runs the tests can't be talked out 4. verifier subagent: fresh eyes, runs checks itself 5. frozen held-out checks + hard caps outside the model's control each rung: more of the verdict is decided by something the agent cannot edit or persuade

Loop 1 lives on rung 2, Loop 3 on rung 1. Rung 3 is next. The rule that ties it all together: write conditions the agent's honest output can demonstrate and a command can confirm. "Production ready" is a vibe the judge waves through. "npm test exits 0 and no file under tests/ was modified" is a fact a 10-line hook enforces. The quality of your loop is exactly the quality of this sentence.

Loop 4: the Stop hook, the loop that cannot be talked out of it

autonomy: unattended within a session · survives: committed; your whole team inherits it

A Stop hook fires every time Claude tries to finish. Exit 0: the session ends. Block: Claude does not get to stop, and the hook's message becomes the next instruction. Ten lines of bash enforce what no prompt can: the session does not end while the tests fail.

{
  "hooks": {
    "Stop": [
      { "hooks": [
        { "type": "command",
          "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/verify-done.sh",
          "timeout": 120 }
      ] }
    ]
  }
}
#!/bin/bash
# .claude/hooks/verify-done.sh
INPUT=$(cat)
# break the loop if we've already blocked too many times
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then exit 0; fi

if npm test >/dev/null 2>&1; then
  exit 0   # verified: allow the stop
fi
echo "npm test is failing. Run it, read the failures, fix them." >&2
exit 2     # exit 2 blocks the stop; stderr goes back to the agent

Wiring:

Three contract details that bite everyone once:

That is rung 3 of the termination ladder, packaged as two committable files: clone the repo, get the loop. The same mechanism powers the official ralph-wiggum plugin (/ralph-loop "<prompt>" --max-iterations 50 --completion-promise "DONE": an in-session Ralph with a cap and no bash). Rungs 4 and 5 add distrust: a verifier subagent re-runs the checks itself with fresh context, and frozen held-out checks pin acceptance tests at a commit and fail any edit under tests/, because a model that can edit the test can pass the test.

Loop 5: the dynamic workflow, the loop that commands a fleet

autonomy: semi-autonomous, runs in background · survives: as /<name> in your repo · v2.1.154+

use a workflow to run npx tsc --noEmit and keep fixing the
reported errors until the type check passes or two rounds
in a row make no progress

This one is different in kind, and the docs name the difference precisely: who holds the plan. In every loop so far the plan lives in a model's head or a blind bash file, one agent at a time, results piling into a context window. Ask for a dynamic workflow ("use a workflow" is the opt-in) and the plan moves into code: Claude writes a JavaScript program, a runtime executes it in the background while your session stays free, and the script itself holds the loop, the branching, and every intermediate result. Conceptually:

// simplified sketch of what Claude writes for you
let dry = 0, round = 0
while (dry < 2) {                      // loop-until-dry: stop after
  round++                              // two rounds with no progress
  const r = await agent(
    `Run npx tsc --noEmit. Fix up to 10 reported errors.
     Return how many you fixed.`)
  if (r.fixed === 0) dry++; else dry = 0
}
return `type check clean after ${round} rounds`

What falls out: loop state lives in script variables, not your context window: 200 files in, one answer out. The stop condition is real code: "two rounds with no progress" is a counter, not a promise tag. And the runtime enforces caps you don't have to remember: 16 agents at once, 1,000 per run.

The payoff: open /workflows, select the run, press s, save to .claude/workflows/ (saving names it; here, fix-types). It is now /fix-types for everyone who clones the repo, and it takes arguments: /triage-issues 1024, 1025. Save to ~/.claude/workflows/ instead and it follows you privately across projects. This is the answer to the Orosz thread: a loop as a named, versioned, argument-taking artifact in version control.

The fleet is where it stops being a better while loop. Because the plan is code, one script can fan agents out in parallel, pipeline them through stages, force structured JSON from each, and route each stage to a different model; per the docs, every agent uses your session's model "unless the script routes a stage to a different one." My daily pattern: Fable writes and drives the script, Opus and Sonnet take the worker stages by complexity, Fable comes back for the verification that needs the most intelligence. That buys quality patterns no single session can hold, like independent agents adversarially reviewing each other's findings before you see them. The research, reviews, and link-checking behind this post ran as dynamic workflows (the link hunt: six agents in parallel, each returning a URL plus verbatim evidence). That machinery deserves a post of its own. Here it stays a loop.

The limits:

Loop 6: the Routine, the loop that runs while your laptop is closed

autonomy: fully autonomous, cloud · survives: your Anthropic account (not your repo)

/schedule daily dependency triage at 9am

Everything above dies when your machine sleeps. A Routine is a saved configuration (prompt, repository, connectors) that runs as a full Claude Code session on Anthropic's cloud: cron (minimum interval one hour) or, from the web console, GitHub events like pull_request.opened. Cherny's "loop babysitting my PRs" is the latter: a subscription to the work queue, not a timer.

Know what you are signing. Each run is a fresh clone of the default branch with no permission prompts mid-run and your identity on every commit. Keep the default restriction that it can only push to claude/-prefixed branches, write the prompt to open draft PRs rather than merge, and read the transcript afterward: a green run status means the session exited cleanly, not that the work is right. Manage with /schedule list, update, run. The asymmetry: this is the one durable loop you cannot commit; routines are per-account, so teammates rebuild from your prompt text.

Loop 7: the stack, the full machine

autonomy: composed · survives: every layer that is a file

The primitives compose because each answers a question the others don't:

  1. A Routine fires on schedule or on pull_request.opened, laptop closed.
  2. Its prompt says "use a workflow", so each firing launches a script that holds the loop and fans out.
  3. The workflow pipelines subagents, each with isolation: worktree, so parallel agents edit one repo without collisions.
  4. Stages split maker / checker: one stage implements, the next reviews with fresh context and gates.
  5. A committed Stop hook enforces "tests pass before you stop" inside every agent; results land as draft PRs on claude/ branches for a human merge.
1 · Routine the clock: cron or pull_request.opened, laptop closed 2 · Dynamic workflow the plan: holds the loop in code, fans out 3 · Subagents in worktrees the hands + workbench: parallel edits, no collisions 4 · Maker / checker stages fresh-context review gates each stage 5 · Stop hook + draft PRs the quality gate: tests pass, a human merges

Two worktree footnotes that save an evening: fresh worktrees branch from origin/HEAD, not your local uncommitted state, and they skip gitignored files like .env unless listed in .worktreeinclude. And don't feel obligated to run all five layers: /loop 20m /fix-types is an interval firing a saved workflow; a bash Ralph around claude -p "/goal …" is fresh context plus a real condition. Small stacks, well verified, beat cathedrals.

Adding and removing the human

Notice what never changed as we climbed: the loop engine and the human's place in it are independent choices.

Adding or removing a person never means switching primitives. It means changing the gating around the one you have.

Keep a human in:

Take the human out:

The one gate you never remove, in either mode, is the merge.

Steal these loops

Don't invent your first real loop. Forward-Future's Loop Library catalogs 85 with named authors and one-click copy. Four worth stealing, lightly trimmed; watch where the words go:

The pattern across all 85: the best loops spend most of their words defining when to stop. Triggers get a clause, actions get two, and the stop condition with its "do not touch" constraints gets paragraphs. That ratio is the tell of someone who has run loops rather than tweeted about them.

What it costs: anatomy of the $6,000 loop

In May, r/ClaudeAI produced the number: "I accidentally burned $6,000 of Claude usage" (~1,300 upvotes). The setup was mundane: a /loop checking PRs every 30 minutes, forgotten overnight next to a long-open analytics session. 46 runs over 26 hours, none individually wrong. The poster's own turn data shows the money: turn 1 sent a few hundred tokens; turn 46 sent roughly 800,000, the whole session history re-bought at cache-write rates.

If you read Anatomy of a $5 "Good Morning", you know the mechanism: a loop is my $5.18 good morning automated. Every message re-sends the session. While the cache is warm, reads bill at 0.1x and each read refreshes the timer; past the window, writes bill at 1.25 to 2x and everything is re-bought. My transcripts: the same throwaway message into a ~500K-token session cost $0.04 to $0.35 inside the hour, $4.75 to $5.31 past it. A loop turns that dice roll into a schedule.

cost per wake-up gap between wake-ups inside the cache window reads at 0.1x · cents cache TTL past the window full-context rewrite at 2x $0.04-0.35 on a 500K session (my data) $4.75-5.31 on a 500K session (my data) the $6k loop: a far bigger session, rebought 46x

The trap is not long gaps (obviously cold) or short ones (safely warm). It is intervals hovering around the cache window. Iterations run long, wake-ups drift, the session grows, and a mid-session change can invalidate the prefix outright (I have a $4.10 receipt for 6 seconds of idle). Each late wake-up re-buys a history bigger than last time. That compounding is how 46 boring iterations become $6,000 without one of them looking wrong.

The design rules that fall out, none of which appear in the essays:

For scale: Anthropic ran 16 Opus agents in a permissions-off while-loop for two weeks to build a C compiler. Roughly 2,000 sessions, just under $20,000, and a 100,000-line Rust compiler that builds Linux 6.9. $20,000 bought a shipped compiler; the danger is spend that nobody meters and nothing verifies. On subscriptions the same mechanics burn your weekly quota, which is why Anthropic's usage limits explicitly target running Claude "continuously in the background, 24/7."

How loops fail

Cost is the gentle failure mode. The taxonomy, worst first; incidents and vote counts as reported in their public posts, the priced ones linked in References:

FailureReal incidentThe guardrail it demands
Destructive actions, unattended A cleanup command ended in rm -rf against $HOME and took a home directory, with the permission system on (issue #75859). Agents deleting production databases while debugging with live credentials is a postmortem genre of its own. Container or VM, never bare host. Prod credentials nowhere in reach.
Runaway recursion / retries A recursive subagent bug burned 4M tokens in under 5 minutes with zero recoverable output. Iteration caps and budget caps, held outside the loop's reach.
Fake-done Tests that print "Test passed!" unconditionally; promise tags emitted on turn one; goals declared met because the transcript said so. The termination ladder: verification that actually runs the checks.
Slop compounding A developer inherited a 3-month-old loop-built repo and rewrote it in a week (7.3k upvotes): "barely any architecture, tests that covered who knows what." An HN commenter, on a coworker's Windsurf-generated suite, put it sharper: "awful tests that were unmaintainable and next to useless... testing that the code 'works the way it was written.'" Each iteration adds a small defense, a redundant check, a single-use abstraction, until the system collapses on its complexity. One task per iteration, review diffs daily, and a periodic human pass that deletes.
Self-sabotage In Anthropic's own compiler run, agents overwrote each other's fixes until git task-locking was invented mid-run, and one agent ran pkill -9 bash and killed its own loop. Worktrees for parallelism; the loop process owned by something the agent cannot reach.

Notice what is absent: model stupidity. Almost every disaster is an authorization failure or a verification failure. The loop did what it was allowed to do, and nobody was watching. Which is why the fix is structural, not "prompt better."

Running a loop without getting popped

This is the section the incident table has been building toward. Everyone quotes Huntley's whimsy; fewer quote his threat model: "It's not if it gets popped, it's when it gets popped. And what is the blast radius?"

An unattended loop is an agent with your credentials, no human in the approval path, ingesting text written by strangers. A loop that triages GitHub issues is one malicious issue title away from executing someone else's instructions. Cloud Routines run with "no permission-mode picker and no approval prompts during a run," your identity on every commit, and connected MCP tools writable by default. Counting on the auto-mode safety classifier? Anthropic's own evaluation measured a 17% false-negative rate on real overeager actions. One in six slips through: a useful layer, not a boundary.

Also: --dangerously-skip-permissions "offers no protection against prompt injection or unintended actions" (the docs' own words). And the default sandbox blocks writes outside your project but can read your entire disk, ~/.ssh and ~/.aws/credentials included, unless you write deny rules. Defaults protect your repo, not your secrets.

host: your ssh keys, cloud creds, everything container / VM: scoped token, egress allowlist worktree: disposable branch, commit per iteration the loop caps + deny rules poisoned issue / PR / CI log should die here, not on the host

The checklist, ordered so each line survives even if every line above it fails:

  1. Contain it. Unattended loops run in a container, VM, or cloud sandbox. Anthropic's compiler writeup says it in parentheses like it is obvious: "(Run this in a container, not your actual machine)." People still skip it.
  2. Scope the credentials. A dedicated token (claude setup-token), repo-scoped git credentials, no ~/.ssh or cloud credential files mounted, prod secrets absent. The loop cannot leak what it cannot read.
  3. Pin the egress. Default-deny outbound with an allowlist (registries, GitHub, the API). Injection that cannot phone home is a much smaller problem. Filtering is by hostname; a broad entry is a tunnel.
  4. Write deny rules that hold in every mode. permissions.deny for .env and credential paths, ask rules for git push, a PreToolUse hook refusing rm -rf. Deny beats ask beats allow, including in the dangerous mode.
  5. Cap the spend. Budget and turn caps per iteration, an iteration cap on the loop, a workspace-level monthly cap per API key. There is no official per-loop budget; you are the circuit breaker.
  6. Treat inbound text as hostile. Issues, PRs, tickets, CI logs are instructions from strangers. That loop gets read-only tools where possible, and never the same loop that holds write access to your infrastructure.
  7. Human gate at the end. The loop opens a PR; it does not merge one. Review is where slop and sabotage both die. This is the one gate no serious harness skips.

If you adopt only one line, make it the first. Inside a container with scoped credentials, every incident in the failure table becomes an anecdote instead of a disaster.

When to write a loop, and when not to

The skeptics are half right, and theirs is the sharpest engineering critique. A Hacker News comment on Ronacher's essay describes teams splitting into an "agent goes brrr" camp shipping code it does not understand and reviewers "holding the line on quality, and just exhausted." Another commenter puts the economics bluntly: the bottleneck is human verification and accountability, and parallel agents cannot fix that. Armin Ronacher's is the most precise: loop output is too defensive, too complex, too local; "if each iteration adds another small defense, the system slowly becomes less understandable while appearing more robust."

The practitioners' rebuttal: move the human work upstream and make verification mechanical. Write the spec you could hand a junior, loop against tests that automate your eyes, review the diff like a stranger wrote it. Both camps describe the same system; the difference is whether the verification was engineered or vibed.

The honest decision test. A task is loop-shaped when:

Fail two and you want a supervised session; fail three and you want to do it yourself. The strongest advocates agree more than the discourse suggests: Huntley calls Ralph greenfield-only, and Cherny's loops babysit PRs rather than architect systems. Output got cheap; judgment did not.

Loops compound whatever your verification actually measures.

That is the thesis this guide has been circling. Point a loop at a mechanical keep-or-revert signal and capability compounds; point it at a signal the agent grades itself on and slop compounds at the same rate. Every loop question (when do I stop it, how do I stop it lying, why did it cost $6,000, is the code any good) is one question in different clothes: who verifies, with what tools, at what cost? Answer that and the rest is mechanical. Now run Loop 1. Literally this, in your next session: /goal all tests pass and lint exits 0; or stop after 20 turns. Sixty seconds, and unlike 181 of the first 182 people in that thread, you will have receipts.

Method and caveats

References