How to Run a Ralph Loop With Claude Code
A ralph loop with Claude Code is the default path: install Ralph, log in to Claude once inside its sandbox, then run ./ralph.sh with no agent flag at all. Claude Code is Ralph’s default agent, so the bare command already wraps claude in headless print mode, hands it a fresh context window every iteration, and keeps re-running it against your task list until the work is done or you hit the iteration cap.
Claude is also the only agent whose output Ralph fully parses. That one fact is why the default is the default, and it changes what you see on screen while a run is happening. This post is the end-to-end setup walkthrough, from an empty repo to a reviewable diff. If the loop itself is new to you, read what the Ralph technique is first. If you want the flag-level mechanics of driving Claude in a loop, the companion piece is running Claude Code in a loop. Consider this the brand recipe and that one the mechanics deep dive.
The differentiator: Claude gives Ralph the richest live telemetry
Section titled “The differentiator: Claude gives Ralph the richest live telemetry”Every supported agent runs the same loop. Only Claude streams structured JSON back that Ralph can read line by line.
Look at scripts/lib/agents.sh. The helper agent_uses_stream_json() returns success for exactly one agent:
agent_uses_stream_json() { [ "$1" = "claude" ]}And build_agent_command() emits Claude with two extra flags no other agent gets:
sbx run --name ralph-claude-<project>-<hash8> claude . -- --output-format stream-json --verbose -p "$PROMPT_CONTENT"The --output-format stream-json --verbose pair turns Claude’s run into a stream of JSON events instead of an opaque wall of text. Ralph consumes that stream to power its live step detection (Thinking, Implementing, Testing) and the rolling preview of what the agent is doing right now. With Codex, Cursor, Copilot, Gemini, or OpenCode you still get a working loop, commits, and history logs, but you do not get the same real-time read on the agent’s internal state. If you want to watch a long unattended run and actually know what phase it is in, Claude is the agent with the most to say.
That is the practical case for the default. Not model quality (pick whatever your account supports), but observability. Ralph was built around Claude’s stream first, and everything else is a good citizen on top of the same machinery.
Two ways to loop Claude: the Stop Hook plugin vs the hackable script
Section titled “Two ways to loop Claude: the Stop Hook plugin vs the hackable script”Anthropic shipped an official way to loop Claude Code: a plugin that registers a Stop Hook, which fires when Claude finishes and re-injects the prompt so it keeps going. It lives in the Claude Code docs, and it is a clean, in-process way to keep a single Claude session churning.
Ralph takes the other road. Instead of a hook that resumes the same session, ralph.sh is a Bash loop that spawns a brand new claude process every iteration inside a Docker sandbox, with a clean context window each time. This is the original shape of the technique, popularized in Geoffrey Huntley’s Ralph writeup, where “Ralph is a Bash loop” is the whole point.
The trade-off is deliberate:
- The Stop Hook keeps one session alive and re-prompts it. Continuity is the feature.
ralph.shthrows the session away and rebuilds state from disk. Discarding continuity is the feature, because it kills context rot on long runs.
Ralph’s script is also yours to edit. The verification stack, the sandbox boundary, the prompt, the step detection, all of it is in a shell script you can read and change. If you want the shape of the wrapper itself, that is covered in the Ralph loop shell script.
Step 1: Install Ralph
Section titled “Step 1: Install Ralph”Run the installer in your project root. It writes the ralph.sh script and the .agent/ directory into the repo.
npx @pageai/ralph-loopThe installer asks for your dev server URL and port and your default agent. Claude is the sensible pick here, and it is already the fallback if you skip the choice. After install, make the script executable if it is not already:
chmod +x ralph.shRalph runs each agent inside a Docker Sandbox, so install and sign in to Docker Sandboxes first. The Docker Sandboxes docs cover the official setup. Under the hood Ralph drives the standalone sbx CLI.
Step 2: Create a PRD and task list
Section titled “Step 2: Create a PRD and task list”The loop runs a task list. It does not invent one for you. Turn your requirements into a PRD and a set of task specs with the prd-creator skill before you run anything.
Open Claude Code (or any agent) in plan mode and prompt it:
Use the prd-creator skill to help me create a PRD and task list for the below requirements.
An app is already set up with React, Tailwind CSS and TypeScript.
Requirements:- A SaaS product that helps users manage their finances.- Track income and expenses, create invoices, generate reports.- Use shadcn/ui for components and Supabase for the database.// etc.The skill writes .agent/prd/PRD.md, a short .agent/prd/SUMMARY.md, and per-task specs under .agent/tasks/, indexed by .agent/tasks.json. Review each task individually before you start. A clear task list with real acceptance criteria is the single biggest factor in whether an overnight run succeeds.
Step 3: Log in to Claude inside the sandbox
Section titled “Step 3: Log in to Claude inside the sandbox”Claude runs inside an isolated sandbox, not on your host, so it needs credentials in that environment. Authenticate once with the login action. Claude is the default, so you can drop the agent flag, but naming it keeps things explicit:
./ralph.sh --login --agent claudeRalph prints the login command for every supported agent, highlights the Claude one, and drops you into the sandbox shell. The command it runs to create the box looks like this:
sbx run --name ralph-claude-<project>-<hash8> claude .Inside the sandbox, authenticate Claude once and follow the prompt. If OAuth login does not persist, the fallback is an API key: Docker Sandboxes forward environment variables at creation time, so you can set ANTHROPIC_API_KEY in your shell before recreating the box. When Claude is not authenticated at the start of a run, Ralph detects the failure and tells you to run /login, so it never burns iterations on a box that can never make progress.
Each agent gets its own deterministic sandbox name, built from the agent slug, the project directory, and an 8-character hash of the absolute path:
ralph-claude-<project>-<hash8>Print the exact name for your project without starting a run:
./ralph.sh --print-name --agent claudeIf your tasks run a dev server you want to reach from the host, publish the configured port to the Claude sandbox. The port comes from the dev-server URL you set during install:
./ralph.sh --ports --agent claudePer-agent names keep state separate. Your Claude sandbox and, say, your Cursor sandbox never share credentials or installed tools, so you can run both against the same repo without one clobbering the other. If you later want the Cursor variant, that walkthrough is running a Ralph loop with the Cursor CLI.
Step 4: Run the loop with Claude
Section titled “Step 4: Run the loop with Claude”With a task list in place and Claude authenticated, start the loop. Because Claude is the default, the bare command already runs it:
# 10 iterations (the default), Claude by default./ralph.sh
# the same thing, stated explicitly./ralph.sh --agent claude
# 50 iterations for a longer unattended run./ralph.sh -n 50
# exactly one iteration, good for a smoke test./ralph.sh --onceThe iteration count is a safety budget, not a target. The loop stops the moment the work is done. The short form -a works too, and --max-iterations 5 is a synonym for -n 5.
Under the hood, Ralph builds a claude command and runs it inside the sandbox. For a first run that creates the box, the expansion is:
sbx run --name ralph-claude-<project>-<hash8> claude . -- --output-format stream-json --verbose -p "$PROMPT_CONTENT"The -p flag is Claude’s print (headless) mode, which still has full tool access including file writes and shell. The --output-format stream-json --verbose pair is Claude-only, and it is what feeds Ralph’s live preview.
Pass a model and flags after the separator
Section titled “Pass a model and flags after the separator”Everything to the right of Ralph’s own -- separator is forwarded straight to Claude, inserted between --verbose and -p. So this:
./ralph.sh --agent claude -- --permission-mode bypassPermissionsexpands to:
sbx run --name ralph-claude-<project>-<hash8> claude . -- --output-format stream-json --verbose --permission-mode bypassPermissions -p "$PROMPT_CONTENT"The rule to remember: everything left of -- configures Ralph (agent, iteration count, login), and everything right of -- configures Claude. If you want to pin a model, pass one your Anthropic account supports rather than guessing at an identifier. Do not hardcode a model name you are not sure exists.
YOLO mode is safe because the sandbox is the boundary
Section titled “YOLO mode is safe because the sandbox is the boundary”For a loop to run unattended it cannot pause for a human to approve each tool call. Claude Code has two ways to skip those prompts: --dangerously-skip-permissions, or the equivalent --permission-mode bypassPermissions. On your laptop that flag is genuinely reckless. Inside a Docker Sandbox it is the intended setup.
The sandbox is an isolated microVM with network denied by default. Claude can edit files and run shell commands freely inside it, but that blast radius never reaches your real machine. When the Docker Sandboxes login flow asks whether to bypass permissions, answering yes is exactly why you are running in a sandbox in the first place.
What happens each iteration
Section titled “What happens each iteration”Every pass is mechanical and identical. Claude reorients from disk, does one task, proves it works, and commits.
- Find the highest-priority incomplete task in
.agent/tasks.json. - Work the steps in
.agent/tasks/TASK-{ID}.json. - Run tests, linting, and type checking.
- Complete the task, take a screenshot, update the task status, and commit.
- Repeat until all tasks pass or the iteration cap is reached.
The key detail is that each iteration spawns a fresh claude -p with a clean context window. Claude does not carry an hours-long transcript from one task to the next. It reads the current state from disk, does one task, and exits. That is why the loop does not resume a prior Claude session: continuity is a liability here, not a feature.
flowchart TD
Start(["./ralph.sh -n 50 (Claude default)"]) --> Pick["Pick top task from .agent/tasks.json"]
Pick --> Spawn["sbx run claude . -- stream-json (fresh context)"]
Spawn --> Stream["Ralph parses stream-json: Thinking, Implementing, Testing"]
Stream --> Verify["Run tests, lint, type check, screenshot"]
Verify --> Commit["Commit and update task status"]
Commit --> Check{"Promise tag emitted?"}
Check -->|"none"| Pick
Check -->|"COMPLETE"| Done(["exit 0, all tasks done"])
Check -->|"BLOCKED or DECIDE"| Stop(["exit 2 or 3, wants a human"])
The filesystem and git history are the memory layer. Progress lives in .agent/tasks.json, .agent/logs/LOG.md, the per-task specs under .agent/tasks/, and the git log, not in a chat transcript. The prompt that drives every pass lives in .agent/PROMPT.md, alongside .agent/prd/SUMMARY.md, .agent/history/ per-iteration logs, and .agent/STEERING.md.
One rule keeps the whole thing reliable: one task per invocation. Claude completes exactly one task, commits, and stops. It never batches several tasks into a single iteration, which is what keeps each commit small and each diff reviewable.
How the loop knows when to stop
Section titled “How the loop knows when to stop”A loop that never ends is a money fire, so Ralph stops on an explicit signal rather than a guess. Claude emits a semantic promise tag in its final message and Ralph reads it out of the stream:
<promise>COMPLETE</promise>means every task is finished.<promise>BLOCKED:reason</promise>means the agent needs human help.<promise>DECIDE:question</promise>means it needs a decision you have to make.
Those map to exit codes, which is what makes the loop scriptable:
./ralph.sh -n 50echo $? # 0 COMPLETE, 1 MAX_ITERATIONS, 2 BLOCKED, 3 DECIDEExit code 1 is not a failure. It means the loop spent its iteration budget with work still pending, which is exactly what a safety cap is for. Because Claude’s output is fully parsed, Ralph can pick these tags and phase changes out of the live stream instead of grepping a final blob, which is the observability edge again.
Steer, debug, and review
Section titled “Steer, debug, and review”Three things keep a long Claude run under control.
Steer without stopping
Section titled “Steer without stopping”If you notice Claude heading the wrong way, you do not have to kill the loop. Edit .agent/STEERING.md while it runs. The agent checks that file on each pass and handles the critical work there before resuming the normal task order. That is steering, not stopping, and it keeps momentum while you correct course.
Get inside the sandbox
Section titled “Get inside the sandbox”The sandbox is a normal container you can poke at. List what exists, then open a shell in the Claude box:
sbx lssbx exec -it ralph-claude-<project>-<hash8> bashFrom there you can re-run a failing test by hand, check auth by running claude, or read .agent/logs/LOG.md and the per-iteration logs in .agent/history/. To reattach to an existing box, sbx run ralph-claude-<project>-<hash8> picks it back up without the create-only --name form.
When an install fails, the deny-by-default network is usually the cause. Allow the domain it needs:
sbx policy allow network ralph-claude-<project>-<hash8> registry.npmjs.orgThat is a feature, not a bug. Claude installs what the task needs without a path to exfiltrate your source.
Review the diff in the morning
Section titled “Review the diff in the morning”Because every task is committed separately, the morning review is a git review, not an archaeology dig:
git log --onelinegit diff main...HEADYou read the commits in order, eyeball the screenshots the agent captured, and accept or revert. A single bad task is one revert, not a tangled mess. That whole premise is covered in running an AI coding agent overnight.
The full Claude recipe, start to finish
Section titled “The full Claude recipe, start to finish”A real Claude loop is four commands:
# 1. install Ralph and scaffoldingnpx @pageai/ralph-loop
# 2. authenticate once (creates the sandbox, you log in inside it)./ralph.sh --login --agent claude
# 3. smoke test a single pass./ralph.sh --once
# 4. run the loop for real (Claude is the default agent)./ralph.sh -n 50Define your tasks in .agent/tasks.json, write a clear .agent/PROMPT.md, start the loop, and read the commits in the morning. The verification stack it assumes is Playwright for e2e, Vitest for units, TypeScript for types, ESLint for lint, and Prettier for format, so the mantra holds: if you didn’t test it, it doesn’t work.
Frequently asked questions
How do I run a Ralph loop with Claude Code?
Install Ralph with npx @pageai/ralph-loop, create a task list, log in with ./ralph.sh --login --agent claude, then run ./ralph.sh. Claude Code is the default agent, so the bare command already wraps claude in headless print mode inside a Docker sandbox, starts a fresh context each iteration, and repeats until every task in .agent/tasks.json is done or the iteration cap is reached.
Why is Claude Code the default agent in Ralph?
Claude is the only agent whose output Ralph fully parses. Ralph runs Claude with --output-format stream-json --verbose, and it reads that structured stream to detect the current phase (Thinking, Implementing, Testing) and show a live preview. Other agents still loop, commit, and log correctly, but they do not give the same real-time read on what the agent is doing.
What is the difference between Ralph and the official Claude Code loop?
Anthropic ships a plugin that uses a Stop Hook to re-inject the prompt and keep one Claude session going, so continuity is the point. Ralph is a hackable Bash script that spawns a brand new claude process each iteration inside a Docker sandbox with a clean context window, which kills context rot on long runs and lets you edit the wrapper yourself.
How do I pass flags or a model to Claude through Ralph?
Put them after the -- separator. Anything to the right of -- is forwarded to Claude, inserted between --verbose and -p, so ./ralph.sh --agent claude -- --permission-mode bypassPermissions runs Claude in print mode with that flag and the Ralph prompt. Pass a model your Anthropic account supports rather than guessing an identifier.
Is it safe to run Claude with --dangerously-skip-permissions?
It is reckless on your laptop and reasonable inside a sandbox. Ralph runs Claude in an isolated Docker Sandbox microVM with network denied by default, so --dangerously-skip-permissions (or --permission-mode bypassPermissions) lets the agent act without approval prompts while staying unable to touch your real files. The sandbox is the boundary, not the agent.