diff --git a/ralph-loop/hooks/capture-response.sh b/ralph-loop/hooks/capture-response.sh
index d8da50a2..5aa1052b 100755
--- a/ralph-loop/hooks/capture-response.sh
+++ b/ralph-loop/hooks/capture-response.sh
@@ -37,7 +37,17 @@ if [[ -z "$RESPONSE_TEXT" ]]; then
fi
# Check for TEXT in the response
-PROMISE_TEXT=$(echo "$RESPONSE_TEXT" | perl -0777 -pe 's/.*?(.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "")
+PROMISE_TEXT=$(
+ printf '%s' "$RESPONSE_TEXT" |
+ perl -0777 -ne '
+ if (/(.*?)<\/promise>/s) {
+ my $promise = $1;
+ $promise =~ s/^\s+|\s+$//g;
+ $promise =~ s/\s+/ /g;
+ print $promise;
+ }
+ ' 2>/dev/null || true
+)
if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then
touch "$DONE_FLAG"
diff --git a/ralph-loop/hooks/tests/capture-response.test.sh b/ralph-loop/hooks/tests/capture-response.test.sh
new file mode 100755
index 00000000..27c91efa
--- /dev/null
+++ b/ralph-loop/hooks/tests/capture-response.test.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+set -euo pipefail
+
+SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+HOOK="$SCRIPT_DIR/../capture-response.sh"
+TEST_PROJECT=$(mktemp -d)
+STATE_DIR="$TEST_PROJECT/.cursor/ralph"
+DONE_FLAG="$STATE_DIR/done"
+
+cleanup() {
+ rm -rf "$TEST_PROJECT"
+}
+trap cleanup EXIT
+
+mkdir -p "$STATE_DIR"
+cat >"$STATE_DIR/scratchpad.md" <<'EOF'
+---
+iteration: 1
+max_iterations: 10
+completion_promise: "ALL TESTS PASS"
+---
+Run the requested task.
+EOF
+
+run_case() {
+ local name=$1
+ local response=$2
+ local expected=$3
+ local actual="absent"
+
+ rm -f "$DONE_FLAG"
+ jq -n --arg text "$response" '{text: $text}' |
+ CURSOR_PROJECT_DIR="$TEST_PROJECT" bash "$HOOK"
+
+ if [[ -f "$DONE_FLAG" ]]; then
+ actual="present"
+ fi
+
+ if [[ "$actual" != "$expected" ]]; then
+ echo "FAIL: $name (expected done flag $expected, got $actual)" >&2
+ return 1
+ fi
+
+ echo "PASS: $name"
+}
+
+run_case "untagged matching text" "ALL TESTS PASS" "absent"
+run_case "tagged matching text" \
+ "Finished: ALL TESTS PASS" "present"
+run_case "tagged text with normalized whitespace" \
+ $'\n ALL TESTS\n PASS \n' "present"
+run_case "tagged non-matching text" \
+ "TESTS FAILED" "absent"
+run_case "missing closing tag" "ALL TESTS PASS" "absent"
+run_case "empty response" "" "absent"