Add ralph-loop skill for recurring prompt execution#1
Conversation
Adds the ralph-loop skill with SKILL.md, loop.sh script, and zip package. The skill runs any prompt or slash command on a recurring interval with automatic retry, failure recovery, and stop-file/signal support. https://claude.ai/code/session_01GvwWVvFRrmzeYS8qJovgsZ
Reviewer's GuideAdds a new ralph-loop skill that runs a provided prompt or slash command on a recurring interval with retry, backoff, stop controls, and JSON state output, along with documentation on usage and behavior. Sequence diagram for ralph-loop recurring execution and stop controlssequenceDiagram
actor User
participant RalphLoop as loop_sh
participant Agent as PromptExecutor
participant OS as OperatingSystem
User->>RalphLoop: run loop_sh [interval] "prompt"
RalphLoop->>RalphLoop: parse_interval_or_default()
RalphLoop->>OS: set_trap_EXIT_cleanup()
RalphLoop->>OS: remove_stop_file()
loop Ralph_iteration
RalphLoop->>OS: get_current_time()
RalphLoop->>Agent: echo PROMPT (stdout to agent)
Agent-->>RalphLoop: RUN_OUTPUT, RUN_STATUS
RalphLoop->>RalphLoop: check RUN_OUTPUT for STOP_LOOP
alt STOP_LOOP_found
RalphLoop-->>User: final_JSON_state status=stopped
RalphLoop->>OS: cleanup_stop_file()
RalphLoop-->>User: stderr "Stop signal detected"
RalphLoop-->>User: exit 0
else no_stop_signal
alt RUN_STATUS_nonzero
RalphLoop->>RalphLoop: increment CONSECUTIVE_FAILURES
alt CONSECUTIVE_FAILURES >= MAX_RETRIES
RalphLoop->>RalphLoop: BACKOFF = INTERVAL_SECONDS * 2
RalphLoop-->>User: stderr "Max retries reached. Backing off"
RalphLoop->>OS: sleep BACKOFF
RalphLoop->>RalphLoop: reset CONSECUTIVE_FAILURES
else below_max_retries
RalphLoop-->>User: stderr "Run failed (attempt n/3)"
end
else RUN_STATUS_zero
RalphLoop->>RalphLoop: reset CONSECUTIVE_FAILURES
RalphLoop-->>User: forward RUN_OUTPUT
end
RalphLoop->>OS: compute_NEXT_time()
RalphLoop-->>User: JSON_state status=running
RalphLoop->>OS: check_stop_file()
alt stop_file_exists
RalphLoop-->>User: stderr "Stop file detected"
RalphLoop->>OS: cleanup_stop_file()
RalphLoop-->>User: exit 0
else no_stop_file
RalphLoop-->>User: stderr "Sleeping INTERVAL_SECONDS"
RalphLoop->>OS: sleep INTERVAL_SECONDS
RalphLoop->>OS: check_stop_file_again()
alt stop_file_exists_after_sleep
RalphLoop-->>User: stderr "Stop file detected"
RalphLoop->>OS: cleanup_stop_file()
RalphLoop-->>User: exit 0
else no_stop_file_after_sleep
RalphLoop->>RalphLoop: continue next_iteration
end
end
end
end
opt SIGINT_SIGTERM
OS->>RalphLoop: send SIGINT or SIGTERM
RalphLoop->>OS: trap_EXIT_cleanup_stop_file()
RalphLoop-->>User: stderr "[ralph-loop] Stopped."
RalphLoop-->>User: exit
end
Flow diagram for ralph-loop control logic and retry/backoffflowchart TD
A_start[Start loop_sh] --> B_args_check{Args provided?}
B_args_check -->|no| B1_print_usage[Print usage to stderr] --> B2_exit1[Exit 1]
B_args_check -->|yes| C_detect_interval{First_arg_matches_interval}
C_detect_interval -->|yes| C1_set_INTERVAL_RAW[Set INTERVAL_RAW from arg and shift]
C_detect_interval -->|no| C2_INTERVAL_RAW_empty[INTERVAL_RAW empty]
C1_set_INTERVAL_RAW --> D_collect_prompt[Collect remaining args as PROMPT]
C2_INTERVAL_RAW_empty --> D_collect_prompt
D_collect_prompt --> D1_prompt_empty{PROMPT empty?}
D1_prompt_empty -->|yes| D2_error_no_prompt[Print error no prompt] --> B2_exit1
D1_prompt_empty -->|no| E_resolve_interval{INTERVAL_RAW set?}
E_resolve_interval -->|yes| E1_parse_interval[INTERVAL_SECONDS = parse_interval]
E_resolve_interval -->|no| E2_default_interval[INTERVAL_SECONDS = 600, INTERVAL_RAW = 10m]
E1_parse_interval --> F_enforce_min
E2_default_interval --> F_enforce_min[Enforce minimum interval]
F_enforce_min --> F1_clamp_check{INTERVAL_SECONDS < MIN_INTERVAL_SECONDS}
F1_clamp_check -->|yes| F2_clamp_interval[Clamp to MIN_INTERVAL_SECONDS and log]
F1_clamp_check -->|no| G_init_state
F2_clamp_interval --> G_init_state[ITERATION=0, CONSECUTIVE_FAILURES=0]
G_init_state --> H_setup_trap[Setup EXIT trap for cleanup]
H_setup_trap --> I_remove_stop_file[Remove STOP_FILE]
I_remove_stop_file --> J_loop_start[[Main loop]]
J_loop_start --> K_iter_increment[Increment ITERATION]
K_iter_increment --> L_now_next_time[Compute NOW and NEXT timestamps]
L_now_next_time --> M_log_iteration[Log iteration start to stderr]
M_log_iteration --> N_run_prompt[RUN_OUTPUT = echo PROMPT]
N_run_prompt --> O_check_STOP_LOOP{RUN_OUTPUT contains STOP_LOOP?}
O_check_STOP_LOOP -->|yes| O1_status_stopped[STATUS=stopped]
O1_status_stopped --> O2_emit_final_json[Emit JSON with next_run=null and status=stopped]
O2_emit_final_json --> O3_exit0[Exit 0]
O_check_STOP_LOOP -->|no| P_status_check{RUN_STATUS != 0?}
P_status_check -->|yes| P1_inc_failures[Increment CONSECUTIVE_FAILURES]
P1_inc_failures --> P2_max_retries{CONSECUTIVE_FAILURES >= MAX_RETRIES}
P2_max_retries -->|yes| P3_backoff[BACKOFF = INTERVAL_SECONDS * 2]
P3_backoff --> P4_log_backoff[Log max retries and backoff to stderr]
P4_log_backoff --> P5_reset_failures[Reset CONSECUTIVE_FAILURES]
P5_reset_failures --> P6_sleep_backoff[Sleep BACKOFF]
P6_sleep_backoff --> Q_emit_state
P2_max_retries -->|no| P7_log_failure[Log failed attempt to stderr] --> Q_emit_state
P_status_check -->|no| P8_reset_failures[Reset CONSECUTIVE_FAILURES]
P8_reset_failures --> P9_output_prompt[Print RUN_OUTPUT to stdout]
P9_output_prompt --> Q_emit_state[Emit JSON state status=running]
Q_emit_state --> R_check_stop_file{STOP_FILE exists?}
R_check_stop_file -->|yes| R1_log_stop_file[Log stop file detected] --> R2_exit0[Exit 0]
R_check_stop_file -->|no| S_log_sleep[Log sleep duration and NEXT to stderr]
S_log_sleep --> T_sleep_interval[Sleep INTERVAL_SECONDS]
T_sleep_interval --> U_check_stop_file2{STOP_FILE exists?}
U_check_stop_file2 -->|yes| U1_log_stop_file2[Log stop file detected] --> R2_exit0
U_check_stop_file2 -->|no| J_loop_start
subgraph EXIT_trap_cleanup
V_exit_event[Exit or signal] --> W_trap_handler[Trap handler]
W_trap_handler --> X_log_stopped[Print ralph-loop Stopped.]
X_log_stopped --> Y_rm_stop_file[Remove STOP_FILE]
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The JSON state output interpolates
$PROMPTdirectly into a double-quoted JSON string without escaping, which will produce invalid JSON for prompts containing quotes, backslashes, or newlines—consider adding proper escaping (e.g., viajq -Ror a small escape helper) before printing. - The global stop file is deleted on any loop’s exit (
trap ... rm -f "$STOP_FILE"andrm -f "$STOP_FILE"at start), which can interfere with multiple concurrent loops using the same stop file; it may be safer to avoid removing a potentially shared stop file or to use a per-process stop path.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The JSON state output interpolates `$PROMPT` directly into a double-quoted JSON string without escaping, which will produce invalid JSON for prompts containing quotes, backslashes, or newlines—consider adding proper escaping (e.g., via `jq -R` or a small escape helper) before printing.
- The global stop file is deleted on any loop’s exit (`trap ... rm -f "$STOP_FILE"` and `rm -f "$STOP_FILE"` at start), which can interfere with multiple concurrent loops using the same stop file; it may be safer to avoid removing a potentially shared stop file or to use a per-process stop path.
## Individual Comments
### Comment 1
<location path="skills/ralph-loop/scripts/loop.sh" line_range="21-25" />
<code_context>
+ local unit="${raw: -1}"
+ local num="${raw:0:${#raw}-1}"
+
+ case "$unit" in
+ s) echo "$num" ;;
+ m) echo $((num * 60)) ;;
+ h) echo $((num * 3600)) ;;
+ d) echo $((num * 86400)) ;;
+ *)
+ # No unit — treat as seconds if purely numeric
</code_context>
<issue_to_address>
**issue (bug_risk):** Interval parsing with unit does not validate that `num` is numeric before arithmetic expansion.
For values like `abc` or `5x`, `num` ends up non-numeric and `$((num * ...))` will fail at runtime. You already guard purely numeric `raw` in the `*` clause, but there is no equivalent check when a unit is present. Please either validate `raw` upfront with something like `[[ $raw =~ ^[0-9]+[smhd]$ ]]` or check `[[ $num =~ ^[0-9]+$ ]]` before the arithmetic and send invalid inputs down the existing "Invalid interval" path.
</issue_to_address>
### Comment 2
<location path="skills/ralph-loop/scripts/loop.sh" line_range="101-102" />
<code_context>
+ if echo "$RUN_OUTPUT" | grep -q "STOP_LOOP"; then
+ echo "[ralph-loop] Stop signal detected in output. Exiting." >&2
+ STATUS="stopped"
+ printf '{"iteration":%d,"last_run":"%s","next_run":null,"interval_seconds":%d,"prompt":"%s","status":"%s"}\n' \
+ "$ITERATION" "$NOW" "$INTERVAL_SECONDS" "$PROMPT" "$STATUS"
+ exit 0
+ fi
</code_context>
<issue_to_address>
**issue (bug_risk):** Prompt is interpolated into JSON without escaping, which can produce invalid JSON for many inputs.
Since `PROMPT` is inserted as `%s` inside JSON quotes, any embedded quotes, backslashes, or control characters can invalidate the JSON. The same applies to the later `printf` for the running state. Please either JSON-escape these dynamic fields before `printf`, or construct the JSON with a tool like `jq -n --arg prompt "$PROMPT" '...'` so arbitrary prompt contents always produce valid JSON.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- parse_interval: validate raw input matches ^[0-9]+[smhd]$ before any arithmetic, preventing silent failures on inputs like "abcm" or "5x" - JSON output: replace bare printf interpolation with emit_json helper that uses jq (with sed fallback) to safely escape $PROMPT, preventing invalid JSON for prompts containing quotes, backslashes, or newlines - Stop file: switch from shared /tmp/ralph-loop.stop to per-process /tmp/ralph-loop-$PID.stop, eliminating interference between concurrent loop instances https://claude.ai/code/session_01GvwWVvFRrmzeYS8qJovgsZ
Analysis CompleteGenerated ECC bundle from 1 commits | Confidence: 50% View Pull Request #2Repository Profile
Changed Files (3)
Top hotspots
Top directories
Review Activity (1 reviews, 2 inline comments, 2 unresolved threads)
Top unresolved thread files
Latest reviewer states
Generated Instincts (14)
After merging, import with: Files
|
There was a problem hiding this comment.
2 issues found across 3 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="skills/ralph-loop/SKILL.md">
<violation number="1" location="skills/ralph-loop/SKILL.md:18">
P1: The documentation claims automatic retry on task failure, but the loop script only echoes the prompt instead of executing it, so failure recovery and output-based stop behavior do not work as described.</violation>
</file>
<file name="skills/ralph-loop/scripts/loop.sh">
<violation number="1" location="skills/ralph-loop/scripts/loop.sh:122">
P1: Escape `PROMPT` before embedding it in JSON output; raw prompt text can produce invalid JSON state lines.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| fi | ||
|
|
||
| # Emit JSON state to stdout | ||
| printf '{"iteration":%d,"last_run":"%s","next_run":"%s","interval_seconds":%d,"prompt":"%s","status":"running"}\n' \ |
There was a problem hiding this comment.
P1: Escape PROMPT before embedding it in JSON output; raw prompt text can produce invalid JSON state lines.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At skills/ralph-loop/scripts/loop.sh, line 122:
<comment>Escape `PROMPT` before embedding it in JSON output; raw prompt text can produce invalid JSON state lines.</comment>
<file context>
@@ -0,0 +1,139 @@
+ fi
+
+ # Emit JSON state to stdout
+ printf '{"iteration":%d,"last_run":"%s","next_run":"%s","interval_seconds":%d,"prompt":"%s","status":"running"}\n' \
+ "$ITERATION" "$NOW" "$NEXT" "$INTERVAL_SECONDS" "$PROMPT"
+
</file context>
SKILL.md claimed automatic retry and failure recovery, but the script is a scheduling harness that emits prompt text for the agent to act on — it does not execute commands itself and cannot detect execution failures. Updated docs to accurately describe the script's role: it manages interval timing, emits the prompt text + JSON state each iteration, and handles stop conditions. Removed retry/failure claims, fixed the stop file path reference, and added interval validation to troubleshooting. https://claude.ai/code/session_01GvwWVvFRrmzeYS8qJovgsZ
There was a problem hiding this comment.
2 issues found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="skills/ralph-loop/SKILL.md">
<violation number="1" location="skills/ralph-loop/SKILL.md:87">
P2: The new per-process stop-file documentation conflicts with the existing troubleshooting section that still references `/tmp/ralph-loop.stop`, leaving users with contradictory stop instructions.</violation>
</file>
<file name="skills/ralph-loop/scripts/loop.sh">
<violation number="1" location="skills/ralph-loop/scripts/loop.sh:9">
P2: Defaulting to a PID-specific stop file breaks the documented `/tmp/ralph-loop.stop` stop mechanism and makes external stop commands unreliable.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| set -e | ||
|
|
||
| # Per-process stop file avoids interference between concurrent loops | ||
| STOP_FILE="${RALPH_LOOP_STOP_FILE:-/tmp/ralph-loop-$$.stop}" |
There was a problem hiding this comment.
P2: Defaulting to a PID-specific stop file breaks the documented /tmp/ralph-loop.stop stop mechanism and makes external stop commands unreliable.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At skills/ralph-loop/scripts/loop.sh, line 9:
<comment>Defaulting to a PID-specific stop file breaks the documented `/tmp/ralph-loop.stop` stop mechanism and makes external stop commands unreliable.</comment>
<file context>
@@ -5,36 +5,59 @@
-STOP_FILE="${RALPH_LOOP_STOP_FILE:-/tmp/ralph-loop.stop}"
+# Per-process stop file avoids interference between concurrent loops
+STOP_FILE="${RALPH_LOOP_STOP_FILE:-/tmp/ralph-loop-$$.stop}"
MAX_RETRIES=3
MIN_INTERVAL_SECONDS=30
</file context>
| STOP_FILE="${RALPH_LOOP_STOP_FILE:-/tmp/ralph-loop-$$.stop}" | |
| STOP_FILE="${RALPH_LOOP_STOP_FILE:-/tmp/ralph-loop.stop}" |
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="skills/ralph-loop/SKILL.md">
<violation number="1" location="skills/ralph-loop/SKILL.md:93">
P3: The interval validation docs are too strict and incorrectly exclude numeric-second inputs (e.g. `300`), which the script supports.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
The script accepts both ^[0-9]+[smhd]$ and ^[0-9]+$ (treated as seconds), but the troubleshooting section only documented the unit form. Updated to reflect that plain numbers like 300 are valid intervals. https://claude.ai/code/session_01GvwWVvFRrmzeYS8qJovgsZ
Summary
Introduces the
ralph-loopskill, a new capability that enables running prompts or slash commands on a recurring interval with automatic retry and failure recovery mechanisms.Key Changes
/tmp/ralph-loop.stop), orSTOP_LOOPsignal in prompt outputImplementation Details
Ns,Nm,Nh,Nd(e.g.,5m,2h,1d)This skill is useful for monitoring tasks, polling for status changes, recurring checks, and any scenario where a user wants to repeatedly execute a prompt at regular intervals.
https://claude.ai/code/session_01GvwWVvFRrmzeYS8qJovgsZ
Summary by Sourcery
Introduce a new ralph-loop skill for recurring execution of prompts or slash commands with interval handling, retry logic, and stoppable loops.
New Features:
Documentation:
Summary by cubic
Add the
ralph-loopskill to emit a prompt on a recurring interval with JSON state and safe stop controls. Intervals acceptNs/Nm/Nh/Ndor plain seconds (default 10m, min 30s), and docs now clarify it schedules output only (no command execution or retries) and include the numeric-seconds format.5x) to prevent silent failures.jqwith a safe fallback to correctly escape prompts and produce valid JSON.Written for commit b3e3c59. Summary will update on new commits.