Skip to content

mdelapenya/claude-ralph-github-action

Repository files navigation

Claude Ralph GitHub Action

A GitHub Action that implements the Ralph loop pattern: iterative work/review/ship cycles on GitHub issues using Claude Code CLI.

When you label an issue, Ralph:

  1. Creates a branch and reads the issue description
  2. Runs a worker agent that writes code to address the issue
  3. Runs a reviewer agent that evaluates the changes
  4. If the reviewer says REVISE, loops back to step 2 with feedback
  5. If the reviewer says SHIP and security_gate_enabled is true (default):
    • Runs a security gate agent that audits the diff for vulnerabilities
    • If the gate says FAIL, loops back to step 2 with security findings as feedback
    • If the gate says PASS, pushes the branch and opens a PR
  6. If security_gate_enabled is false, ships immediately after the reviewer approves

Ralph Loop

Quick Start

  1. Add ANTHROPIC_API_KEY as a repository secret

  2. Create .github/workflows/ralph.yml:

name: Ralph Loop

on:
  issues:
    types: [labeled, edited]
  issue_comment:
    types: [created]
  pull_request:
    types: [labeled]

permissions:
  contents: write
  pull-requests: write
  issues: write

jobs:
  reject-pr:
    if: github.event_name == 'pull_request' && github.event.label.name == 'ralph'
    runs-on: ubuntu-latest
    steps:
      - name: Comment on PR
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          existing="$(gh pr view "${{ github.event.pull_request.number }}" \
            --repo "${{ github.repository }}" \
            --json comments --jq '.comments[].body' \
            | grep -c '🤖 \*\*Ralph\*\*' || true)"
          if [[ "${existing}" -eq 0 ]]; then
            gh pr comment "${{ github.event.pull_request.number }}" \
              --repo "${{ github.repository }}" \
              --body "🤖 **Ralph** can only work on issues, not pull requests. Please create an issue and label it with \`ralph\` instead."
          fi

  ralph:
    if: >-
      (github.event_name == 'issues' && github.event.action == 'labeled' && github.event.label.name == 'ralph') ||
      (github.event_name == 'issues' && github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'ralph')) ||
      (github.event_name == 'issue_comment' && github.event.action == 'created' && contains(github.event.issue.labels.*.name, 'ralph') && github.event.comment.user.type != 'Bot' && !contains(github.event.comment.body, '<!-- ralph-comment-') && !github.event.issue.pull_request) ||
      (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request && (github.event.comment.body == '/ralph-review' || startsWith(github.event.comment.body, '/ralph-review ')) && github.event.comment.user.type != 'Bot')
    runs-on: ubuntu-latest
    timeout-minutes: 60
    concurrency:
      group: ralph-${{ github.event.issue.number || github.event.pull_request.number }}
      cancel-in-progress: false
    steps:
      - uses: actions/checkout@v4

      # Pin to an immutable SHA for supply chain security (SLSA/SSDF compliance).
      # To find the SHA: gh release view v1 --repo mdelapenya/claude-ralph-github-action --json targetCommitish
      # Example: mdelapenya/claude-ralph-github-action@abc1234def5678  # v1
      - uses: mdelapenya/claude-ralph-github-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
  1. Create a ralph label in your repository
  2. Label any issue with ralph to trigger the loop

Inputs

Input Required Default Description
anthropic_api_key Yes Anthropic API key for Claude CLI
github_token No ${{ github.token }} GitHub token for PR and issue operations
worker_model No sonnet Claude model for the worker phase
reviewer_model No sonnet Claude model for the review phase
max_iterations No 5 Maximum number of work/review cycles
max_turns_worker No (Claude CLI default) Maximum agentic turns per worker invocation
max_turns_reviewer No (Claude CLI default) Maximum agentic turns per reviewer invocation
trigger_label No ralph Issue label that triggers the loop
base_branch No Branch to create the PR against (auto-detected from repository default branch if not specified)
worker_allowed_tools No Bash,Read,Write,Edit,Glob,Grep,Task,WebFetch,WebSearch Comma-separated tools the worker can use
reviewer_tools No Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch,Task Comma-separated tools the reviewer can use
merge_strategy No pr Merge strategy: pr (create a pull request) or squash-merge (squash and push directly to default branch)
default_branch No Default branch to merge into when using squash-merge strategy (auto-detected from repo if not specified)
worker_tone No Personality/tone for the worker agent (e.g., "pirate", "formal", "enthusiastic"). If set, the worker will respond with this personality
reviewer_tone No Personality/tone for the reviewer agent (e.g., "pirate", "formal", "enthusiastic"). If set, the reviewer will respond with this personality
commit_author_name No claude-ralph[bot] Git author name for commits
commit_author_email No claude-ralph[bot]@users.noreply.github.com Git author email for commits
ralph_review_command No /ralph-review Slash command to trigger a re-review run on a Ralph-created PR (e.g., /ralph-review focus on tests)
security_gate_enabled No true Enable the security gate. Set to false to skip the security audit before shipping (not recommended for production)
security_gate_model No sonnet Claude model for the security gate phase
max_turns_security_gate No (Claude CLI default) Maximum agentic turns per security gate invocation
security_gate_tools No Bash,Read,Write,Glob,Grep Comma-separated tools the security gate can use
security_gate_tone No Personality/tone for the security gate agent (e.g., "Agent Smith", "HAL 9000")

Outputs

Output Description
pr_url URL of the created/updated pull request, or merge commit SHA when using squash-merge
iterations Number of work/review iterations completed
final_status SHIPPED, MAX_ITERATIONS, or ERROR

How It Works

Ralph creates a .ralph/ directory in the working tree (never committed to the branch) to pass state between agents:

  • task.md — The issue title and body
  • pr-info.txt — Repo, branch, issue title, and existing PR number (if any)
  • work-summary.txt — Worker's summary of changes made
  • review-result.txtSHIP or REVISE
  • review-feedback.txt — Reviewer's feedback for the next iteration
  • pr-title.txt — PR title in conventional commits format (set by reviewer)
  • iteration.txt — Current iteration number
  • issue-number.txt — GitHub issue number (set once at startup)
  • security-result.txtPASS or FAIL (written by security gate; defaults to FAIL if missing)
  • security-feedback.txt — Security gate findings for the worker (present only on FAIL)
  • final-status.txt — Final loop outcome: SHIPPED, MAX_ITERATIONS, or ERROR
  • push-error.txt — Last push failure details (cleared on success)
  • audit.log — Append-only structured log of all phase transitions
  • *.sha256 — SHA-256 checksum sidecars for tamper-detection on review-result.txt and security-result.txt

The worker agent merges the base branch (resolving any conflicts), implements the task, and commits changes directly. The reviewer agent evaluates the changes, runs tests and linters independently, and decides whether to SHIP or REVISE. When the reviewer decides SHIP, the security gate performs an independent read-only audit of the branch diff before the loop exits. If the worker makes no commits in an iteration, the loop continues to the next iteration with feedback instead of aborting.

Security Gate

The security gate is a separate Claude agent that runs after every SHIP decision. It audits the branch diff for:

  • Secrets: hardcoded API keys, tokens, passwords (including in git history)
  • Injection: command injection, SQL injection, path traversal, XSS, SSRF
  • Auth: missing authentication, broken access control, insecure session management
  • Cryptography: broken algorithms (MD5, SHA-1, DES), disabled TLS verification
  • Shell safety: unquoted variables, eval with external input, insecure temp files
  • Dependencies: unpinned versions, known-vulnerable packages
  • Information disclosure: stack traces, debug endpoints, sensitive data in logs
  • Privilege: world-writable files, unnecessary root operations

Any finding of MEDIUM severity or higher writes FAIL and forces another worker iteration with the findings as feedback. The gate defaults to FAIL if it crashes or produces no output (fail-safe). It also detects prompt injection attempts in any file it reads and treats them as CRITICAL findings.

Disable with security_gate_enabled: false. See SECURITY.md for the full threat model.

PR titles

PR titles follow conventional commits format. The reviewer agent infers the type from the changes and sets the title:

feat: add input validation to entrypoint
fix: resolve git safe directory error
chore: update dependencies
refactor: simplify state management

Supported types: feat, fix, chore, refactor, docs, test, style, perf, build, ci, revert.

If a PR already exists (on re-runs), the reviewer updates the title directly via gh pr edit. On the first run, the reviewer writes the title to .ralph/pr-title.txt and the orchestration uses it when creating the PR.

Triggers and Re-runs

Ralph triggers in four ways:

  • Label added: When the ralph label is added to an issue (first run or re-trigger by removing and re-adding the label).
  • Issue edited: When an issue that already has the ralph label is edited (title or body changed). This lets you refine requirements and have Ralph re-process the updated task.
  • Comment added: When a new comment is posted on an issue that has the ralph label. This enables a conversational workflow where you can give Ralph follow-up instructions via comments. Ralph's own comments (identified by <!-- ralph-comment-* --> markers) do not retrigger the workflow. This works with the standard GITHUB_TOKEN — no PAT is required.
  • /ralph-review on a Ralph PR: When you post /ralph-review as a comment on a Ralph-created pull request, Ralph re-runs the loop incorporating all PR review feedback. See PR Review Workflow below.

In all cases, Ralph detects the existing branch if one exists, checks it out, and continues from where it left off. The worker re-reads the task from the issue (which may have changed) and the branch's commit history to understand what was already done. New commits are added on top — Ralph never force-pushes.

Merge Strategies

Ralph supports two merge strategies:

pr (default)

Creates or updates a pull request. The PR remains open for human review and must be manually merged. This is the recommended approach for most use cases.

squash-merge

When the reviewer approves (SHIP), Ralph squashes all commits into a single commit and pushes directly to the default branch. The issue is automatically closed. The commit message uses the PR title set by the reviewer (in conventional commits format).

Example workflow configuration for squash-merge:

# Pin to an immutable SHA for supply chain security (SLSA/SSDF compliance).
# To find the SHA: gh release view v1 --repo mdelapenya/claude-ralph-github-action --json targetCommitish
# Example: mdelapenya/claude-ralph-github-action@abc1234def5678  # v1
- uses: mdelapenya/claude-ralph-github-action@v1
  with:
    anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
    merge_strategy: squash-merge

Note: With squash-merge, if the reviewer requests revisions, max iterations is reached, or the squash-merge fails for any reason, Ralph falls back to creating a PR for human review.

Security consideration: The squash-merge strategy pushes directly to the default branch, bypassing pull request reviews and any branch protection rules. Only use this for low-risk, well-scoped tasks where you trust the automated review process.

Agent Tone Configuration

You can configure the personality and tone of both the worker and reviewer agents. This allows agents to communicate in a specific style while still performing their tasks correctly.

Example workflow configuration:

# Pin to an immutable SHA for supply chain security (SLSA/SSDF compliance).
# To find the SHA: gh release view v1 --repo mdelapenya/claude-ralph-github-action --json targetCommitish
# Example: mdelapenya/claude-ralph-github-action@abc1234def5678  # v1
- uses: mdelapenya/claude-ralph-github-action@v1
  with:
    anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
    worker_tone: "pirate"
    reviewer_tone: "professional and concise"

When tone is configured, the agent will respond with that personality throughout its work. For example, a worker with worker_tone: "pirate" might write commit messages and summaries in pirate speak, while still producing correct, functional code.

Use cases:

  • Fun team projects where personality adds engagement
  • Formal corporate environments requiring professional tone
  • Educational contexts where enthusiastic encouragement is helpful

The tone instruction is appended to the system prompt, so agents maintain their core capabilities while adopting the requested personality.

Permissions

Ralph requires the following GitHub Actions permissions:

  • contents: write — Required to create branches, commit changes, and push code
  • pull-requests: write — Required to create and update pull requests
  • issues: write — Required to comment on issues

Modifying workflow files

By default, the GITHUB_TOKEN cannot modify workflow files in .github/workflows/. This is a GitHub security restriction that cannot be overridden via the permissions block (workflows is not a valid permission scope).

If you need Ralph to edit workflow files, use a Personal Access Token (PAT) with the workflow scope:

  1. Create a fine-grained or classic PAT with the workflow scope
  2. Add it as a repository secret (e.g., GH_PAT_TOKEN)
  3. Pass it to the action:
# Pin to an immutable SHA for supply chain security (SLSA/SSDF compliance).
# To find the SHA: gh release view v1 --repo mdelapenya/claude-ralph-github-action --json targetCommitish
# Example: mdelapenya/claude-ralph-github-action@abc1234def5678  # v1
- uses: mdelapenya/claude-ralph-github-action@v1
  with:
    anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
    github_token: ${{ secrets.GH_PAT_TOKEN }}

Without a PAT: Ralph can modify all files except those in .github/workflows/. Push attempts that include workflow changes will fail.

With a PAT (workflow scope): Ralph can modify any file including workflows. Use this when tasks specifically require workflow changes.

PR Review Workflow

Once Ralph opens a pull request, you can close the feedback loop without leaving GitHub:

  1. Review the PR normally — leave inline comments, an overall review, or both
  2. Post /ralph-review as a comment on the PR to trigger another Ralph run
  3. Ralph re-runs the full loop, incorporating all review feedback into the task context:
    • Inline code comments (file, line, and body)
    • Overall review bodies and their state (e.g., CHANGES_REQUESTED)
  4. Ralph commits the fixes and pushes to the same branch, updating the PR automatically

Passing reviewer instructions: You can add extra guidance after the command. Everything after /ralph-review (separated by a space or newline) is passed to the worker as "Reviewer Instructions":

/ralph-review focus on error handling and add unit tests for the new functions

Customizing the slash command: If you want to use a different command (e.g., /review), change the literal string in both the job if condition and the ralph_review_command action input. The env context is not available in job-level if conditions (a GitHub Actions limitation), so the command string must be hardcoded there:

# Job-level if: hardcode the literal string
if: >-
  ...
  (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request && (github.event.comment.body == '/ralph-review' || startsWith(github.event.comment.body, '/ralph-review ')) && github.event.comment.user.type != 'Bot')

# Action step: pass as input so entrypoint.sh knows the command
      # Pin to an immutable SHA for supply chain security (SLSA/SSDF compliance).
      # To find the SHA: gh release view v1 --repo mdelapenya/claude-ralph-github-action --json targetCommitish
      # Example: mdelapenya/claude-ralph-github-action@abc1234def5678  # v1
      - uses: mdelapenya/claude-ralph-github-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          ralph_review_command: '/ralph-review'

Note: Only non-bot users can trigger /ralph-review. Ralph's own comments are never used as triggers.

Pull requests

Ralph only works on issues. If the ralph label is added to a pull request, Ralph will post a comment explaining it can only work on issues, and exit without making changes.

Multi-Agent Support

Ralph supports splitting complex tasks into multiple subtasks that can be processed in parallel. If the worker agent determines that a task is too complex or would benefit from parallel execution, it can create multiple GitHub issues labeled with ralph. Each issue will be processed by a separate Ralph action instance concurrently.

How it works:

  1. The worker agent assesses the task complexity during implementation
  2. If appropriate, it creates temporary files describing each subtask (title and body)
  3. It invokes the /scripts/create-subtask-issues.sh helper script
  4. The script creates GitHub issues with the ralph label for each subtask
  5. GitHub Actions spawns separate Ralph workflows to process each issue in parallel

When to split tasks:

  • Multiple independent features or components
  • Different areas of the codebase that don't depend on each other
  • Large scope that would be clearer as separate, focused tasks

Example:

If an issue requests "Add user authentication with login, registration, and password reset," the worker might split it into:

  • Issue 1: "Add login API endpoint with JWT authentication"
  • Issue 2: "Add registration API endpoint with validation"
  • Issue 3: "Add password reset flow with email verification"

Each subtask is then processed independently and can be merged separately, allowing for faster parallel execution and clearer code reviews.

Supply Chain Security

TL;DR: Always pin to a full commit SHA. Never use @main, @latest, or a mutable tag like @v1 in production workflows.

This action runs with access to your ANTHROPIC_API_KEY and github_token. A compromised maintainer account or Docker Hub credential could replace the code or image under a mutable reference without any visible change to your workflow file.

Pin to an immutable SHA:

# UNSAFE — tag can be silently moved to a different commit
- uses: mdelapenya/claude-ralph-github-action@v1

# SAFE — SHA cannot be retroactively changed
- uses: mdelapenya/claude-ralph-github-action@f8a8ef2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f  # v1.2.3

Get the SHA for the latest release:

gh release view --repo mdelapenya/claude-ralph-github-action --json tagName,targetCommitish

Use Dependabot to get automatic SHA-update PRs when a new release is published — add package-ecosystem: "github-actions" to your .github/dependabot.yml.

See SECURITY.md for the full threat model, Dependabot setup, and recommended permission scoping.

Testing

Ralph includes unit and integration tests that validate the scripts without calling the Claude API.

Running Tests

# Run all unit + integration tests (no API key needed, completes in seconds)
bash test/run-all-tests.sh

# Lint all shell scripts
shellcheck --severity=warning entrypoint.sh scripts/*.sh test/**/*.sh test/*.sh

Test Suite

Category Files What it tests
Unit tests test/unit/test-state.sh state.sh read/write helpers, including security result/feedback helpers
test/unit/test-output-format.sh Action output format validation (pr_url, iterations, final_status)
test/unit/test-git-config.sh Git author config is set correctly from INPUT_COMMIT_AUTHOR_*
test/unit/test-workflow-patch.sh workflow-patch.sh helper functions generate correct patch comments
Integration tests test/integration/test-shipped-flow.sh Full SHIP path: worker commits, reviewer approves, gate passes, PR URL written
test/integration/test-max-iterations.sh REVISE loop exhausts INPUT_MAX_ITERATIONS, exits with code 2
test/integration/test-error-handling.sh Worker failure triggers ERROR exit with code 1
test/integration/test-squash-merge.sh Squash-merge strategy writes merge-commit.txt instead of PR URL
test/integration/test-pr-review-comment.sh /ralph-review slash command runs loop with PR review context in task.md
test/integration/test-pr-review-submitted.sh pull_request_review event triggers loop with review body as context
test/integration/test-reaction.sh Issue reactions trigger the Ralph loop correctly
test/integration/test-push-error-recovery.sh Push failure is recorded and fed back to the worker as review feedback
test/integration/test-rerun-already-pushed.sh Re-run on a branch already pushed to remote continues without force-pushing
test/integration/test-workflow-push-fallback.sh Workflow file push failures generate a patch comment and retry without .github/workflows/
test/integration/test-security-gate-pass.sh Reviewer SHIPs, security gate PASSes → SHIPPED; audit log records gate phases
test/integration/test-security-gate-fail-then-pass.sh Gate FAILs on first SHIP, forces REVISE with findings; gate PASSes on iteration 2 → SHIPPED
test/integration/test-security-gate-disabled.sh security_gate_enabled: false skips gate, ships normally, no security-result.txt written

How Integration Tests Work

Integration tests exercise the real ralph-loop.sh -> worker.sh -> reviewer.sh pipeline with mock binaries:

  • Mock claude (test/helpers/mocks.sh): A standalone script placed on PATH that inspects the prompt to determine worker vs reviewer vs security gate mode. The worker mock creates a file and commits it. The reviewer mock writes SHIP or REVISE to state files. The security gate mock writes PASS or FAIL. Behavior is configurable via env vars:
    • MOCK_REVIEW_DECISIONSHIP (default) or REVISE
    • MOCK_WORKER_FAIL — Set to true to simulate worker failure
    • MOCK_MERGE_STRATEGY — Set to squash-merge for squash-merge tests
    • MOCK_SECURITY_GATE_DECISIONPASS (default), FAIL, or FAIL_ONCE (fails the first invocation, passes subsequent ones)
  • Mock gh: Returns mock PR URLs and no-ops for issue comments
  • Isolated workspaces (test/helpers/setup.sh): Each test runs in a temp directory with its own git repo and bare remote, so git push works without network access

CI Integration

To run tests in your CI workflow, copy the job definitions from test/ci-example.yml into your .github/workflows/ci.yml. The example includes separate jobs for unit and integration tests.

Local Testing

# Requires Docker and an Anthropic API key
ANTHROPIC_API_KEY=sk-... ./test/run-local.sh

# With verbose Claude CLI output (⚠️ security: logs include full tool call payloads and file contents;
# do not enable in workflows where runner logs are publicly visible)
RALPH_VERBOSE=true ANTHROPIC_API_KEY=sk-... ./test/run-local.sh

# Override defaults
INPUT_WORKER_MODEL=haiku INPUT_MAX_ITERATIONS=1 ANTHROPIC_API_KEY=sk-... ./test/run-local.sh

License

MIT

About

Implements the Ralph loop pattern using Claude Code CLI - iterative work/review/ship cycles on GitHub issues

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors