Claude
Skills
Sign in
Back

ralph-ci

Included with Lifetime
$97 forever

CI infrastructure for Ralph loops: GitHub Actions (matrix parallelism) and VPS (PM2). Adds deterministic success gates, zombie protection, kill switches, webhook alerts, cost controls, and circuit breakers. Downstream consumer of add-project — layers CI onto an existing project shell that already has .ralph/PLAN.md, .ralph/PROMPT.md, .ralphrc. Use when the user says "add CI to ralph", "ralph CI", "ralph github actions", "ralph VPS", "add CI loops", "ralph harness", or "CI for ralph".

Cloud & DevOps

What this skill does


# Ralph CI — Reliable Loop Infrastructure

Single skill with two modes (GitHub Actions vs VPS) controlled by `ralph.config.json`.
The harness scripts are identical across modes — only the process manager differs.

**This skill is a downstream consumer of `add-project`.** It layers CI infrastructure
onto an existing project that already has `.ralph/PLAN.md`, `.ralph/PROMPT.md`, `.ralphrc`, etc.

---

## What Gets Created

```
ralph.config.json                    # Central config (mode, iterations, budget, checks, webhook, sms)
harness/
  run.sh                             # Main loop runner (~250 lines, GitHub Actions UI)
  check.sh                           # Deterministic success gate (~100 lines)
  report.sh                          # Structured logging + webhook + SMS reporting (~200 lines)
harness/vps/
  bootstrap.sh                       # Hetzner/DO VPS provisioning script
  ecosystem.config.cjs               # PM2 process definitions
  cleanup.sh                         # Chrome zombie killer (cron)
.github/workflows/
  ralph-loop.yml                     # Main CI loop with matrix strategy
  ralph-watchdog.yml                 # Scheduled health check (cron)
  ralph-kill.yml                     # Emergency kill switch via workflow_dispatch
  ralph-pause.yml                    # One-click pause (graceful, state preserved)
  ralph-resume.yml                   # One-click resume (unpauses + triggers loop)
```

---

## Prerequisites

- Project with `.ralph/PLAN.md`, `.ralph/PROMPT.md`, `.ralphrc` (created by `add-project`)
- Git repository initialized with at least one commit
- `jq` available on the system (standard on GitHub Actions runners, installed by VPS bootstrap)
- **GitHub Actions secrets** (F24):
  - `ANTHROPIC_API_KEY` — must be a direct Anthropic API key (`sk-ant-*`), NOT a gateway/proxy key (`vck_*`, etc.). Claude Code CLI only accepts direct Anthropic keys.
  - `GH_PAT` — GitHub Personal Access Token with repo variable write access (for kill switch + pause/resume)
  - `N8N_SMS_KEY` — (optional) API key for SMS notifications via n8n webhook

---

## Phases

| Phase | What It Does |
|-------|-------------|
| 1. Configure | Generate `ralph.config.json` with project-specific values |
| 2. Harness | Create `harness/run.sh`, `check.sh`, `report.sh` |
| 3. GitHub Actions | Create `.github/workflows/ralph-loop.yml`, `ralph-watchdog.yml`, `ralph-kill.yml`, `ralph-pause.yml`, `ralph-resume.yml` |
| 4. VPS (optional) | Create `harness/vps/bootstrap.sh`, `ecosystem.config.cjs`, `cleanup.sh` |
| 5. Template Setup | Configure repo as GitHub template (optional), add badges to README |
| 6. Verification | Test check.sh standalone, test kill switch, validate YAML/JSON |

---

## Phase 1: Generate `ralph.config.json`

Create the central configuration file at the project root. This file supersedes `.ralphrc`
for CI — `.ralphrc` is still sourced for claude-specific settings (tools, session), but the
harness reads all loop config from this JSON file.

See [references/ralph-config-schema.md](references/ralph-config-schema.md) for full schema docs.

### Detection: Build vs Polish mode

Ask the user, or detect automatically:

- If `.ralph/PLAN.md` has PENDING steps with "Apply `X` skill" → **build** mode
- If `.ralph/PLAN.md` has only lint/typecheck/test steps → **polish** mode
- If no `.ralph/PLAN.md` → ask the user

### Default config

```json
{
  "mode": "build",
  "maxIterations": 40,
  "timeoutMinutes": 180,
  "iterationTimeoutMinutes": 30,
  "model": "claude-sonnet-4-6",
  "killSwitch": "RALPH_ENABLED",
  "budgetMaxUsd": 50,
  "concurrency": 3,
  "logRetention": 20,
  "checks": ["build", "typecheck", "lint", "test"],
  "webhook": {
    "url": "",
    "events": ["failure", "success", "stalled", "circuit_breaker"]
  },
  "circuitBreaker": {
    "noProgressThreshold": 3,
    "sameErrorThreshold": 5
  },
  "sms": {
    "enabled": false,
    "url": "https://n8n.mattwood.co/webhook/surge-sms",
    "apiKeyEnv": "N8N_SMS_KEY",
    "to": "",
    "events": [
      "phase_complete",
      "success",
      "failure",
      "circuit_breaker",
      "budget_exceeded",
      "max_iterations",
      "kill_switch",
      "paused",
      "resumed",
      "verify_failure"
    ]
  },
  "artifacts": ["logs", "diffs", "screenshots"],
  "vps": {
    "enabled": false,
    "processManager": "pm2",
    "chromeCleanup": true
  }
}
```

### Customization prompts

Ask the user about:

1. **Mode** — build or polish? (default: build)
2. **Max iterations** — how many loops before stopping? (default: 40)
3. **Budget** — max USD to spend? (default: $50)
4. **Concurrency** — how many parallel loops in CI? (default: 3)
5. **Webhook URL** — Slack/Discord webhook for alerts? (default: empty)
6. **VPS mode** — deploy to VPS? (default: false)
7. **Checks** — which checks to gate on? (default: all four)
8. **SMS notifications** — phone number for SMS alerts? (default: disabled)
   If provided, set `sms.enabled: true` and `sms.to` to the phone number.

Write the config:

```bash
cat > ralph.config.json << 'EOF'
{CONFIG_JSON}
EOF
```

Validate:

```bash
jq . ralph.config.json > /dev/null && echo "OK: valid JSON" || echo "FAIL: invalid JSON"
```

**Do not proceed until ralph.config.json validates.**

---

## Phase 2: Create Harness Scripts

Create `harness/` directory and the three core scripts.

```bash
mkdir -p harness
```

### 2a: `harness/run.sh` — Main Loop Runner

This is the core of Ralph CI. It orchestrates the loop, handles crashes, tracks budget,
and enforces all safety mechanisms.

**Key design choices:**

- Uses `set -uo pipefail` (no `-e`) so the loop survives individual command failures
- State files are scoped by `RALPH_LOOP_ID` so parallel loops don't collide
- Claude JSON output is captured raw (not piped through sanitize) to preserve cost parsing
- Uses `gtimeout` on macOS, `timeout` on Linux (auto-detected)
- All git commands use `--no-pager` to prevent hangs on VPS
- GitHub Actions integration: `::group::`/`::endgroup::` for collapsible iteration sections
- Step name from PLAN.md displayed in group labels and `$GITHUB_STEP_SUMMARY`
- Pause check (`RALPH_PAUSED`) for graceful stop/resume (F25)
- Phase completion detection with SMS notifications (F26)

```bash
cat > harness/run.sh << 'RUNSH'
#!/usr/bin/env bash
set -uo pipefail

# Ralph CI — Main Loop Runner
# Exit codes: 0=success, 2=circuit breaker, 3=kill switch, 4=budget exceeded, 5=timeout, 6=paused
#
# GitHub Actions integration:
#   - Each iteration wrapped in ::group:: / ::endgroup:: (collapsible sections)
#   - Step name from PLAN.md used as group label
#   - Progress table written to $GITHUB_STEP_SUMMARY after each iteration
#   - ::notice:: and ::error:: annotations for key events

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CONFIG="$PROJECT_DIR/ralph.config.json"
STATE_DIR="$PROJECT_DIR/.ralph"
LOG_DIR="$STATE_DIR/logs"

# ─── Detect GitHub Actions ──────────────────────────────────────────────────
IS_CI="${GITHUB_ACTIONS:-false}"

# GitHub Actions log grouping helpers
gh_group() {
  if [[ "$IS_CI" == "true" ]]; then
    echo "::group::$1"
  else
    echo "─── $1 ───"
  fi
}

gh_endgroup() {
  if [[ "$IS_CI" == "true" ]]; then
    echo "::endgroup::"
  fi
}

gh_notice() {
  if [[ "$IS_CI" == "true" ]]; then
    echo "::notice::$1"
  fi
}

gh_error() {
  if [[ "$IS_CI" == "true" ]]; then
    echo "::error::$1"
  fi
}

gh_warning() {
  if [[ "$IS_CI" == "true" ]]; then
    echo "::warning::$1"
  fi
}

# ─── Loop ID (for parallel execution isolation) ─────────────────────────────
LOOP_ID="${RALPH_LOOP_ID:-0}"
STATE_FILE="$STATE_DIR/.harness_state_${LOOP_ID}"

# ─── Load Config ─────────────────────────────────────────────────────────────
if [[ ! -f "$CONFIG" ]]; then
  echo "FATAL: $CONFIG not found" >&2
  exit 1
fi
if ! jq empty "$CONFIG" 2>/dev/null; then
  echo "FATAL: $CONFIG is not valid JSON" >&2
  exit 1
fi

config() { jq -r "$1 // empty" "$CONFIG"; }
Files: 3
Size: 118.1 KB
Complexity: 53/100
Category: Cloud & DevOps

Related in Cloud & DevOps