From 4d450c3d45e23a5f2ed176ac8a533d14a41b5712 Mon Sep 17 00:00:00 2001 From: Nahrin Date: Thu, 11 Jun 2026 22:39:39 -0400 Subject: [PATCH] fix(ralph-wiggum): make completion promise matching case-insensitive and whitespace-tolerant Promise matching previously used exact string comparison, causing false negatives when Claude output casing differed from the configured promise (e.g. "Complete" vs "COMPLETE") or when YAML parsing introduced extra whitespace. Both sides are now normalized and lowercased before comparison. Uses tr for portability with macOS default Bash 3. Co-Authored-By: Claude Opus 4.6 --- plugins/ralph-wiggum/README.md | 2 +- plugins/ralph-wiggum/hooks/stop-hook.sh | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/ralph-wiggum/README.md b/plugins/ralph-wiggum/README.md index a65f010889..12f1f32bf6 100644 --- a/plugins/ralph-wiggum/README.md +++ b/plugins/ralph-wiggum/README.md @@ -131,7 +131,7 @@ Always use `--max-iterations` as a safety net to prevent infinite loops on impos # - Suggest alternative approaches" ``` -**Note**: The `--completion-promise` uses exact string matching, so you cannot use it for multiple completion conditions (like "SUCCESS" vs "BLOCKED"). Always rely on `--max-iterations` as your primary safety mechanism. +**Note**: The `--completion-promise` uses case-insensitive string matching with whitespace normalization, so `COMPLETE`, `Complete`, and `complete` are all equivalent. However, it does not support multiple completion conditions (like "SUCCESS" vs "BLOCKED"). Always rely on `--max-iterations` as your primary safety mechanism. ## Philosophy diff --git a/plugins/ralph-wiggum/hooks/stop-hook.sh b/plugins/ralph-wiggum/hooks/stop-hook.sh index 9aa611c104..68b296dcf7 100755 --- a/plugins/ralph-wiggum/hooks/stop-hook.sh +++ b/plugins/ralph-wiggum/hooks/stop-hook.sh @@ -118,9 +118,11 @@ if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then # .*? is non-greedy (takes FIRST tag), whitespace normalized PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -pe 's/.*?(.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "") - # Use = for literal string comparison (not pattern matching) - # == in [[ ]] does glob pattern matching which breaks with *, ?, [ characters - if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then + # Normalize and lowercase both sides for case-insensitive, whitespace-tolerant matching + # Uses tr for portability (${var,,} requires Bash 4+ but macOS ships Bash 3) + PROMISE_LOWER=$(echo "$PROMISE_TEXT" | tr '[:upper:]' '[:lower:]') + EXPECTED_LOWER=$(echo "$COMPLETION_PROMISE" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//; s/[[:space:]]\{1,\}/ /g' | tr '[:upper:]' '[:lower:]') + if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_LOWER" = "$EXPECTED_LOWER" ]]; then echo "✅ Ralph loop: Detected $COMPLETION_PROMISE" rm "$RALPH_STATE_FILE" exit 0