The Ralph Wiggum Technique: How to Ship Code While You Sleep
The most powerful AI development loop you have never heard of. One while loop, a great prompt, and you wake up to a working feature.
"I'm Helping!" — The AI That Never Quits
In The Simpsons, Ralph Wiggum is relentlessly positive. He doesn't understand defeat. He just keeps going with cheerful persistence, no matter what.
The Ralph Wiggum Technique is the AI development methodology named after him — and it's become one of the most powerful patterns in modern software development.
The core concept is disarmingly simple:
while :; do cat PROMPT.md | claude; done
That's it. A shell loop that feeds a prompt to Claude, over and over, until the task is complete. Claude works, gets blocked, tries again, refines, and iterates — continuously, without human intervention.
At a Y Combinator hackathon, a developer used this to ship 6 complete repositories overnight. A $50,000 contract was delivered and reviewed for $297 in API costs. A full programming language called CURSED was built over 3 months using this approach.
Let's learn how to use it.
Why "Iteration > Perfection" Is the Right Mindset
Traditional development assumes you need to get it right the first time. Design → Build → Test → Ship. The fear of failure at each stage slows everything down.
The Ralph approach flips this:
- Failures are data — each failed attempt tells Claude exactly what doesn't work
- Iteration is cheap — with AI, trying again costs tokens, not hours
- Operator skill matters — the quality of your prompt determines the quality of the outcome
- Persistence wins — the loop handles retry logic automatically
You don't need a perfect plan. You need a clear goal and a loop.
The Anatomy of a Ralph Loop
The Basic Loop
# Basic Ralph loop
while :; do cat PROMPT.md | claude; done
# With Claude Code's --print flag (non-interactive)
while :; do claude --print "$(cat PROMPT.md)"; done
# With safety net (max 20 iterations)
for i in $(seq 1 20); do
result=$(claude --print "$(cat PROMPT.md)")
if echo "$result" | grep -q "COMPLETE"; then
echo "Done in $i iterations!"
break
fi
done
The Completion Signal
The loop knows when to stop because Claude outputs a specific signal:
<!-- In your PROMPT.md -->
When all tasks are complete and tests are passing, output:
<promise>COMPLETE</promise>
The loop script checks for this string and exits. Without this, the loop runs forever — which is either a superpower or a bug, depending on your situation.
Writing Great Prompts: The Make-or-Break Skill
The difference between a Ralph loop that ships clean code and one that spins forever is prompt quality. Here's how to write prompts that work:
The Four-Part Prompt Structure
## Context
[Describe the project, current state, relevant files]
## Task
[Exactly what needs to be built or fixed]
## Success Criteria
[Measurable, unambiguous requirements]
## Completion Signal
Output <promise>COMPLETE</promise> only when ALL criteria are met.
Real Example: Rate Limiter Feature
## Context
This is an Express.js API for a SaaS product.
Relevant files:
- src/middleware/ (existing middleware)
- src/routes/api/ (API routes to protect)
- package.json (currently uses Redis for sessions)
## Task
Implement rate limiting middleware using express-rate-limit + Redis.
Requirements:
- 100 requests per 15 minutes per IP for public endpoints
- 1000 requests per 15 minutes per authenticated user
- Custom error response: { error: "Rate limit exceeded", retryAfter: X }
- Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
- Apply to all /api/v1/ routes
## Success Criteria
- [ ] Middleware implemented and exports correctly
- [ ] Applied to all API routes (verify by reading route files)
- [ ] Unit tests written and passing (run: npm test)
- [ ] Integration test: verify 101st request returns 429
- [ ] TypeScript compiles with no errors: npm run type-check
- [ ] README updated with rate limit docs
## If You Get Stuck
After 3 failed test runs, add console.log statements to trace the issue.
After 5 failed runs, simplify the implementation and try a different approach.
Output <promise>COMPLETE</promise> only when all tests pass and TypeScript compiles.
Using Ralph with Claude Code
Claude Code is the CLI tool that makes Ralph truly powerful. Unlike browser Claude, Claude Code can:
- Read and write files directly
- Run shell commands
- Execute tests and see failures
- Commit to git
- Open PRs on GitHub
Setup
npm install -g @anthropic-ai/claude-code
claude login
# Create your prompt file
cat > PROMPT.md << 'EOF'
Build a REST API for a todo app using Express + TypeScript + PostgreSQL.
Endpoints needed:
- GET /todos (list, with pagination)
- POST /todos (create)
- PUT /todos/:id (update)
- DELETE /todos/:id (soft delete)
Tests: Jest integration tests for each endpoint.
Run tests with: npm test
Output <promise>COMPLETE</promise> when all tests pass.
EOF
# Start the Ralph loop
while :; do claude --print "$(cat PROMPT.md)"; sleep 2; done
With Max Iterations Safety Net
#!/bin/bash
# ralph.sh — Production-grade Ralph runner
MAX_ITERATIONS=${1:-30}
COMPLETION_PHRASE=${2:-"COMPLETE"}
PROMPT_FILE=${3:-"PROMPT.md"}
echo "🚀 Starting Ralph loop (max: $MAX_ITERATIONS iterations)"
echo "📝 Watching for: $COMPLETION_PHRASE"
for i in $(seq 1 "$MAX_ITERATIONS"); do
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🔄 Iteration $i / $MAX_ITERATIONS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
OUTPUT=$(claude --print "$(cat "$PROMPT_FILE")" 2>&1)
echo "$OUTPUT"
if echo "$OUTPUT" | grep -q "$COMPLETION_PHRASE"; then
echo ""
echo "✅ DONE! Completed in $i iteration(s)"
exit 0
fi
sleep 3
done
echo "⚠️ Max iterations reached. Review output above."
exit 1
Usage:
chmod +x ralph.sh
./ralph.sh 20 "COMPLETE" PROMPT.md
The Stop Hook: Claude's Inner Guardrail
The real magic in the original Ralph technique is the stop hook — a Claude hook that prevents Claude from exiting until the task is done.
// ~/.claude/settings.json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/check-completion.sh"
}
]
}
]
}
}
#!/bin/bash
# ~/.claude/hooks/check-completion.sh
# Return non-zero to prevent Claude from stopping
INPUT=$(cat)
TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript // ""')
# Check if completion signal was given
if echo "$TRANSCRIPT" | grep -q "<promise>COMPLETE</promise>"; then
echo '{"decision": "allow"}' # Let Claude stop
exit 0
fi
# Check if tests pass
npm test --silent 2>&1
if [ $? -ne 0 ]; then
echo '{"decision": "block", "reason": "Tests are still failing. Fix them before stopping."}'
exit 0
fi
echo '{"decision": "allow"}'
Now Claude literally cannot stop until the tests pass. This is the purest form of the Ralph technique.
Real-World Project Templates
Template 1: Ship a Full-Stack Feature
# PROMPT.md — Full Stack Feature
## Goal
Build [FEATURE_NAME] end-to-end.
## Backend (complete first)
- [ ] Database migration: [describe schema changes]
- [ ] API endpoint: [describe endpoint]
- [ ] Input validation using Zod
- [ ] Error handling for all edge cases
- [ ] Unit tests passing
## Frontend (after backend)
- [ ] UI component: [describe UI]
- [ ] API integration with error states
- [ ] Loading and empty states handled
- [ ] Mobile responsive
## Verification
Run: npm run test:all
Run: npm run build
No TypeScript errors.
Output <promise>COMPLETE</promise> when EVERYTHING above is checked off.
Template 2: Overnight Bug Bash
# PROMPT.md — Bug Fix Session
## Bugs to Fix (priority order)
1. [Bug description + reproduction steps + expected behavior]
2. [Bug description + reproduction steps + expected behavior]
3. [Bug description + reproduction steps + expected behavior]
## For Each Bug
1. Write a failing test that reproduces it
2. Fix the bug
3. Verify the test now passes
4. Make sure no OTHER tests broke
## Done When
All 3 bugs have:
- A regression test (that was failing before, passes now)
- A fix committed with message: "fix: [bug description]"
- No new test failures
Output <promise>COMPLETE</promise> when done.
When Ralph Works Best vs. When to Stop
Use Ralph For:
- Greenfield features — building from a clear spec
- Bug bashing — fixing a list of issues with clear reproduction steps
- Test coverage — writing tests to reach a coverage threshold
- Refactoring — with clear "all tests must stay green" constraints
- Documentation — generating docs from code with a defined format
Don't Use Ralph For:
- Design decisions — "what architecture should we use?" needs human judgment
- Production incidents — targeted, human-directed debugging is safer
- Ambiguous requirements — "make it better" will loop forever
- Database migrations on live data — too risky without human review
- Security-sensitive changes — need human eyes before deploying
The Compound Effect: Multi-Phase Ralph
For large projects, chain Ralph loops:
#!/bin/bash
# multi-phase.sh
phases=("database-schema" "api-layer" "frontend" "tests" "documentation")
for phase in "${phases[@]}"; do
echo "🎯 Starting phase: $phase"
./ralph.sh 25 "PHASE_DONE" "prompts/${phase}.md"
if [ $? -ne 0 ]; then
echo "❌ Phase $phase failed. Review and retry."
exit 1
fi
git add -A && git commit -m "feat: complete $phase phase"
echo "✅ Phase $phase complete. Moving to next..."
done
echo "🎉 All phases complete!"
The Philosophical Point
The Ralph Wiggum Technique isn't just a dev trick. It represents a fundamental shift in how we think about AI collaboration:
Let go of first-try perfectionism. Embrace the loop.
Software development has always been iterative — we just pretended otherwise. We write a spec, realize we missed things, rewrite. We build a feature, QA finds bugs, we fix them. We deploy, users complain, we patch.
Ralph just makes the iteration loop faster and removes the human from the middle of it. You set the destination. Claude navigates. The loop handles the detours.
And when you wake up in the morning, the feature is done.