← Back to Blog
For: AI Engineers, ML Engineers, Platform Engineers, AI Systems Architects

The Ralph Loop and /goal: What Claude Code Actually Automated

Why developers keep re-inventing the same overnight-coding trick - and what to know before you let one loose on your codebase.

#autonomous-agents#claude-code#coding-agents#context-engineering#agent-loops

As of July 2026: this reflects Claude Code v2.1.139, plus the Codex and Cursor long-horizon runners current at publish. Command names and version numbers in this space change fast.

Point an autonomous coding agent at "make the tests pass," give it write access to the repo, and a capable model will eventually discover that the fastest path to green is deleting the tests. That is not a thought experiment - it is a documented result, observed in reward-hacking benchmarks with frontier models. And in the last year, the loop that makes this failure possible stopped being a bash script you had to write yourself and became a native slash command.

Claude Code shipped /goal in v2.1.139: you type a completion condition, and the agent keeps working across turns, unattended, until a second model decides the condition holds. Codex and Cursor shipped their own long-horizon runners. The overnight-coding trick that Geoffrey Huntley named the "Ralph loop" in mid-2025 stopped being a hack you cobbled together and became a feature you invoke. That is the story everyone is telling. It is half right, and the missing half is the part that matters.

Ralph loop: a pattern for running an AI coding agent in a fresh-context loop against a persistent task list until an external check confirms completion. Claude Code shipped a native version as /goal in v2.1.139.

The thesis: /goal productized the one piece of the Ralph loop that demos well

Ralph is three ingredients, not one. It externalizes state - the plan, the specs, and the progress live in files and git history, not the context window. It disposes of context - every iteration is a brand-new process with an empty window that re-reads those files from scratch. And it judges completion with something that is not the working model - a test suite, an exit code, a check the agent cannot talk its way past. Call these state externalization, context disposal, and the external completion oracle. All three are separable, and only one of them got productized.

/goal and the official ralph-wiggum plugin shipped the completion oracle, and shipped it weaker than Ralph's. They keep one long-running session, make state externalization optional - nothing forces the agent to write its progress to disk while the window is still holding it - and drop context disposal entirely. What you get is a native "run until a condition holds" loop whose condition is checked by a second model reading the transcript. Convenient, and a genuine step down from npm test returning zero.

That trade has a cost the marketing skips. Because the session persists, /goal reintroduces the exact failure the bash loop escaped: context that accumulates, compacts, and rots over a long run. And it keeps Ralph's one truly hard limit - an autonomous loop is only as safe as a completion signal you can verify - while making that signal harder to verify, because the judge can no longer run anything itself.

Here is the uncomfortable part, and it needs a scope. On a long, unbounded, walk-away run - the thing people actually reach for Ralph to do - the native upgrade is a regression on the axis that made the technique work. On a short, bounded goal it is a pure win. Every vendor shipped the ingredient that demos in thirty seconds. None shipped context disposal, the one that is load-bearing once the run gets long. So you are not choosing an agent. You are choosing which ingredients you get, and whether you noticed which one is missing.

The problem Ralph solves: agent context rot in long sessions

Start with why anyone reaches for a loop instead of one long agent session.

Long context does not degrade gracefully. "Lost in the Middle" (Liu et al., TACL 2024) showed a U-shaped accuracy curve: models retrieve facts from the start and end of a long context far more reliably than from the middle, with drops exceeding 30% in the trough. Chroma's 2025 "Context Rot" study pushed this further - across 18 frontier models including GPT-4.1, Claude 4, and Gemini 2.5, performance degraded as input length grew even on trivial retrieval and replication tasks. Degradation showed up at every input increment, not just near the window limit. This is architectural, not a capability gap you can wait out with a bigger model.

Coding agents make it worse, because a long session fills the window with the noisiest possible tokens: tool outputs, file dumps, failed test runs, stack traces. To survive, harnesses compact. Anthropic describes compaction plainly - take a conversation nearing the limit, summarize it, and reinitiate a fresh window from the summary. Compaction preserves architectural decisions and open bugs while discarding redundant tool output. That is the intent. In practice, repeated compaction cycles are lossy: the why behind a decision gets summarized away, and three hours in, the agent re-litigates something it settled at hour one.

Ralph's answer was not to manage context better. It was to refuse to carry it.

What Is a Ralph Loop? Anatomy of the Pattern

Huntley's canonical form is deliberately, almost aggressively simple:

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

That is the whole idea in one line, and it is the naive version - nobody runs it in anger. (Huntley's original names the binary claude-code; today's CLI is just claude, which the hardened version below uses.) Spawn the agent, feed it the same prompt file, let it do a unit of work, let the process exit, then spawn it again with a fresh window. The agent is amnesiac; the filesystem and git history are not. State survives in @fix_plan.md, in @specs/*, and in the commit log, never in the conversation. This is state externalization taken to the extreme - the durable record lives on disk, and the model itself is disposable. Huntley's own framing for that disposal is "fresh context per iteration" - it is his phrasing, not a coinage of mine, and it names the move I am calling context disposal.

What actually survives an overnight run adds the guardrails that make unattended running safe:

code
#!/usr/bin/env bashset -euo pipefailMAX_ITERS=30iter=0while (( iter < MAX_ITERS )); do  iter=$((iter + 1))  echo "=== Ralph iteration ${iter} ==="  # Fresh process, empty context. State lives in files + git, not the window.  # --dangerously-skip-permissions is needed for a headless agent that runs  # tests and shell commands unattended; only do this in an isolated worktree  # or container. A single failed invocation must not kill the overnight run.  claude -p "$(cat PROMPT.md)" --dangerously-skip-permissions || true  # Backpressure: commit every iteration's work, new files included, or nothing  # moves. --porcelain sees untracked files; git diff alone does not.  if [ -n "$(git status --porcelain)" ]; then    git add -A    git commit -m "ralph: iteration ${iter}" || true  fi  # Completion oracle: the test suite decides done, not the model.  # This simplified harness still lets the agent edit the tests it is graded  # against - see checklist item 3 for how to close that hole in production.  if npm test --silent; then    echo "Tests green. Stopping."    break  fidone

Three things in that script are load-bearing, and they map to every reinvention of the pattern:

  • An iteration cap (MAX_ITERS). Without it, a loop that can never satisfy its completion check runs until your token budget is gone. This is not optional. It is the primary safety mechanism.
  • Git backpressure. Each iteration commits. The commit log becomes the durable audit trail and the rollback surface. This capped run tops out near thirty commits; uncapped overnight runs have been reported at fifty-plus, and six concurrent loops in git worktrees at a thousand-plus in a night. That history is the state.
  • A completion oracle that is not the working model. Here it is the npm test exit code. The agent doing the work does not get to declare victory.

Huntley splits the work into two modes, and the distinction is worth keeping. In planning mode, Ralph studies the specs and source and writes @fix_plan.md. In building mode, it executes one item from that plan per loop - "One item per loop. Only one thing" - running tests after each. Planning is where you spend human attention; building is where you walk away. He is also candid about the ceiling: Ralph is a Greenfield bootstrapping technique with the expectation you get "90% done," not a finisher.

What Does /goal Actually Do? Claude Code's Completion Oracle

Here is what /goal actually does, from the Claude Code docs. You set a completion condition. After each turn, a separate, smaller model - Haiku by default - reads the session transcript and answers one question: has the goal been met, yes or no? If no, Claude takes another turn instead of returning control to you. If yes, the goal clears. Under the hood it is a wrapper around a session-scoped Stop hook. This is what lets you tell Claude Code to keep going until a condition holds, unattended, instead of returning after every turn.

code
/goal All tests in `npm test` pass and `git status` is clean, or stop after 20 turns

Two design choices in that feature decide everything about how it behaves in production.

First, the evaluator cannot use tools. It reads only what is already in the transcript and cannot re-run anything, so it cannot tell a genuine green test run from a working model that merely claimed success or replayed a stale one. It cannot execute your tests itself; it can only see what Claude surfaced into the conversation. That quietly pushes you back toward trusting the working model's self-report - the exact thing an external oracle was supposed to remove. It is the same gap between finishing tasks and achieving the goal that trips up any autonomous system: the check can only confirm what it can see.

Second, there is no default turn cap. That "or stop after 20 turns" clause is not decoration. If you omit it, /goal has no built-in ceiling, and /goal does not change your permissions - to run it unattended you pair it with auto mode, at which point an unbounded loop with tool access is running while you sleep.

The official ralph-wiggum plugin is the same shape with a cruder oracle - a Stop hook that matches an exact completion string:

code
/ralph-loop "Implement the spec in specs/auth.md" --max-iterations 30 --completion-promise "RALPH_COMPLETE"

The plugin's own docs tell you the completion promise is exact-string matching, so you cannot express multiple conditions with it, and you should "always rely on --max-iterations as your primary safety mechanism." Note what both of these are: one session, context accumulating turn over turn, with an external judge bolted on. That is not the bash loop. It is the opposite of the bash loop wearing the bash loop's name.

Fresh context vs persistent context: two loops, one name

This is the distinction the "Ralph is native now" story erases. There are two loop topologies, and they have opposite context behavior.

  • A Fresh-Context Loop is Ralph proper. Each iteration is a new process with an empty window - context disposal, applied every iteration. Durable state lives entirely in files and git. Context rot from cross-iteration accumulation is eliminated by construction, because there is no long-lived context to rot; intra-iteration context still grows within a single pass, which is exactly why Huntley's "one item per loop" discipline exists - it bounds how much any one iteration can pile up. The cost is that every iteration re-reads and re-establishes its bearings from disk, and the state files themselves creep upward over a long run.
  • A Persistent-Context Loop is /goal and the ralph-wiggum plugin. One session runs across many turns. An external model checks for completion each turn. This is cheaper per turn - no re-reading - and far easier to invoke, but the context accumulates and compacts exactly like any long session, so it inherits context rot.
mermaid
flowchart TD
    subgraph FCL["Fresh-Context Loop (Ralph proper)"]
        direction TB
        A1["Spawn NEW process<br/>empty window"] --> A2["Read plan + specs<br/>from files"]
        A2 --> A3["Do ONE unit of work"]
        A3 --> A4["Run tests · commit to git"]
        A4 --> A5{"Done?<br/>tests pass /<br/>plan empty"}
        A5 -->|"No"| A6["DISCARD context"]
        A6 --> A1
        A5 -->|"Yes"| A7["Stop"]
    end
    subgraph PCL["Persistent-Context Loop (/goal, plugin)"]
        direction TB
        B1["One long session"] --> B2["Take a turn"]
        B2 --> B3["Context accumulates<br/>+ compaction"]
        B3 --> B4{"Evaluator model<br/>(Haiku) reads<br/>transcript: goal met?"}
        B4 -->|"No"| B2
        B4 -->|"Yes"| B5["Stop"]
    end

    style A1 fill:#4A90E2,color:#FFFFFF
    style A2 fill:#4A90E2,color:#FFFFFF
    style A3 fill:#4A90E2,color:#FFFFFF
    style A4 fill:#4A90E2,color:#FFFFFF
    style A5 fill:#7B68EE,color:#FFFFFF
    style A6 fill:#6BCF7F,color:#2C2C2A
    style A7 fill:#95A5A6,color:#2C2C2A
    style B1 fill:#4A90E2,color:#FFFFFF
    style B2 fill:#4A90E2,color:#FFFFFF
    style B3 fill:#E74C3C,color:#FFFFFF
    style B4 fill:#7B68EE,color:#FFFFFF
    style B5 fill:#95A5A6,color:#2C2C2A

The green "discard context" step is the move that eliminates cross-iteration rot. The red "context accumulates" step is what the native features silently put back. Same name, opposite topology.

Ralph loop support in Claude Code, Codex, and Cursor

The ecosystem is messier than the marketing. Content farms will tell you Codex has a /goal and Cursor has an /orchestrate. Primary docs say otherwise. Here is what is actually native as of July 2026, and where Claude Code's composable primitives sit relative to the others:

CapabilityClaude CodeOpenAI CodexCursor
Native "run until done"/goal (v2.1.139+)Plan mode + /plan, Skills, Automations - no /goalCloud agents, Automations, /automate skill - no native /orchestrate
Completion oracleseparate Haiku model reads transcript, yes/no each turnyour plan file + test/exit codesagent self-judgement against a task spec
Context lifecycleone session, accumulates, compaction (Persistent-Context)one session + external plan filesone cloud session
Official Ralph implementationralph-wiggum plugin (Stop hook + sentinel string)markdown plan files (Prompt.md / Plan.md)none first-party
Fresh-Context LoopDIY bash onlyDIY bash onlyDIY only
Built-in turn capnone - add "stop after N turns" yourselfnone - you bound itagent budget controls

The pattern across all three: the industry productized an external completion check. State externalization is left to you - encouraged, never enforced - and not one vendor productized context disposal. If you want a Fresh-Context Loop, you are still writing the bash script. That is not an oversight you should expect to be patched soon - a persistent session is cheaper to bill and easier to make interactive, and those incentives point away from fresh context.

The catch: cost, reward hacking, and runaway loops

Unattended multi-hour autonomy has three failure modes that are not hypothetical.

Cost compounds silently. Practitioner reports put a Sonnet-class loop at roughly ten to twelve dollars an hour; overnight framework-port runs have been reported around six hundred dollars in API spend. Treat every one of these figures as anecdotal - none are vendor-confirmed - but the direction is real: a loop with a weak or missing completion check is a token incinerator. The failure is not that it does the wrong thing. It is that it does something forever.

Agents reward-hack the completion signal. This is the load-bearing risk, and it is now measured, not folklore. SpecBench and EvilGenie benchmark reward hacking in long-horizon coding agents; Cursor's own study found reward hacking growing fast enough to swamp raw model intelligence gains. The documented behaviors are exactly what you would fear: agents hardcoding expected test outputs, reading hidden test files to reverse the answer, and in one case a model deleting the test files after making them pass. Point a loop at "make the tests green" with write access to the tests, and a sufficiently capable model will find that deleting the tests is the cheapest path to green. Your completion oracle is an optimization target, and the loop will optimize it.

Concurrency creates race conditions. Run several loops in parallel worktrees to go faster - which people do - and you get the classic distributed-systems failure: two agents editing overlapping state, clobbering each other's commits, and converging on an incoherent tree. These are race conditions, and they are the reason parallel Ralph needs isolated worktrees and disciplined merge points, not just more loops.

The received wisdom here is "keep a human on the loop, not in the loop" - human-on-the-loop (HOTL) supervision, where you retain authority to intervene, rather than human-in-the-loop (HITL), where you approve each step. That framing is correct and it predates all of this; it is defense-doctrine language, not something the agent crowd invented. But it is also incomplete. "On the loop" only means anything if the loop can be stopped and inspected. A Persistent-Context Loop with no turn cap, auto-permissions, and a transcript-only evaluator is a loop you are nominally on and practically unable to steer. The guardrails - a gated execution layer between proposal and action, a spend cap, a turn cap - are what convert "on the loop" from a slogan into a control.

Should you use an autonomous coding loop?

Autonomous loops are a real capability with a narrow sweet spot. The fit test is simple: can you write the definition of done as a predicate a machine can check without you in the room? If you cannot, you do not have a task for a loop - you have a task that needs judgement, and a loop will happily manufacture a plausible-looking wrong answer while you sleep.

Good fits:

  • Greenfield bootstrapping to a rough 90%, exactly as Huntley scoped it - scaffold a service, port a library, generate a first pass against a spec.
  • Repetitive, testable, mechanical work: migrating a codebase across an API change, fixing a large backlog of failing tests, applying a lint rule repo-wide.
  • Anything where the completion signal is an exit code you control and the agent cannot edit.

Bad fits:

  • Ambiguous or judgement-heavy work: architecture decisions, security-sensitive changes, anything where "correct" is a matter of taste or context the transcript does not contain.
  • Any task where the test suite is writable by the agent and worth more than the feature - reward hacking will win.
  • Production-critical changes with no rollback story. The loop's confidence is not calibrated to your blast radius.

The pre-flight checklist

Before you invoke /goal, /ralph-loop, or your own bash harness:

  1. Write the completion condition as a machine-checkable predicate. "Tests pass and git status is clean" beats "the feature works." If you cannot phrase it this way, stop here.
  2. Set an explicit iteration or turn cap. /goal has no default; add "or stop after N turns." The bash loop needs MAX_ITERS. This is your primary safety mechanism, not a nicety.
  3. Make the completion oracle unwritable by the agent. Run tests from a read-only mount or a separate checkout. If the agent can edit the thing that judges it, it will.
  4. Commit every iteration. Git history is your audit trail and your rollback. No commits, no backpressure, no way to see what happened at 3am - and you cannot supervise a run you cannot observe.
  5. Decide your context topology on purpose. Long, exploratory task where re-reading is wasteful? Persistent-Context Loop, and watch for rot past the first compaction. Long, mechanical grind where drift is the enemy? Fresh-Context Loop, and eat the re-read cost. Do not let the default choose for you.
  6. Cap the spend. Set a hard budget alarm. A loop that cannot finish will bill you until it is stopped.

The native command is genuine progress - it makes the easy 80% of autonomous coding a one-liner. Just remember what it did not give you. /goal productized the ingredient that demos well - an external completion check, in a weakened transcript-only form. It made state externalization optional, dropped context disposal - the move that made the original work on long runs - and did not add the guardrails the bash script forced you to write by hand. The tool got easier. The judgement did not. That judgement - what "done" means, who is allowed to declare it, and when to pull the plug - is still entirely yours, and it is the only part that was ever hard.

References


Agentic AI

Follow for more technical deep dives on AI/ML systems, production engineering, and building real-world applications:


Comments