← Back to Blog

Loop Engineering in Claude Code: Let the Agent Run Itself

Key Takeaways

  • Loop engineering is designing the system that runs your agent in a cycle (find work, do it, check the result, decide the next move) instead of hand-prompting every turn.
  • Loops aren’t just for chores. Point one at a single task and it iterates to done on its own, new feature work included.
  • A self-paced /loop lets Claude decide when it’s done (fine when “tests are green” is trustworthy); /goal makes a separate model confirm your condition every turn (use it when a false “done” is costly).
  • Triggers form a durability ladder: /loop (session), Desktop scheduled tasks (local, app open), and Routines via /schedule (cloud, runs with your laptop off).
  • The verifier is the whole game: close every loop on a check Claude can’t fake, then cap it with a turn limit and a budget.

Most developers still run their coding agent by hand: type a prompt, wait, read the diff, type the next one. Loop engineering is the shift to building a small system that does that for you: it finds the work, runs the agent, checks the result, and decides whether to go again.

Two things make that worth doing. A loop can drive a single task all the way to done without you babysitting each turn — a refactor, a migration, a whole feature — or it can run recurring work on a schedule, like triaging CI every night. In Claude Code both are real commands, not a wrapper you script yourself. The part that decides whether any of it works is the verifier: the check that says “done,” and whether Claude can fake it.

What Loop Engineering Is

Loop engineering is designing the cycle your coding agent runs in: it finds work, hands it to the model, checks the result against a real signal, and decides whether to go again, without you in the chair for each step. You build the loop once; it prompts the agent from then on. The shift is from typing prompts to designing the thing that types them. In Claude Code, that cycle is a few built-in commands you compose: /goal, /loop, and /schedule.

A loop is more than a trigger. It has a skill that says what to do, a verifier that decides when it’s done, guardrails that keep it from doing damage, and usually some state so it remembers across runs. Skip the verifier and you don’t have a loop. You have an agent agreeing with itself on repeat, billing you for it.

And it does one of two jobs, which are worth keeping separate in your head:

  • Run one task until it’s done — point the loop at a single goal (make these tests pass, finish this migration, ship this feature) and let it iterate to completion on its own.
  • Run work on a schedule — fire the loop on a cadence or an event (every night, on every merged PR) for recurring chores.

Most write-ups only cover the second. The first is where loops quietly change how you build.

Run One Task Until It’s Done

You don’t need a recurring chore to justify a loop. The everyday use is pointing one at a single piece of work and letting it run to completion: a refactor that has to keep the build green, a migration across a dozen modules, a feature that isn’t finished until its tests pass. Two commands do this, and the choice between them comes down to who decides it’s done.

Self-paced /loop (drop the interval) lets Claude work, check itself, and stop when it judges the job complete. Reach for it when the success check is something Claude can honestly verify in-context (tests pass, the build is green) and you don’t mind it occasionally declaring victory a little early.

> /loop migrate the user service to the new logger, keep the build green and the
        tests passing the whole way, and stop when the service is fully migrated

  migrates the auth module, runs build + tests — green
  next pass in 2m: auth done; billing and webhooks still on the old logger

  migrates billing, reruns — green
  next pass in 2m: webhooks left

  migrates webhooks, full build + test run passes
  done: user service migrated, build green, 0 failing tests. ending the loop.

That “next pass in N minutes” line is real: with no interval, Claude picks the delay after each iteration and prints why, then ends the loop itself once the work is provably complete (scheduled tasks docs). The catch is in that word itself. Claude is grading its own homework, so a self-paced loop can decide it’s finished when it isn’t.

/goal closes that gap. You write a completion condition, and after every turn a separate small model (Haiku by default) checks whether the condition holds — completion is decided by a fresh model, not the one doing the work (goal docs). Use it when “done” is fuzzier or the cost of a false “done” is high. That’s exactly new feature work, where the model has every incentive to fake the finish: the condition has to be proven, and Claude can’t route around the hard part by editing the test or hardcoding the answer.

> /goal the tests in test/ratelimit pass via `pnpm test ratelimit`, with no edits
        to the test files and no hardcoded values, or stop after 25 turns

◎ /goal active · turn 1 · 0m
  writes src/ratelimit/window.ts, runs the suite
  evaluator → no: 2 of 7 fail, the burst window resets early. keep going.

◎ /goal active · turn 3 · 4m
  fixes the reset boundary, reruns
  evaluator → no: 7/7 green, but ratelimit.test.ts was edited. revert it.

◎ /goal active · turn 4 · 6m
  reverts the test, moves the fix into the source, reruns
  evaluator → yes: 7/7 pass on the unmodified tests. goal achieved.

One accuracy note that changes how you write conditions: the evaluator can’t run commands or read files on its own. It only judges what Claude has already surfaced in the conversation. So “the tests pass” only means something if the loop actually runs the tests and shows the output — write the condition as something Claude’s own output can demonstrate, and the or stop after N turns clause so it can’t run forever. A live ◎ /goal active indicator and a /goal status view show the condition, turns evaluated, token spend, and the evaluator’s latest reason.

/loop (self-paced) /goal Stop hook
Next turn starts when A delay elapses The previous turn finishes The previous turn finishes
Stops when Claude decides it’s done (or you stop it) A separate model confirms your condition Your own script decides
Who judges “done” Claude itself An independent model (Haiku by default) Your code

Under the hood /goal is a wrapper around a session-scoped Stop hook, the same mechanism you’d reach for to script your own completion logic. Both fire after every turn; /goal just ships the evaluator for you.

Run Work on a Schedule

The other mode is recurring work: nightly CI triage, weekly dependency bumps, lint-and-fix on every pull request. Here a loop earns its keep only when the work recurs often enough to amortize the setup, a check can fail bad output automatically, and you’re fine spending the tokens each run costs. If it happens less than weekly, a single sharp prompt is cheaper than a loop.

Three triggers run recurring work, and they differ by how long they survive — a durability ladder, not a single command (scheduled tasks docs).

  1. /loop with an interval — session-scoped. /loop 15m fix any CI failures on this PR and ping me when it’s green fires on a timer inside your terminal session. It runs as long as that session stays open and the machine is awake, so you can leave it going overnight on a machine you don’t shut. Press Esc to stop it; it also auto-expires after seven days.
  2. Desktop scheduled tasks — local, app-persistent. Created from the Claude Code desktop app, these survive a restart and keep their own skill file on disk, but they only run while the app is open and the computer is awake. More durable than a session, still tied to your machine.
  3. Routines (/schedule) — cloud. A routine runs on Anthropic’s infrastructure in a fresh checkout, so it keeps running with your laptop closed. This is the one to reach for when you want the job truly hands-off, not because /loop can’t run overnight, but because you shouldn’t have to keep a terminal open to babysit it.
> /schedule nightly at 2am, review every PR merged that day, flag risky
            changes, and post a summary to the #eng channel

  created routine "nightly-merge-review"  (cloud)
  trigger: schedule · 0 2 * * *  (America/New_York)
  runs on Anthropic's infrastructure — no session required
  manage at claude.ai/code/routines

From the CLI, /schedule creates time-based routines; API and GitHub triggers (fire on a webhook, or on a PR or release) are added on the web (routines docs). The trade-off for that durability: a routine clones your repo fresh each run and has no access to your local files.

/loop Desktop task Routine (/schedule)
Runs where Your terminal session The desktop app Anthropic cloud
Survives closing the session No Yes Yes
Survives the machine off No No (needs app open + awake) Yes
Sees local files Yes Yes No (fresh clone)
Best for Tending work while you’re at the keyboard Daily local reviews Unattended, laptop-closed jobs

The Skill, the State, and the Guardrails

The trigger is only the heartbeat. What the loop actually does each run lives in a skill — a SKILL.md file with YAML frontmatter and plain-Markdown instructions. The format is worth seeing, because it does more than hold text: the !`command` syntax runs a shell command and drops its output into the skill before Claude reads it (skills docs).

~/.claude/skills/review-diff/SKILL.md
---
description: Summarize uncommitted changes and flag anything risky. Use when
  the user asks what changed, wants a commit message, or asks to review a diff.
---

## Current changes

!`git diff HEAD`

## Instructions

Summarize the changes above in two or three bullet points, then list any
risks you notice: missing error handling, hardcoded values, tests that need
updating. If the diff is empty, say there are no uncommitted changes.

That “injected diff” trick is how a skill starts every run with fresh, real context instead of a stale description — the same context-engineering discipline, automated. When a loop needs to act, not just report, the allowed-tools field grants specific commands without a prompt each time:

---
name: commit
description: Stage and commit the current changes
disable-model-invocation: true
allowed-tools: Bash(git add *) Bash(git commit *) Bash(git status *)
---

State is what the loop carries between runs: what it already tried, what it escalated, what it learned. Keep it in a file the next run reads — a checklist, a tracker, a small STATE.md — not in the chat, where the next run can’t see it. A scheduled task even stores its own skill on disk for exactly this reason. Guardrails are the last part: allowed-tools and disallowed-tools bound what the agent may touch, and anything irreversible — a deploy, a migration, a dependency bump — should sit behind a human-in-the-loop approval gate. This is the same logic as harness engineering: the agent that wrote the code is the wrong one to decide it’s safe to ship.

Stops and Cost Control

An unattended loop is a loop spending money while you’re not watching, so every loop needs three guards working together.

  1. A hard stop. Append or stop after N turns to a /goal, press Esc on a /loop, or set a budget on a Routine. A /loop auto-expires after seven days, but seven days is a long time to be wrong.
  2. A real stop condition. Not “when it looks done,” but a checkable one — which is what the verifier gives you. The stop condition and the verifier are the same lever.
  3. A spend cap. Run /cost to watch tokens, and mind the interval: a loop polling every few minutes is the costly middle ground, because the prompt cache goes cold between runs and each wake rebuilds context from scratch.

The cheapest safety feature is built in. A /loop dies when you close the session, so if one misbehaves, closing the terminal stops it. A Routine doesn’t — it keeps spending precisely because it survives, which is why the cloud one is the one that needs a real budget and tight guardrails before you walk away.

Build Your First Loop

Before you automate a task, run it through five questions:

  1. Can a test, type check, build, or linter reject bad output? No automated gate means the agent grades its own homework.
  2. Can the agent run the code and see what breaks? No reproduction means it iterates blind.
  3. Is “done” something Claude’s output can prove? If a false “done” is expensive, that’s a /goal, not a self-paced /loop.
  4. Does it have a hard stop — a turn cap, budget, or time limit? Without one it runs until someone notices the bill.
  5. Is anything irreversible gated behind a human? Merges, deploys, and dependency changes need an approval step.

Then build the smallest version. For run-until-done work, start with /goal and a condition you can prove. For a recurring chore, pick the trigger by durability: a /loop while you’re at the keyboard, a Routine for hands-off. Write the work into a SKILL.md, cap the turns, and watch the first few runs before you trust it. A loop is fast to start and easy to get wrong; the small version teaches you which.

A loop is only as good as the signal it closes on — and that signal is your codebase’s tests, types, and guardrails. Structured vibe coding and our AI agent starter kit ship that verifier layer: quality gates, review subagents, and scoped permissions, so a loop has something real to check against instead of its own opinion. See editions from $149 →

Frequently Asked Questions

Can I use a Claude Code loop to build a new feature, or just for recurring chores?

Both. Point /goal or a self-paced /loop at a single task and it iterates until done without you re-prompting each turn — a refactor, a migration, a whole feature. /goal is the safer choice for feature work, because a separate model has to confirm your condition is met, so Claude can't quietly declare victory.

What's the difference between /loop and /goal in Claude Code?

A self-paced /loop runs until Claude itself decides the work is done. /goal runs until a separate small model confirms your written condition after each turn. Use /loop when Claude can honestly verify success in-context (tests pass, build green); use /goal when a false 'done' is costly.

Does a Claude Code loop run after I close my laptop?

A /loop runs inside your session, so it keeps going only while that session is open and the machine is awake — you can leave it running overnight that way. For hands-off automation with the laptop closed, use a Routine (/schedule), which runs on Anthropic's cloud independently of your machine.

How does /goal know when the work is done?

After each turn, a small fast model (Haiku by default), separate from the one writing code, checks your condition and returns yes or no plus a reason. It can't run commands or read files itself, so write the condition as something Claude's own output can demonstrate — for example, have it run the tests and show the result.

How do I stop a loop from running up a bill?

Cap it: add 'or stop after N turns' to a /goal, press Esc to end a /loop, set a budget on a Routine, and watch /cost. Closing the session ends any /loop immediately, since it lives in the session.

Have more questions? See our full FAQ →