Skip to content

fix(response_analyzer): match RALPH_STATUS YAML colon-block format#262

Open
justanotherguyme wants to merge 2 commits intofrankbria:mainfrom
justanotherguyme:zach/extend-ralph-status-yaml-emit
Open

fix(response_analyzer): match RALPH_STATUS YAML colon-block format#262
justanotherguyme wants to merge 2 commits intofrankbria:mainfrom
justanotherguyme:zach/extend-ralph-status-yaml-emit

Conversation

@justanotherguyme
Copy link
Copy Markdown

@justanotherguyme justanotherguyme commented May 3, 2026

Summary

Extends response_analyzer.sh RALPH_STATUS detection to also match the YAML colon-block format (RALPH_STATUS: with indented keys) emitted by some agent prompts, in addition to the canonical ---RALPH_STATUS--- separator-marker format.

Problem

Some agent prompts emit the structured-status block as YAML rather than the canonical separator-marker form documented in lib/enable_core.sh:560-568. For example:

RALPH_STATUS:
  EXIT_SIGNAL: true
  reason: "All tasks complete"

The detection regex at two locations currently greps for the literal ---RALPH_STATUS--- separator only:

  • lib/response_analyzer.sh:161 (JSON-mode .result text path)
  • lib/response_analyzer.sh:489 (text-mode structured-output fallback)

YAML-format emits silently fall through. exit_signal stays at default false, the loop continues past explicit EXIT_SIGNAL: true emissions, tokens burn until the user manually interrupts.

Fix

Extend both regexes to also match RALPH_STATUS: (the trailing colon distinguishes it from prose mentions of RALPH_STATUS):

-if [[ -n "$result_text" ]] && echo "$result_text" | grep -q -- "---RALPH_STATUS---"; then
+if [[ -n "$result_text" ]] && echo "$result_text" | grep -qE -- "---RALPH_STATUS---|RALPH_STATUS:"; then
-if grep -q -- "---RALPH_STATUS---" "$output_file"; then
+if grep -qE -- "---RALPH_STATUS---|RALPH_STATUS:" "$output_file"; then

The downstream grep "EXIT_SIGNAL:" | cut -d: -f2 | xargs extraction already handles both layouts uniformly because both formats emit one EXIT_SIGNAL: <bool> line.

A documentation comment block above each regex describes both supported formats so future maintainers see the dual-format contract before the regex.

Tests

4 new bats tests in tests/unit/test_json_parsing.bats (60 @test blocks total, was 56):

  • Text mode + YAML emit + EXIT_SIGNAL=true → exits cleanly
  • Text mode + YAML emit + EXIT_SIGNAL=false → continues (respects explicit continue intent, same semantics as separator format)
  • JSON-mode .result wrapper + YAML emit + EXIT_SIGNAL=true → exits
  • JSON-mode .result wrapper + YAML emit + EXIT_SIGNAL=false → continues

The existing canonical-format test at line 394 (@test "analyze_response still handles traditional RALPH_STATUS format") is unmodified and verifies no regression on the separator-marker path.

Test plan

  • CI green on this PR (existing 56 tests + 4 new tests = 60 pass)
  • Manual verification: agent prompt emitting YAML format triggers clean exit (verified locally pre-PR via canned input through patched parser)

Backwards compatibility

Backwards-compatible. The existing ---RALPH_STATUS--- separator-marker code paths are unchanged in semantics; the regex alternation adds a second match form. Existing prompts emitting the canonical format see identical behavior.

Why both lines

Line 161 is the JSON-output mode path (default per CLAUDE_OUTPUT_FORMAT="json"). Line 489 is the text-mode structured-output fallback. Both paths can be reached depending on --output-format and CLI version; fixing both lines closes the bug regardless of mode.

Summary by CodeRabbit

  • Bug Fixes

    • Improved detection of structured status blocks to recognize both canonical and YAML-style headers (including indented headers), ensuring reliable exit-signal parsing across formats.
  • Tests

    • Added comprehensive tests covering YAML-style status blocks in both text and JSON analysis modes, including positive, negative, and indentation-edge cases.

…on to ---RALPH_STATUS--- separator

Some agent prompts (e.g. multi-agent task-board harnesses) emit the
structured-status block as YAML — a colon heading with indented keys —
rather than the canonical separator-marker form documented in
lib/enable_core.sh:560-568:

  RALPH_STATUS:
    EXIT_SIGNAL: true
    reason: "..."

The detection regex at lib/response_analyzer.sh:161 (JSON-mode .result
text path) and lib/response_analyzer.sh:489 (text-mode structured-output
fallback) currently grep for the literal "---RALPH_STATUS---" separator
only, so YAML-format emits silently fall through and exit_signal stays
at the default false value. The loop then continues past explicit
EXIT_SIGNAL: true emissions, burning tokens until the user manually
interrupts.

This change extends both regexes to also match "RALPH_STATUS:" (with the
trailing colon distinguishing it from prose mentions of RALPH_STATUS).
The downstream extraction `grep "EXIT_SIGNAL:" | cut -d: -f2 | xargs`
already handles both layouts uniformly because both formats emit one
"EXIT_SIGNAL: <bool>" line.

A documentation block above each regex describes both supported formats
so future maintainers see the dual-format contract before the regex.

4 new bats tests in tests/unit/test_json_parsing.bats verify:

  - Text mode + YAML emit + EXIT_SIGNAL=true → exits cleanly
  - Text mode + YAML emit + EXIT_SIGNAL=false → continues (respects
    explicit continue intent, matching the documented EXIT_SIGNAL
    semantics for the separator format)
  - JSON-mode .result wrapper + YAML emit + EXIT_SIGNAL=true → exits
  - JSON-mode .result wrapper + YAML emit + EXIT_SIGNAL=false →
    continues

Backwards-compatible: existing canonical-format tests (line 394
test_json_parsing.bats) still pass; no change to the separator-marker
code paths.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

Walkthrough

This PR updates lib/response_analyzer.sh to recognize both canonical ---RALPH_STATUS--- and YAML-style RALPH_STATUS: headers (including indented headers) when deciding to extract EXIT_SIGNAL, and adds tests covering both formats and related regressions.

Changes

RALPH_STATUS Format Flexibility

Layer / File(s) Summary
Header Detection
lib/response_analyzer.sh
parse_json_response and analyze_response broaden the gate to match either ---RALPH_STATUS--- or a line-anchored RALPH_STATUS: (allows optional leading whitespace).
Exit Signal Extraction
lib/response_analyzer.sh
Downstream parsing continues to read EXIT_SIGNAL: lines the same way; the gate change controls whether extraction runs.
Tests — YAML block behavior
tests/unit/test_json_parsing.bats
Adds tests verifying text-mode YAML-style RALPH_STATUS: blocks cause EXIT_SIGNAL: true/false behavior as expected, and JSON-mode .result with YAML block sets analysis.exit_signal accordingly.
Tests — Non-triggering inline mentions & indentation
tests/unit/test_json_parsing.bats
Adds regressions ensuring inline (non-block) RALPH_STATUS: mentions do NOT trigger the gate, and that indented/leading-whitespace RALPH_STATUS: block headers DO trigger it.
Test Fix
tests/unit/test_json_parsing.bats
Removes stray numeric tokens that previously polluted a text-mode heuristic test's assertion.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A rabbit reads the output trail,
Dashes or colons, neither will fail,
Indented or neat, status it finds,
EXIT_SIGNAL answered in all kinds,
Hopping home with test-green tails.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: extending RALPH_STATUS detection to support the YAML colon-block format alongside the canonical separator format.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/response_analyzer.sh`:
- Line 174: The current check that scans result_text for RALPH_STATUS is too
loose; update the conditional that references result_text (e.g., the if using
grep -qE -- "---RALPH_STATUS---|RALPH_STATUS:") to only match block-header lines
by anchoring the pattern to the start of a line (allowing optional leading
whitespace), for example using a regex like
'^[[:space:]]*(---RALPH_STATUS---|RALPH_STATUS:)' so inline occurrences are
ignored; then, in the downstream exit-detection logic that reads the
RALPH_STATUS block (the code paths handling EXIT_SIGNAL and
completion_indicators), enforce the dual-condition: require an explicit
EXIT_SIGNAL field present in the RALPH_STATUS block and completion_indicators >=
2 before triggering exit to prevent false positives.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6592b497-a34c-4945-99be-2706c5cf1c6f

📥 Commits

Reviewing files that changed from the base of the PR and between 24b8969 and 86e04da.

📒 Files selected for processing (2)
  • lib/response_analyzer.sh
  • tests/unit/test_json_parsing.bats

Comment thread lib/response_analyzer.sh Outdated
Per CodeRabbit review on PR frankbria#262: the dual-format regex
"---RALPH_STATUS---|RALPH_STATUS:" matches inline mentions in prose
(e.g., a doc string or commit message containing "the RALPH_STATUS:
emit format") because grep -E without an anchor matches anywhere on
a line. That false-matches the gate, enters the EXIT_SIGNAL extraction
block, and on prose containing both "RALPH_STATUS:" and
"STATUS: COMPLETE" can wrongly set exit_signal=true.

Anchor to start-of-line (with optional leading whitespace) so only
block headers trigger the gate:

  -if [[ ... ]] && grep -qE -- "---RALPH_STATUS---|RALPH_STATUS:" ...
  +if [[ ... ]] && grep -qE -- "^[[:space:]]*(---RALPH_STATUS---|RALPH_STATUS:)" ...

Applied at both call sites:
- lib/response_analyzer.sh:174 (JSON-mode .result text path)
- lib/response_analyzer.sh:507 (text-mode structured-output fallback)

Doc-comment blocks above each regex updated to describe the line-anchor
behavior so future maintainers don't accidentally regress.

3 new bats regression tests in tests/unit/test_json_parsing.bats:

- text mode: inline "RALPH_STATUS:" in prose does NOT trigger the gate
- JSON-mode .result with inline "RALPH_STATUS:" in prose does NOT
  trigger the gate
- text mode: leading-whitespace block header (indented YAML, e.g.,
  quoted in tool output) DOES still trigger — the anchor allows
  optional leading whitespace by design

Total @test blocks in test_json_parsing.bats: 56 → 60 (initial PR) →
63 (this commit). All previous tests still pass; the four YAML-format
tests added in 86e04da continue to verify the dual-format detection.
@justanotherguyme
Copy link
Copy Markdown
Author

Thanks @coderabbitai for the line-anchor catch — pushed 8f1466e addressing it:

  • Anchored both regex sites (lib/response_analyzer.sh:174 JSON-mode and :507 text-mode) to start-of-line with optional leading whitespace: grep -qE -- "^[[:space:]]*(---RALPH_STATUS---|RALPH_STATUS:)".
  • Updated doc-comment blocks above each regex to describe the line-anchor behavior so future maintainers don't accidentally regress.
  • Added 3 new bats regression tests in tests/unit/test_json_parsing.bats (now 63 @test blocks total, was 60 in the original PR commit):
    • text mode: inline "RALPH_STATUS:" in prose does NOT trigger the gate
    • JSON-mode .result with inline "RALPH_STATUS:" in prose does NOT trigger the gate
    • text mode: leading-whitespace block header (indented YAML) DOES still trigger — the anchor allows optional leading whitespace by design

On the dual-condition concern: ralph_loop.sh:312-327 already enforces recent_completion_indicators >= 2 AND claude_exit_signal == "true" per the existing CLAUDE.md "Completion Indicators with EXIT_SIGNAL Gate" architecture. This PR doesn't touch that path, so the existing dual-condition gate continues to apply unchanged. The line-anchor fix above closes the false-positive vector at response_analyzer.sh directly.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

Tip

For best results, initiate chat on the files or code changes.

@justanotherguyme: The fix looks solid — ^[[:space:]]*(---RALPH_STATUS---|RALPH_STATUS:) correctly threads the needle between rejecting inline prose occurrences and still matching indented YAML block headers. The three new regression tests (two negative prose cases + one positive indented-whitespace case) give good confidence that the anchor behaves as intended. The clarification on ralph_loop.sh:312-327's dual-condition gate is appreciated — it confirms the defense-in-depth is already in place upstream and this PR tightens the first layer appropriately. Thanks for the thorough follow-up!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/response_analyzer.sh`:
- Line 176: The issue is that combining "local" with command substitution masks
the command's exit code for result_text; change the code in the response parsing
logic so you first declare the variable (local result_text) and then assign it
with the jq command substitution (result_text=$(jq -r '.result // ""'
"$output_file" 2>/dev/null)), and after assignment check the jq exit status ($?)
or handle empty/error cases; reference the variable result_text and input
variable output_file and ensure any failure from jq is propagated or logged
instead of being hidden.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 45caac2a-8f67-4006-9bfd-62f991b8c19c

📥 Commits

Reviewing files that changed from the base of the PR and between 86e04da and 8f1466e.

📒 Files selected for processing (2)
  • lib/response_analyzer.sh
  • tests/unit/test_json_parsing.bats

Comment thread lib/response_analyzer.sh
# both layouts uniformly because both emit one EXIT_SIGNAL: <bool> line.
# See also the parallel check at the structured-output text-parsing fallback below.
if [[ "$exit_signal" == "false" && "$has_result_field" == "true" ]]; then
local result_text=$(jq -r '.result // ""' "$output_file" 2>/dev/null)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

SC2155: Declare and assign result_text separately.

When local and command substitution are combined, the local builtin's exit code (always 0) masks the subcommand's exit code. Shellcheck flags this on line 176.

🛠 Proposed fix
-    local result_text=$(jq -r '.result // ""' "$output_file" 2>/dev/null)
+    local result_text
+    result_text=$(jq -r '.result // ""' "$output_file" 2>/dev/null)
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 176-176: Declare and assign separately to avoid masking return values.

(SC2155)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/response_analyzer.sh` at line 176, The issue is that combining "local"
with command substitution masks the command's exit code for result_text; change
the code in the response parsing logic so you first declare the variable (local
result_text) and then assign it with the jq command substitution
(result_text=$(jq -r '.result // ""' "$output_file" 2>/dev/null)), and after
assignment check the jq exit status ($?) or handle empty/error cases; reference
the variable result_text and input variable output_file and ensure any failure
from jq is propagated or logged instead of being hidden.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant