-
Notifications
You must be signed in to change notification settings - Fork 827
Description
Bug Report
Three independent bugs found in the ralph-loop plugin. Fixes are minimal (6 lines changed across 2 files). PR #448 was auto-declined due to external contributor policy, so documenting here with full fixes.
Bug 1: Promise detection matches without <promise> tags
File: hooks/stop-hook.sh, line 119
Problem: The perl command uses -pe which prints input unchanged when no substitution matches. If Claude's entire output happens to equal the completion promise text (without using <promise> tags), the loop incorrectly exits.
# Current (broken): -pe prints full input when no <promise> tags found
PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -pe 's/.*?<promise>(.*?)<\/promise>.*/$1/s; ...')Fix: Change -pe to -ne with conditional print — only outputs when <promise> tags are actually present:
PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -ne 'if (/<promise>(.*?)<\/promise>/s) { $t = $1; $t =~ s/^\s+|\s+$//g; $t =~ s/\s+/ /g; print $t; }' 2>/dev/null || echo "")Bug 2: awk silently drops --- lines from user prompts
File: hooks/stop-hook.sh, line 136
Problem: The awk rule /^---$/{i++; next} matches ALL --- lines in the file, not just the two YAML frontmatter delimiters. Any --- line in the user's prompt body is silently consumed and removed from the prompt fed back to Claude.
The comment on line 135 says "Use i>=2 instead of i==2 to handle --- in prompt content" — but i>=2 only fixes printing of lines between --- markers. The --- line itself is still eaten by the {i++; next} action.
# Current (broken): ALL --- lines are consumed
PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$RALPH_STATE_FILE")
# Reproduction: prompt containing markdown separator
# ---
# active: true
# iteration: 1
# ---
#
# Refactor this config:
# --- ← THIS LINE IS SILENTLY REMOVED
# key: value
# --- ← THIS LINE IS SILENTLY REMOVEDFix: Add && i<2 guard so only the first two frontmatter delimiters are consumed:
PROMPT_TEXT=$(awk '/^---$/ && i<2 {i++; next} i>=2' "$RALPH_STATE_FILE")Bug 3: YAML quoting breaks with double quotes in completion promise
File: scripts/setup-ralph-loop.sh, lines 134-138 (write side) + hooks/stop-hook.sh, line 25 (read side)
Problem: The completion promise is wrapped in double quotes without escaping internal quotes, producing invalid YAML:
# Current (broken):
COMPLETION_PROMISE_YAML="\"$COMPLETION_PROMISE\""
# With --completion-promise 'say "hello"':
# Produces: completion_promise: "say "hello"" ← invalid YAMLFix (write side, setup-ralph-loop.sh): Escape backslashes and double quotes:
ESCAPED=$(echo "$COMPLETION_PROMISE" | sed 's/\\/\\\\/g; s/"/\\"/g')
COMPLETION_PROMISE_YAML="\"$ESCAPED\""Fix (read side, stop-hook.sh line 25): Add unescaping:
COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/' | sed 's/\\"/"/g; s/\\\\/\\/g')Full diff
diff --git a/plugins/ralph-loop/hooks/stop-hook.sh b/plugins/ralph-loop/hooks/stop-hook.sh
index abc1234..def5678 100755
--- a/plugins/ralph-loop/hooks/stop-hook.sh
+++ b/plugins/ralph-loop/hooks/stop-hook.sh
@@ -22,7 +22,7 @@ ITERATION=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//')
MAX_ITERATIONS=$(echo "$FRONTMATTER" | grep '^max_iterations:' | sed 's/max_iterations: *//')
# Extract completion_promise and strip surrounding quotes if present
-COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/')
+COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/' | sed 's/\\"/"/g; s/\\\\/\\/g')
@@ -116,8 +116,8 @@ if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then
# Extract text from <promise> tags using Perl for multiline support
# -0777 slurps entire input, s flag makes . match newlines
- # .*? is non-greedy (takes FIRST tag), whitespace normalized
- PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -pe 's/.*?<promise>(.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "")
+ # -ne only prints when match found (unlike -pe which prints input unchanged on no match)
+ PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -ne 'if (/<promise>(.*?)<\/promise>/s) { $t = $1; $t =~ s/^\s+|\s+$//g; $t =~ s/\s+/ /g; print $t; }' 2>/dev/null || echo "")
@@ -133,7 +133,7 @@ NEXT_ITERATION=$((ITERATION + 1))
# Extract prompt (everything after the closing ---)
# Skip first --- line, skip until second --- line, then print everything after
-# Use i>=2 instead of i==2 to handle --- in prompt content
-PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$RALPH_STATE_FILE")
+PROMPT_TEXT=$(awk '/^---$/ && i<2 {i++; next} i>=2' "$RALPH_STATE_FILE")
diff --git a/plugins/ralph-loop/scripts/setup-ralph-loop.sh b/plugins/ralph-loop/scripts/setup-ralph-loop.sh
--- a/plugins/ralph-loop/scripts/setup-ralph-loop.sh
+++ b/plugins/ralph-loop/scripts/setup-ralph-loop.sh
@@ -134,7 +134,8 @@ if [[ -n "$COMPLETION_PROMISE" ]] && [[ "$COMPLETION_PROMISE" != "null" ]]; then
- COMPLETION_PROMISE_YAML="\"$COMPLETION_PROMISE\""
+ ESCAPED=$(echo "$COMPLETION_PROMISE" | sed 's/\\/\\\\/g; s/"/\\"/g')
+ COMPLETION_PROMISE_YAML="\"$ESCAPED\""Environment
- macOS 15.3 (Darwin 25.2.0)
- bash 3.2 (macOS default)
- Claude Code latest
- Plugin installed from marketplace 2026-02-21