Loops and autonomous runs: /loop, iteration, and scheduling
# Loops and autonomous runs: iteration and scheduling
Some work is not one task but the same task on repeat until a condition is met. "Keep fixing bugs until every test passes." "Poll the deploy every minute until it's live, then tell me." "Every morning at 8, summarize what changed in the repo overnight." None of these is a single action. They are loops with a stop condition, and Claude Code handles them in two different ways depending on whether the repetition happens inside one run or across many scheduled runs.
This lesson is about running Claude Code in a loop: iterating until a goal is met, running autonomously without you babysitting each step, and scheduling an agent to fire on a cadence. Just as importantly, it is about the guardrails that keep an unattended run from burning your tokentokenA token is the basic unit of text that language models process, often a word fragment, whole word, or punctuation mark rather than a single character.View full definition → budget or your codebase.
When a loop is the right tool
A loop is worth reaching for when three things are true:
- The task repeats. You are doing the same operation more than once (poll, retry, iterate).
- There's a measurable stop condition. Tests pass, a build is green, a file exists, a value crosses a threshold. If you cannot describe "done" precisely, you cannot loop safely.
- The outcome improves with each pass, or you're waiting on an external change. Fixing tests improves each pass. Polling a build waits for something external.
If none of those hold, a single call is cleaner and cheaper. Do not loop just because you can. "Refactor this function" is one call. "Refactor every function until the linter is silent" is a loop.
How iteration actually works in Claude Code
Claude Code runs in your terminal and already has access to your files, your shell, and your git history. There is no dedicated loop command. Instead you get iteration in one of two ways.
The first way is inside a single run. You describe the task, the stop condition, and a hard cap directly in your prompt, and Claude runs the work, checks its own output against the condition, and repeats until the condition is met or the cap is hit. Claude can run a command, read the result, decide whether it's done, and act again, all within one session. This is the "keep going until X" pattern.
The second way is a shell loop around headless mode. You call claude -p "..." (a single prompt that runs and exits) from a bash while loop or a script, and your script owns the repetition, the delay between runs, and the exit logic. This is the right shape for polling something external on a fixed interval, because your script controls the timing.
Here's the in-session pattern. You give Claude the task, the stop condition, and the ceiling in plain language:
claude
> Run the full test suite with pytest. If any tests fail,
read the failure output, fix the SOURCE code (not the tests),
and run the suite again. Repeat until all tests pass or you
have made 10 attempts. If you hit 10 attempts, stop and tell me
what is still failing.Notice three things baked into that prompt. The task is explicit (run pytest, fix code). The stop condition is unambiguous (all tests pass). And there's a hard cap (10 attempts) so a stubborn bug cannot spin forever.
And here's the interval pattern as a shell loop, useful when you're waiting on something outside your control:
# Poll a deploy every 30s, up to 20 times, until it returns 200
for i in $(seq 1 20); do
if curl -sf -o /dev/null https://myapp.example.com/health; then
claude -p "The deploy is live. Post a one-line 'shipped' note."
break
fi
sleep 30
doneThe script owns the loop, the delay, and the cap. Claude only runs when there's something to do.
Why the stop condition must be machine-checkable
"Fix the code until it looks good" is not a loop condition. Claude has no objective signal to check against, so it will either stop arbitrarily or keep polishing forever. "Fix the code until pytest exits 0" is checkable: the exit code is a fact, not a judgment.
Good loop conditions are things a command can answer:
pytestexit code is 0npm run buildsucceedsgit statusshows no uncommitted changes- a specific string appears in a log file
- an HTTP endpoint returns 200
Anytime you can express "done" as a command that returns success or failure, you have a loop you can trust to stop on its own.
The worked example: keep fixing until the suite is green
This is the canonical use case, so let's make it concrete. You have a test suite with a handful of failures after a messy merge. You want Claude to iterate: run tests, read what broke, patch the source, run again, until the whole suite is green.
claude
> Goal: make the entire test suite pass.
1. Run: pytest -x --tb=short
2. If exit code is 0, the goal is met. Stop and summarize what changed.
3. If tests failed, read the traceback, fix the SOURCE code only.
Never edit or delete tests to make them pass.
4. Commit each fix with a clear message, then repeat from step 1.
Budget: stop after 8 attempts and report if not green by then.Read the guardrails hidden in that prompt. Step 3 forbids the single most common cheat an autonomous agent will reachreachThe number of unique people exposed to your message in a given period. Unlike impressions, reach counts each person once, no matter how often they see it.View full definition → for: deleting the failing test. Left unconstrained, a model asked to "make tests pass" may well decide the fastest path is to remove the test. The instruction "fix the SOURCE code only, never edit tests" closes that door.
The commit-per-fix instruction matters too. It gives you a clean git history to review and an easy revert if one of Claude's fixes was wrong. When the run ends, you review the diff, not the process.
This is exactly the kind of task where iteration earns its keep. A single fix-and-stop would clear one round of failures, likely revealing a second round it never touched. Iterating grinds through all of it while you do something else.
Autonomous Test-Fixing Loops in Claude Code
Guardrails for unattended runs
The moment a run proceeds without you watching each step, it can do damage or waste money at machine speed. Three guardrails are non-negotiable.
1. A hard budget
Every loop needs a ceiling that does not depend on the model's judgment. An attempt count in the prompt is the simplest. If your script owns the loop, cap the number of iterations there (the for i in $(seq 1 20) above is exactly this). The budget is a backstop for when the stop condition never triggers, because sometimes the tests genuinely cannot be made to pass, and you want the run to give up and tell you, not keep trying forever.
2. A clear stop condition
Covered above, but it belongs on the guardrail list because it is the primary safety mechanism. The budget is the backstop; the stop condition is the intended exit. A run with a budget but no real stop condition just burns to the cap every time.
3. A human review gate
For anything that changes state (writes code, pushes commits, sends messages, calls a paid APIAPIApplication Programming Interface: a standardised interface that lets applications communicate and exchange data without knowing each other's internal workings.View full definition →), decide in advance where a human signs off. Two common patterns:
- Review at the end. The run proceeds fully autonomously but only produces a branch and a diff. You review before merging. This is the green-tests example: commits pile up on a branch, nothing ships until you say so.
- Review per action. You configure Claude Code's permission prompts so risky operations (file writes outside a directory, shell commands, network calls) require your approval each time. Slower, but appropriate when the blast radius is large.
You control this through Claude Code's permission settings. Constrain what tools the agent may use and which directories it may touch before you let it run unattended. The Claude Code documentation covers permission modes and tool configuration in detail.
A useful mental rule: the more autonomous the run, the tighter the sandbox. A run you watch can have broad permissions. One that fires at 3am while you sleep should be able to touch almost nothing except the one thing it's there to do.
Knowledge check
1. According to the lesson, what is the essential difference between a task that warrants a loop and one that should be a single call?
2. Why does the lesson insist that you must be able to describe 'done' precisely before using a loop?
3. A developer wants Claude to poll a CI deploy every minute and notify them once it goes live. Which /loop flavor fits, and why?
4. Select ALL statements that correctly describe the two flavors of the /loop command.
Select all the correct answers.
5. Select ALL elements that, according to the lesson, define the shape of a well-formed /loop command.
Select all the correct answers.
Scheduling: runs that fire on a cadence
Iteration answers "repeat until done." Scheduling answers "run again later, on a rhythm." A recurring digest, a nightly dependency check, a weekly report: these are not runs that spin until a condition, they are tasks that wake up on a schedule.
Claude Code does not run a background daemon. You schedule it the same way you schedule any command-line tool: with your operating system's scheduler (cron on Linux and macOS, Task Scheduler on Windows) or a CI runner like GitHub Actions.
Here's a nightly repo digest as a cron entry that runs Claude in non-interactive (headless) mode:
# Every weekday at 8:00 AM, summarize overnight git activity
0 8 * * 1-5 cd /home/me/project && \
claude -p "Summarize commits from the last 24 hours grouped by \
author. Flag anything touching auth/ or billing/. Output markdown." \
>> ~/digests/daily-$(date +\%F).md 2>&1The -p flag runs a single prompt and exits, which is exactly what you want for a scheduled job: no interactive session, deterministic start and finish, output redirected to a file. Pair this with an MCP server and the same scheduled run could post that digest to Slack or open a GitHub issue. See the Model Context Protocol docs for how to wire those connections.
For scheduling that lives with your repo rather than one machine, GitHub Actions is the natural home. Anthropic's official Claude Code GitHub Action lets you trigger Claude on a schedule or on repo events (a new PR, an issue labeled a certain way). A weekly on: schedule workflow that runs Claude to audit dependencies and open a PR is a clean, reviewable, scheduled autonomous run.
Scheduled runs need the same guardrails, more so
A scheduled job is autonomous by definition: nobody is watching when it fires. Everything from the guardrails section applies double. Give it a budget, give it a stop condition even for a one-shot digest (so a hung external call doesn't leave it running), and route anything that changes state through a review gate. A scheduled run that opens a PR is safe. A scheduled run that pushes to main is a foot-gun waiting for a bad night.
Iteration versus scheduling versus a single call
Quick decision guide:
- Single call: one task, no repetition, you're present. "Refactor this module."
- In-session iteration: describe the task, a checkable stop condition, and an attempt cap in one prompt; Claude repeats until done. "Fix until tests pass, max 8 attempts."
- Shell loop around
claude -p: your script owns the repetition and the delay, calling Claude on each pass. Best for polling something external on an interval. "Poll the deploy every 30s until live." - Scheduled run: wake up on a cadence, usually a fresh single task each time, via cron or GitHub Actions running
claude -p. "Every morning, digest the repo."
The most powerful setups combine them: a scheduled GitHub Actions workflow that fires nightly and, inside that run, has Claude iterate until its dependency upgrades pass CI. Cadence on the outside, iteration on the inside.
Key Takeaways
- Loop only when the task repeats and "done" is machine-checkable. If you cannot express the stop condition as a command that returns success or failure, you don't have a safe loop yet.
- Claude Code has no dedicated loop command. You get iteration by describing the task, stop condition, and an attempt cap inside one prompt, or by wrapping
claude -pin a shell loop that owns the repetition. - Every unattended run needs three guardrails: a hard budget (attempts, time, or tokenstokensA token is the basic unit of text that language models process, often a word fragment, whole word, or punctuation mark rather than a single character.View full definition →), a clear stop condition, and a human review gate on anything that changes state.
- Constrain the agent to the cheat. "Make tests pass" invites deleting tests; say "fix source only, never edit tests" and commit each fix so you review a clean diff, not a black-box process.
- Schedule with the tools you already have:
cron, Task Scheduler, or GitHub Actions runningclaude -pin headless mode. Claude Code is the worker, not the scheduler. - The more autonomous the run, the tighter the sandbox. A run you watch can have broad permissions; one that fires while you sleep should be able to touch only the one thing it exists to do.