Skip to content

daniloaguiarbr/claudecode-ralph-loop-plus

claudecode-ralph-loop-plus

The Ralph Loop you already know — with the footguns filed down.

A hardened fork of Anthropic's ralph-loop plugin for Claude Code. Same while-true agent technique, same slash command surface, same stop hook — but portable across operating systems, safe from runaway iteration cost, and with regex-based completion signals when a literal phrase isn't flexible enough.

License: Apache 2.0 Claude Code plugin Fork of Ralph Wiggum Technique


Why this exists

The original ralph-loop plugin from Anthropic is elegant — a Stop hook that returns {"decision":"block", "reason":"<same prompt>"} in a while-true loop, so the agent iterates on the same task until a <promise>…</promise> tag matches or a hard iteration cap is reached.

In day-to-day use it has three rough edges:

  1. The args file lives in /tmp. That's fine on macOS and Linux but breaks on Windows, and two concurrent Claude Code sessions in the same project can race for the same path.
  2. No iteration cap by default. If you forget --max-iterations and your --completion-promise never fires, Ralph runs forever — and every iteration is a full Claude turn on your bill.
  3. Completion matching is a literal string compare. TASK COMPLETE and task complete don't match. ALL_DONE_v2 can't be caught by ALL_DONE_.*.

claudecode-ralph-loop-plus fixes those three without changing what Ralph fundamentally is.

What changed vs. upstream

Concern Upstream ralph-loop ralph-loop-plus
Args file location /tmp/ralph-loop-args.txt .claude/.ralph-args.txt (project-scoped)
Windows portability ❌ (no /tmp)
Default when no stopping condition Runs forever Auto-cap at 50 iterations
Regex completion matching --completion-regex <PATTERN> (ERE)
Case-insensitive promises --promise-case-insensitive
Non-destructive inspector command /ralph-status
Structured logging ✅ Optional, via RALPH_LOG_FILE (JSONL)
Flag parsing sed -E regex (fragile) awk match() literal (O(n), inherited fix)
Shell-injection in slash command Historically vulnerable Write-tool-first contract (inherited fix)

The dual-step Write → Bash(--prompt-file) initialization is inherited from the upstream fix that ships in Anthropic's plugin cache. See CHANGELOG.md for the full diff.

Quick start

1. Install via plugin cloning

git clone https://github.com/daniloaguiarbr/claudecode-ralph-loop-plus.git \
  ~/.claude/plugins/local/claudecode-ralph-loop-plus

Then enable it in your ~/.claude/settings.json:

{
  "enabledPlugins": {
    "claudecode-ralph-loop-plus@local": true
  }
}

Restart Claude Code. /ralph-loop, /ralph-status, /cancel-ralph, and /help should all appear.

2. Run a loop

/ralph-loop Refactor src/cache.ts so every public method has a doctest. Output <promise>DOCTESTS COMPLETE</promise> when every public method has one. --completion-promise 'DOCTESTS COMPLETE' --max-iterations 15

3. Inspect

/ralph-status

4. Stop

Either let the promise match, let the iteration cap expire, or:

/cancel-ralph

Full option reference

/ralph-loop [PROMPT...] [OPTIONS]

OPTIONS:
  --max-iterations <n>             Hard cap. 0 = unlimited.
  --completion-promise '<text>'    Literal phrase inside <promise>…</promise>.
  --completion-regex '<pattern>'   ERE regex matched against the text inside
                                   <promise>…</promise>. grep -E semantics.
  --promise-case-insensitive       Compare promises case-insensitively.
  --unlimited                      Explicitly disable the safety auto-cap.
  -h, --help                       Show usage.

Safety cap

If you set none of --max-iterations, --completion-promise, or --completion-regex, the loop is auto-capped at 50 iterations. That's deliberately low — it's a guardrail, not a ceiling. Override it:

export RALPH_DEFAULT_MAX_ITERATIONS=200   # Raise the default
/ralph-loop Do the thing --unlimited      # Disable on this one loop

Structured logging

Set RALPH_LOG_FILE before starting the loop:

export RALPH_LOG_FILE=.claude/ralph-loop.log.jsonl

Every loop_started and iteration_continue event is appended as one JSON object per line. Good for postmortems on long-running loops, or for counting how many iterations a task really took.

How it works

┌──────────────┐       writes       ┌─────────────────────────────┐
│ /ralph-loop  │ ───────────────▶  │ .claude/.ralph-args.txt     │
│ (slash cmd)  │                   │ (raw, byte-for-byte prompt) │
└──────────────┘                    └──────────┬──────────────────┘
       │                                        │
       │ invokes (via Bash tool)                │ reads
       ▼                                        ▼
┌──────────────────────────┐          ┌─────────────────────────────┐
│ scripts/                 │ ────────▶│ .claude/ralph-loop.local.md │
│   setup-ralph-loop.sh    │  writes  │ (YAML frontmatter + prompt) │
└──────────────────────────┘          └──────────┬──────────────────┘
                                                  │ read by stop hook
                                                  ▼
                   ┌──────────────────────────────────────────┐
                   │ hooks/stop-hook.sh                       │
                   │   — parses frontmatter                   │
                   │   — checks iteration, max, promise/regex │
                   │   — returns {"decision":"block",         │
                   │     "reason":<same prompt>} to continue  │
                   │   — or exit 0 to allow stop              │
                   └──────────────────────────────────────────┘

The key architectural idea is the same as upstream: the prompt never crosses a shell boundary. The slash command uses the Write tool (no shell interpretation) to drop the raw prompt on disk; the setup script reads it back with cat and parses flags with awk match() substring literals (not regex, not eval).

Completion signal — two modes

Ralph exits the loop when the text inside the first <promise>…</promise> tag in the agent's most recent text block satisfies one of these:

Literal mode (default when --completion-promise is set):

--completion-promise 'DOCTESTS COMPLETE'

Matches <promise>DOCTESTS COMPLETE</promise> exactly. Whitespace inside is normalized first (leading/trailing stripped, internal collapsed to one space). Add --promise-case-insensitive to relax case matching.

Regex mode (when --completion-regex is set):

--completion-regex 'FEATURE_[A-Z_]+_SHIPPED'

Matches <promise>FEATURE_AUTH_SHIPPED</promise> or <promise>FEATURE_SEARCH_SHIPPED</promise>. Uses grep -E semantics. If both --completion-promise and --completion-regex are set, literal match is tried first, then regex.

Relationship to the upstream plugin

This is a derivative work of Anthropic's ralph-loop plugin, licensed under Apache 2.0 (inherited). The NOTICE file credits the original authors. The underlying technique is by Geoffrey Huntley (ghuntley.com/ralph). Ralph Wiggum is a Simpsons character, chosen by Huntley for his deterministically bad persistence — which turns out to be a great model for iterative AI workflows.

If you want the original plugin instead, it ships in anthropics/claude-plugins-official under plugins/ralph-loop/.

Files

claudecode-ralph-loop-plus/
├── .claude-plugin/
│   └── plugin.json              # manifest
├── commands/
│   ├── ralph-loop.md            # /ralph-loop (Write + --prompt-file)
│   ├── ralph-status.md          # /ralph-status (new in Plus)
│   ├── cancel-ralph.md          # /cancel-ralph
│   └── help.md                  # /help
├── hooks/
│   ├── hooks.json               # registers Stop hook
│   └── stop-hook.sh             # block + feed prompt back
├── scripts/
│   └── setup-ralph-loop.sh      # flag parsing + state file creation
├── tests/
│   └── smoke.sh                 # bash -n + JSON validation + ShellCheck
├── .github/workflows/
│   └── validate.yml             # CI on push/PR
├── LICENSE                      # Apache 2.0 (inherited)
├── NOTICE                       # attribution
├── README.md                    # this file
├── CHANGELOG.md                 # Keep a Changelog
├── CONTRIBUTING.md
├── SECURITY.md
└── CODE_OF_CONDUCT.md

Troubleshooting

Slash commands don't appear after install. Restart Claude Code after editing ~/.claude/settings.json. Verify plugin.json is inside .claude-plugin/ (not at the repo root).

The loop never stops. You probably passed no stopping condition AND used --unlimited. Either remove --unlimited, add --max-iterations, or set a --completion-promise / --completion-regex that actually fires.

Windows line endings corrupt the state file. Add .claude/ralph-loop.local.md text eol=lf to your .gitattributes, or run dos2unix on it if you've edited it by hand.

I want to reset state. Delete .claude/ralph-loop.local.md and .claude/.ralph-args.txt. Both are gitignored by default via this repo's .gitignore — copy those lines into your project's .gitignore too.

Contributing

See CONTRIBUTING.md. TL;DR: open an issue first for anything non-trivial, run tests/smoke.sh before PR, keep the Ralph philosophy intact (simple, few moving parts, no magic).

Security

See SECURITY.md. The primary attack surface is shell metacharacter injection via the user prompt; the Write-first contract eliminates it in normal use. If you find a way past that, email rather than filing a public issue.

License

Apache 2.0 — same as upstream. Attribution preserved in NOTICE.

Português — resumo

Fork endurecido do plugin ralph-loop da Anthropic para Claude Code. Mesma técnica de loop agêntico (Ralph Wiggum), mesmo Stop hook, mesmos slash commands — porém:

  • Arquivo de argumentos em .claude/.ralph-args.txt em vez de /tmp → funciona no Windows, sem colisão entre sessões concorrentes.
  • Cap automático de 50 iterações quando você não define condição de parada → protege sua conta de loop infinito. Use --unlimited para desativar.
  • Matching de completion por regex via --completion-regex.
  • Matching case-insensitive via --promise-case-insensitive.
  • Comando /ralph-status para inspecionar o loop sem mexer no estado.
  • Logging estruturado opcional em JSON Lines via RALPH_LOG_FILE.

Uso e instalação idênticos ao original (veja "Quick start" acima). Licença Apache 2.0 herdada do upstream; atribuição em NOTICE.

About

Hardened fork of Anthropic's ralph-loop plugin for Claude Code: portable args file, auto safety cap, regex completion matching, /ralph-status inspector, structured logging — same Ralph technique, footguns filed down.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages