fix(loop): guard bare function calls against set -e script termination#207
fix(loop): guard bare function calls against set -e script termination#207timothy-20 wants to merge 40 commits intofrankbria:mainfrom
Conversation
…rankbria#190) build_loop_context() was only called inside the CLAUDE_USE_CONTINUE=true conditional, so SESSION_CONTINUITY=false sessions received no loop context (loop number, remaining tasks, circuit breaker state, previous summary). This is especially problematic for fresh sessions that lack prior context. Move the call outside the conditional so context is always built and passed via --append-system-prompt. Session ID initialization remains gated behind CLAUDE_USE_CONTINUE as intended. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ess of session mode (frankbria#190)
…nkbria#190) Add QUESTION_PATTERNS array and detect_questions() function to identify when Claude is asking questions instead of acting autonomously. Integrate detection into both JSON and text analysis paths, emitting asking_questions (bool) and question_count (int) fields in .response_analysis output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rankbria#190) When Claude asks questions instead of acting, hold consecutive_no_progress steady rather than incrementing. This prevents the circuit breaker from opening prematurely during question loops in headless mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ons (frankbria#190) When .response_analysis shows asking_questions=true, build_loop_context() now appends a headless-mode corrective message telling Claude to choose safe defaults and proceed autonomously instead of asking questions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… detect_questions - test_json_parsing: 5 tests for detect_questions() (pattern+?, no pattern, no ?, empty input, multiple questions) - test_exit_detection: 2 tests for analyze_response() asking_questions field - test_cli_modern: 2 tests for build_loop_context() corrective message + sync inline function copy with ralph_loop.sh - Fix: sanitize grep -c output with tr -d before arithmetic evaluation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add asking_questions and question_count to structured fields list - Document detect_questions() and QUESTION_PATTERNS in response_analyzer - Add question loop suppression to circuit breaker thresholds section - Add corrective guidance to loop context documentation - Update test counts: 568 → 578 (json_parsing 57, exit_detection 55, cli_modern 71) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…estion detection (frankbria#190) work_summary is already populated from .json_parse_result at L372. The intermediate json_text_content variable and file re-read were unnecessary defensive code that duplicated the same jq extraction. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(frankbria#190): detect question loops and suppress circuit breaker in headless mode 3-layer solution for Bug 2 — SESSION_CONTINUITY=false headless question loop: 1. Question pattern detection (detect_questions + QUESTION_PATTERNS) 2. Circuit breaker no-progress counter freeze on question loops 3. Corrective guidance injection in next loop iteration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ng (frankbria#190) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
check_claude_version() was defined but never called in the startup sequence. Add both check_claude_version() and new check_claude_updates() calls after validate_claude_command(), ensuring CLI compatibility and freshness before entering the loop. Auto-update queries npm registry and attempts upgrade; failures are non-blocking (WARN only). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rankbria#190) - Add compare_semver() helper with proper major→minor→patch sequential comparison, replacing integer arithmetic (major*10000+minor*100+patch) that produced incorrect results for patch numbers >= 100 - Refactor check_claude_version() and check_claude_updates() to use compare_semver(), eliminating duplicated version comparison logic - Add CLAUDE_AUTO_UPDATE config flag (default: true) to .ralphrc, templates, enable_core, and setup — allows disabling auto-update checks for air-gapped environments - Add 7 new tests: semver edge cases + CLAUDE_AUTO_UPDATE behavior - Update CLAUDE.md: test counts 588→595, document new config variable Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nkbria#190) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…verage (frankbria#190) Remove 49 redundant tests: cross-file duplicates (6), structural grep-only tests covered by behavioral tests (26), and threshold/parameter duplicates consolidated into single tests (17). 595 -> 546 tests, all passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3 test files hardcoded /tmp/ralph-* paths in mktemp, causing 'Operation not permitted' in sandboxed environments. Bare mktemp -d uses the system default temp directory, consistent with the other 15 test files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n-UTC systems The BSD date fallback in get_past_timestamp() used date -r which outputs local time, but the format string hardcoded +00:00 (UTC). On non-UTC systems (e.g. JST+9), parse_iso_to_epoch() interpreted the local time as UTC, producing a future epoch and negative elapsed minutes, causing 5 cooldown tests to always fail. Adding -u flag to date -r ensures UTC output matching the +00:00 suffix. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…paths - Kill orphaned Claude child process on Ctrl+C via global _CLAUDE_PID tracking - Switch from trap SIGINT/SIGTERM to trap EXIT to cover set -e and normal exits - Add reentrancy guard (_CLEANUP_DONE) to prevent double cleanup execution - Reset stale .exit_signals and .response_analysis on startup (SIGKILL/OOM recovery) - Clean up empty stderr files and temporary stream logs to prevent accumulation - Fix test_session_continuity grep range to verify reset_session in expanded cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rankbria#190) build_loop_context() was only called inside the CLAUDE_USE_CONTINUE=true conditional, so SESSION_CONTINUITY=false sessions received no loop context (loop number, remaining tasks, circuit breaker state, previous summary). This is especially problematic for fresh sessions that lack prior context. Move the call outside the conditional so context is always built and passed via --append-system-prompt. Session ID initialization remains gated behind CLAUDE_USE_CONTINUE as intended. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nkbria#190) Add QUESTION_PATTERNS array and detect_questions() function to identify when Claude is asking questions instead of acting autonomously. Integrate detection into both JSON and text analysis paths, emitting asking_questions (bool) and question_count (int) fields in .response_analysis output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rankbria#190) When Claude asks questions instead of acting, hold consecutive_no_progress steady rather than incrementing. This prevents the circuit breaker from opening prematurely during question loops in headless mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ons (frankbria#190) When .response_analysis shows asking_questions=true, build_loop_context() now appends a headless-mode corrective message telling Claude to choose safe defaults and proceed autonomously instead of asking questions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… detect_questions - test_json_parsing: 5 tests for detect_questions() (pattern+?, no pattern, no ?, empty input, multiple questions) - test_exit_detection: 2 tests for analyze_response() asking_questions field - test_cli_modern: 2 tests for build_loop_context() corrective message + sync inline function copy with ralph_loop.sh - Fix: sanitize grep -c output with tr -d before arithmetic evaluation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add asking_questions and question_count to structured fields list - Document detect_questions() and QUESTION_PATTERNS in response_analyzer - Add question loop suppression to circuit breaker thresholds section - Add corrective guidance to loop context documentation - Update test counts: 568 → 578 (json_parsing 57, exit_detection 55, cli_modern 71) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…estion detection (frankbria#190) work_summary is already populated from .json_parse_result at L372. The intermediate json_text_content variable and file re-read were unnecessary defensive code that duplicated the same jq extraction. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ng (frankbria#190) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
check_claude_version() was defined but never called in the startup sequence. Add both check_claude_version() and new check_claude_updates() calls after validate_claude_command(), ensuring CLI compatibility and freshness before entering the loop. Auto-update queries npm registry and attempts upgrade; failures are non-blocking (WARN only). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rankbria#190) - Add compare_semver() helper with proper major→minor→patch sequential comparison, replacing integer arithmetic (major*10000+minor*100+patch) that produced incorrect results for patch numbers >= 100 - Refactor check_claude_version() and check_claude_updates() to use compare_semver(), eliminating duplicated version comparison logic - Add CLAUDE_AUTO_UPDATE config flag (default: true) to .ralphrc, templates, enable_core, and setup — allows disabling auto-update checks for air-gapped environments - Add 7 new tests: semver edge cases + CLAUDE_AUTO_UPDATE behavior - Update CLAUDE.md: test counts 588→595, document new config variable Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nkbria#190) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…verage (frankbria#190) Remove 49 redundant tests: cross-file duplicates (6), structural grep-only tests covered by behavioral tests (26), and threshold/parameter duplicates consolidated into single tests (17). 595 -> 546 tests, all passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…frankbria#190) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
….ralphrc (frankbria#190) - Wrap detect_questions() calls in if-guards to prevent set -e from aborting on exit code 1 (no questions found) - Add chmod 600 to .ralphrc in setup.sh for both lib and fallback paths Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e patterns (frankbria#190) Remove the ? bail-out and ? filter in detect_questions() so that declarative wait patterns (e.g. "Please confirm", "Awaiting input") are detected without requiring a question mark. Use grep -ciw for word-boundary matching to prevent false positives from substring overlap (e.g. "should implement" no longer matches "should I"). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts: # lib/response_analyzer.sh # tests/unit/test_json_parsing.bats
frankbria#190) set -e (line 6) converts non-zero return from bare function calls into immediate script exit, making most error handling code dead code. Fixes 7 instances: - check_claude_version: return 1 -> return 0 (non-blocking warning) - check_claude_updates: return 1 -> return 0 (non-blocking warning) - execute_claude_code: bare call -> || exec_result=$? - record_loop_result: bare call -> || circuit_result=$? - analyze_response: bare call -> || analysis_exit_code=$? - update_exit_signals: bare call -> || true - log_analysis_summary: bare call -> || true The || pattern exploits bash rule that set -e is disabled in LHS of ||, safely capturing exit codes without script termination. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WalkthroughThe PR adds question-detection to response analysis, surfaces Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant ralph as ralph_loop.sh
participant npm as npm_registry
participant claude as Claude_CLI
participant analyzer as response_analyzer.sh
participant cb as circuit_breaker.sh
User->>ralph: start CLI (startup)
ralph->>ralph: validate command
ralph->>ralph: check_claude_version() / compare_semver()
alt update enabled
ralph->>npm: check_claude_updates()
npm-->>ralph: latest_version
alt newer available
ralph->>npm: attempt npm update (non-blocking)
npm-->>ralph: update result
end
end
ralph->>claude: launch Claude CLI (capture stderr to file)
claude-->>ralph: stdout (JSON/text)
claude-->>ralph: stderr (to stderr file)
ralph->>analyzer: analyze_response(stdout)
analyzer-->>ralph: analysis JSON (incl. asking_questions, question_count)
ralph->>cb: record_loop_result(analysis)
cb-->>ralph: update circuit state (suppress no-progress if asking_questions)
ralph->>User: loop continues or circuit triggers
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
tests/unit/test_file_protection.bats (1)
187-194: Verify all optional files are excluded, not just one.The comment at line 41 lists four optional files (
.ralph/logs/,.ralph/status.json,.ralph/.call_count,.ralph/.exit_signals), but line 193 only verifies thatstatus.jsonis not inRALPH_REQUIRED_PATHS. Consider checking all optional files for comprehensive coverage.🔍 Proposed fix to verify all optional paths
# Optional paths should NOT be required - [[ ! " ${RALPH_REQUIRED_PATHS[*]} " =~ "status.json" ]] + local optional=(".ralph/logs" "status.json" ".call_count" ".exit_signals") + for opt_path in "${optional[@]}"; do + [[ ! " ${RALPH_REQUIRED_PATHS[*]} " =~ "$opt_path" ]] + done🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/test_file_protection.bats` around lines 187 - 194, The test "RALPH_REQUIRED_PATHS contains all critical paths and excludes optional files" currently only asserts that "status.json" is not required; update the test to also assert that ".ralph/logs/", ".ralph/.call_count", and ".ralph/.exit_signals" are not present in RALPH_REQUIRED_PATHS. Locate the test block (function name or description) and add negative membership checks for each optional path against the RALPH_REQUIRED_PATHS array so all four optional files listed in the comment are verified as excluded.ralph_loop.sh (1)
1490-1491:analysis_exit_codeis captured but never used.This adds dead state without affecting behavior. Either remove it or log/action it when non-zero.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ralph_loop.sh` around lines 1490 - 1491, The variable analysis_exit_code is assigned but never used after calling analyze_response("$output_file" "$loop_count"), so either remove the unused capture or handle non-zero results; modify the call in ralph_loop.sh to either just call analyze_response "$output_file" "$loop_count" without "|| analysis_exit_code=$?" or replace it with a conditional that logs or exits on failure (e.g., if ! analyze_response "$output_file" "$loop_count"; then processLogger/error or exit with $?); reference analyze_response, output_file, loop_count, and analysis_exit_code when making the change.tests/unit/test_cli_modern.bats (2)
532-535: Inline test function diverges from production implementation.The inline
build_claude_commanduses--continue(line 534), but the production code inralph_loop.sh(lines 1127-1140 per relevant snippets) uses--resume "$session_id"instead. The production code explicitly avoids--continuebecause it "can hijack active Claude Code sessions" (Issue#151).While this may not break current tests (since
CLAUDE_USE_CONTINUEis often false in test scenarios), it could mask regressions if session continuity behavior is tested. Consider syncing this inline copy with the production implementation.♻️ Proposed sync with production
# Add session continuity flag - if [[ "$CLAUDE_USE_CONTINUE" == "true" ]]; then - CLAUDE_CMD_ARGS+=("--continue") + # IMPORTANT: Use --resume with explicit session ID instead of --continue + # --continue resumes the "most recent session in current directory" which + # can hijack active Claude Code sessions. --resume with a specific session ID + # ensures we only resume Ralph's own sessions. (Issue `#151`) + if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then + CLAUDE_CMD_ARGS+=("--resume" "$session_id") fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/test_cli_modern.bats` around lines 532 - 535, The test helper's inline build_claude_command currently appends "--continue" when CLAUDE_USE_CONTINUE is true, diverging from production ralph_loop.sh which uses "--resume \"$session_id\"" to avoid hijacking sessions; update the test to mirror production by replacing the "--continue" branch so it constructs CLAUDE_CMD_ARGS with "--resume" and the appropriate session identifier (use the same session variable name used in tests) when CLAUDE_USE_CONTINUE is true, keeping the CLAUDE_CMD_ARGS array usage and any existing conditional logic intact so tests exercise the same session-continuity behavior as ralph_loop.sh.
621-621: Update test assertion if inline function is synced.If the inline
build_claude_commandis updated to use--resumeinstead of--continue(as in production), this assertion at line 621 would need to change accordingly.♻️ Proposed update (if sync is applied)
- [[ "$cmd_string" == *"--continue"* ]] + [[ "$cmd_string" == *"--resume"* ]]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/test_cli_modern.bats` at line 621, The test assertion in tests/unit/test_cli_modern.bats currently checks for "--continue" in the generated command string (variable cmd_string); if the inline function build_claude_command has been synced to use "--resume" in production, update this assertion to look for "--resume" instead of "--continue" (search for references to cmd_string and the test assertion and replace the pattern match [[ "$cmd_string" == *"--continue"* ]] with the equivalent that checks for "--resume"); also scan the file for any other assertions or expectations that reference "--continue" and update them to "--resume" to keep tests consistent with build_claude_command.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@ralph_loop.sh`:
- Around line 1602-1625: The EXIT trap is causing cleanup() to run on normal
successful exits and overwrite the final status; change the signal handling to
only invoke cleanup on actual interruption signals (e.g., SIGINT and SIGTERM)
instead of EXIT. Replace or modify the existing trap line (trap cleanup EXIT) so
that cleanup is registered for SIGINT and SIGTERM (trap 'cleanup' SIGINT
SIGTERM) and ensure normal successful termination from main() is allowed to set
final status without being clobbered by cleanup's reset_session/update_status
logic that references loop_count, reset_session, and update_status.
In `@tests/unit/test_cli_modern.bats`:
- Around line 1453-1461: The grep command in the test "update_exit_signals and
log_analysis_summary use || true for set -e safety" uses single quotes around
'$script' which prevents expansion; update the run line so $script is expanded
inside the bash -c string by using escaped double quotes around the variable
(e.g. run bash -c "grep -c '|| true' <(sed -n '/Analyze the response/,/Get file
change count/p' \"$script\")"), ensuring the inner double quotes are escaped so
the variable $script is interpolated correctly.
---
Nitpick comments:
In `@ralph_loop.sh`:
- Around line 1490-1491: The variable analysis_exit_code is assigned but never
used after calling analyze_response("$output_file" "$loop_count"), so either
remove the unused capture or handle non-zero results; modify the call in
ralph_loop.sh to either just call analyze_response "$output_file" "$loop_count"
without "|| analysis_exit_code=$?" or replace it with a conditional that logs or
exits on failure (e.g., if ! analyze_response "$output_file" "$loop_count"; then
processLogger/error or exit with $?); reference analyze_response, output_file,
loop_count, and analysis_exit_code when making the change.
In `@tests/unit/test_cli_modern.bats`:
- Around line 532-535: The test helper's inline build_claude_command currently
appends "--continue" when CLAUDE_USE_CONTINUE is true, diverging from production
ralph_loop.sh which uses "--resume \"$session_id\"" to avoid hijacking sessions;
update the test to mirror production by replacing the "--continue" branch so it
constructs CLAUDE_CMD_ARGS with "--resume" and the appropriate session
identifier (use the same session variable name used in tests) when
CLAUDE_USE_CONTINUE is true, keeping the CLAUDE_CMD_ARGS array usage and any
existing conditional logic intact so tests exercise the same session-continuity
behavior as ralph_loop.sh.
- Line 621: The test assertion in tests/unit/test_cli_modern.bats currently
checks for "--continue" in the generated command string (variable cmd_string);
if the inline function build_claude_command has been synced to use "--resume" in
production, update this assertion to look for "--resume" instead of "--continue"
(search for references to cmd_string and the test assertion and replace the
pattern match [[ "$cmd_string" == *"--continue"* ]] with the equivalent that
checks for "--resume"); also scan the file for any other assertions or
expectations that reference "--continue" and update them to "--resume" to keep
tests consistent with build_claude_command.
In `@tests/unit/test_file_protection.bats`:
- Around line 187-194: The test "RALPH_REQUIRED_PATHS contains all critical
paths and excludes optional files" currently only asserts that "status.json" is
not required; update the test to also assert that ".ralph/logs/",
".ralph/.call_count", and ".ralph/.exit_signals" are not present in
RALPH_REQUIRED_PATHS. Locate the test block (function name or description) and
add negative membership checks for each optional path against the
RALPH_REQUIRED_PATHS array so all four optional files listed in the comment are
verified as excluded.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
CLAUDE.mdlib/circuit_breaker.shlib/enable_core.shlib/response_analyzer.shralph_loop.shsetup.shtemplates/ralphrc.templatetests/unit/test_circuit_breaker_recovery.batstests/unit/test_cli_modern.batstests/unit/test_exit_detection.batstests/unit/test_file_protection.batstests/unit/test_integrity_check.batstests/unit/test_json_parsing.batstests/unit/test_rate_limiting.batstests/unit/test_session_continuity.bats
💤 Files with no reviewable changes (1)
- tests/unit/test_integrity_check.bats
| cleanup() { | ||
| log_status "INFO" "Ralph loop interrupted. Cleaning up..." | ||
| reset_session "manual_interrupt" | ||
| update_status "$loop_count" "$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")" "interrupted" "stopped" | ||
| exit 0 | ||
| # Reentrancy guard — prevent double execution from EXIT + signal combination | ||
| if [[ "$_CLEANUP_DONE" == "true" ]]; then return; fi | ||
| _CLEANUP_DONE=true | ||
|
|
||
| # Kill background Claude process if still running | ||
| if [[ -n "$_CLAUDE_PID" ]] && kill -0 "$_CLAUDE_PID" 2>/dev/null; then | ||
| kill "$_CLAUDE_PID" 2>/dev/null || true | ||
| wait "$_CLAUDE_PID" 2>/dev/null || true | ||
| fi | ||
| _CLAUDE_PID="" | ||
|
|
||
| # Only perform session/status cleanup if main loop was running | ||
| if [[ $loop_count -gt 0 ]]; then | ||
| log_status "INFO" "Ralph loop interrupted. Cleaning up..." | ||
| reset_session "manual_interrupt" | ||
| update_status "$loop_count" "$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")" "interrupted" "stopped" | ||
| fi | ||
| # No exit here — EXIT trap handles natural termination | ||
| } | ||
|
|
||
| # Set up signal handlers | ||
| trap cleanup SIGINT SIGTERM | ||
| # Set up signal handlers — EXIT covers SIGINT, SIGTERM, set -e errors, and normal exit | ||
| trap cleanup EXIT | ||
|
|
There was a problem hiding this comment.
EXIT trap currently clobbers successful completion state.
On Line 1624, trap cleanup EXIT runs cleanup() on normal exits too. Since Lines 1615-1619 only gate on loop_count > 0, successful runs are rewritten as manual_interrupt/interrupted, overwriting the final status set by main().
💡 Proposed fix
cleanup() {
+ local exit_code=$?
# Reentrancy guard — prevent double execution from EXIT + signal combination
if [[ "$_CLEANUP_DONE" == "true" ]]; then return; fi
_CLEANUP_DONE=true
@@
- # Only perform session/status cleanup if main loop was running
- if [[ $loop_count -gt 0 ]]; then
+ # Only perform interrupt-style cleanup on abnormal termination
+ if [[ $loop_count -gt 0 && $exit_code -ne 0 ]]; then
log_status "INFO" "Ralph loop interrupted. Cleaning up..."
reset_session "manual_interrupt"
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")" "interrupted" "stopped"
fi
# No exit here — EXIT trap handles natural termination
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ralph_loop.sh` around lines 1602 - 1625, The EXIT trap is causing cleanup()
to run on normal successful exits and overwrite the final status; change the
signal handling to only invoke cleanup on actual interruption signals (e.g.,
SIGINT and SIGTERM) instead of EXIT. Replace or modify the existing trap line
(trap cleanup EXIT) so that cleanup is registered for SIGINT and SIGTERM (trap
'cleanup' SIGINT SIGTERM) and ensure normal successful termination from main()
is allowed to set final status without being clobbered by cleanup's
reset_session/update_status logic that references loop_count, reset_session, and
update_status.
| @test "update_exit_signals and log_analysis_summary use || true for set -e safety" { | ||
| local script="${BATS_TEST_DIRNAME}/../../ralph_loop.sh" | ||
|
|
||
| # Both non-critical analysis functions should have || true | ||
| run bash -c "grep -c '|| true' <(sed -n '/Analyze the response/,/Get file change count/p' '$script')" | ||
| assert_success | ||
| # Should find at least 2 (update_exit_signals || true and log_analysis_summary || true) | ||
| [[ "$output" -ge 2 ]] | ||
| } |
There was a problem hiding this comment.
Bug: Single quotes prevent variable expansion in grep command.
The $script variable inside single quotes at line 1457 won't be expanded. This will cause the test to fail or look for a literal $script file.
🐛 Proposed fix
- run bash -c "grep -c '|| true' <(sed -n '/Analyze the response/,/Get file change count/p' '$script')"
+ run bash -c "grep -c '|| true' <(sed -n '/Analyze the response/,/Get file change count/p' \"$script\")"Note: The escaped double quotes (\") are needed because this is inside a bash -c "..." string.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @test "update_exit_signals and log_analysis_summary use || true for set -e safety" { | |
| local script="${BATS_TEST_DIRNAME}/../../ralph_loop.sh" | |
| # Both non-critical analysis functions should have || true | |
| run bash -c "grep -c '|| true' <(sed -n '/Analyze the response/,/Get file change count/p' '$script')" | |
| assert_success | |
| # Should find at least 2 (update_exit_signals || true and log_analysis_summary || true) | |
| [[ "$output" -ge 2 ]] | |
| } | |
| `@test` "update_exit_signals and log_analysis_summary use || true for set -e safety" { | |
| local script="${BATS_TEST_DIRNAME}/../../ralph_loop.sh" | |
| # Both non-critical analysis functions should have || true | |
| run bash -c "grep -c '|| true' <(sed -n '/Analyze the response/,/Get file change count/p' \"$script\")" | |
| assert_success | |
| # Should find at least 2 (update_exit_signals || true and log_analysis_summary || true) | |
| [[ "$output" -ge 2 ]] | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_cli_modern.bats` around lines 1453 - 1461, The grep command
in the test "update_exit_signals and log_analysis_summary use || true for set -e
safety" uses single quotes around '$script' which prevents expansion; update the
run line so $script is expanded inside the bash -c string by using escaped
double quotes around the variable (e.g. run bash -c "grep -c '|| true' <(sed -n
'/Analyze the response/,/Get file change count/p' \"$script\")"), ensuring the
inner double quotes are escaped so the variable $script is interpolated
correctly.
…termination (frankbria#190) Layer 1: Add || true to 22 non-fatal function calls in main() and CLI parsing sections (reset_session, update_status, init_session_tracking, init_call_tracking, update_session_last_used, show_circuit_status, reset_circuit_breaker). Layer 2: Fix log_circuit_transition data corruption bug — jq failure on corrupted history file would overwrite it with empty string. Now falls back to single-entry array preserving the current transition. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
ralph_loop.sh (1)
1603-1624:⚠️ Potential issue | 🟠 Major
EXITtrap still overwrites successful completion as an interrupt.Line 1615 only checks
loop_count > 0, so normal successful exits still run interrupt cleanup and rewrite status/session state.🔧 Proposed fix
cleanup() { + local exit_code=$? # Reentrancy guard — prevent double execution from EXIT + signal combination if [[ "$_CLEANUP_DONE" == "true" ]]; then return; fi _CLEANUP_DONE=true @@ - # Only perform session/status cleanup if main loop was running - if [[ $loop_count -gt 0 ]]; then + # Only perform interrupt cleanup when exiting abnormally + if [[ $loop_count -gt 0 && $exit_code -ne 0 ]]; then log_status "INFO" "Ralph loop interrupted. Cleaning up..." reset_session "manual_interrupt" update_status "$loop_count" "$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")" "interrupted" "stopped" fi }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ralph_loop.sh` around lines 1603 - 1624, The EXIT trap's cleanup function is treating normal successful completion as an interrupt because it only checks loop_count; add a completion flag (e.g. _RALPH_COMPLETED or similar) that the main loop sets right before a normal/successful exit, then update cleanup (the cleanup function that the trap calls) to skip the interrupt-specific actions (reset_session and update_status) when that completion flag is set; keep the existing loop_count check but require both loop_count -gt 0 AND the completion flag to be false before logging "Ralph loop interrupted" and calling reset_session/update_status so trap cleanup only rewrites status on real interrupts, not successful exits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@CLAUDE.md`:
- Line 180: The document contains inconsistent hard-coded test counts around the
"Run all tests" header and the test-results table; reconcile them by choosing a
single source of truth (either compute the total from the table rows or remove
the hard-coded total), then update the "Run all tests" header value and every
other occurrence of the total so they match the computed sum of the table rows
(or remove all totals so only individual row counts remain); make sure the table
rows themselves accurately sum to the chosen total and update any related
sentence that references the total.
In `@ralph_loop.sh`:
- Around line 1489-1496: The code currently ignores a failing analyze_response
call (analysis_exit_code is set but not acted upon), which can let
update_exit_signals and log_analysis_summary operate on stale .response_analysis
state; modify the control flow in the block that calls analyze_response,
update_exit_signals, and log_analysis_summary so that if analyze_response
returns non-zero you either (a) skip calling update_exit_signals and
log_analysis_summary and propagate or handle the error immediately, or (b)
refresh/clear the .response_analysis state before calling them; specifically
change the sequence around the analyze_response invocation in the function that
declares analysis_exit_code so that analyze_response's exit code is checked and
handled (using analyze_response, update_exit_signals, and log_analysis_summary
identifiers to locate the code).
- Around line 651-657: In check_claude_updates(), avoid hard-exit when npm is
missing by first checking for the npm binary (e.g., if ! command -v npm
>/dev/null; then log_status "INFO" "npm not found; skipping Claude CLI update
check" and return 0), and guard the command substitution so a failing npm
invocation doesn’t propagate under set -e (for example run the npm view call in
a subshell with its exit ignored or capture its exit/status and set
latest_version to empty on failure); update references to latest_version
assignment and any early returns in check_claude_updates() accordingly so
missing or failing npm simply logs and returns 0 rather than terminating the
script.
---
Duplicate comments:
In `@ralph_loop.sh`:
- Around line 1603-1624: The EXIT trap's cleanup function is treating normal
successful completion as an interrupt because it only checks loop_count; add a
completion flag (e.g. _RALPH_COMPLETED or similar) that the main loop sets right
before a normal/successful exit, then update cleanup (the cleanup function that
the trap calls) to skip the interrupt-specific actions (reset_session and
update_status) when that completion flag is set; keep the existing loop_count
check but require both loop_count -gt 0 AND the completion flag to be false
before logging "Ralph loop interrupted" and calling reset_session/update_status
so trap cleanup only rewrites status on real interrupts, not successful exits.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
CLAUDE.mdlib/circuit_breaker.shralph_loop.shtests/unit/test_circuit_breaker_recovery.batstests/unit/test_cli_modern.bats
| ### Running Tests | ||
| ```bash | ||
| # Run all tests (568 tests) | ||
| # Run all tests (554 tests) |
There was a problem hiding this comment.
Test-count values are inconsistent in this doc.
Line 180 says 554, Line 537 says 559, and the table rows currently sum to a different value. Please normalize this to a single source of truth (or remove hard-coded totals).
📝 Suggested docs-stability tweak
-# Run all tests (554 tests)
+# Run all tests
npm test
@@
-### Test Files (559 tests total)
+### Test FilesAlso applies to: 537-559
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@CLAUDE.md` at line 180, The document contains inconsistent hard-coded test
counts around the "Run all tests" header and the test-results table; reconcile
them by choosing a single source of truth (either compute the total from the
table rows or remove the hard-coded total), then update the "Run all tests"
header value and every other occurrence of the total so they match the computed
sum of the table rows (or remove all totals so only individual row counts
remain); make sure the table rows themselves accurately sum to the chosen total
and update any related sentence that references the total.
| # Query latest version from npm registry | ||
| local latest_version | ||
| latest_version=$(npm view @anthropic-ai/claude-code version 2>/dev/null) | ||
| if [[ -z "$latest_version" ]]; then | ||
| log_status "INFO" "Could not check for Claude CLI updates (npm registry unreachable)" | ||
| return 0 | ||
| fi |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find ralph_loop.sh
script=$(fd -a ralph_loop.sh | head -1)
echo "Script location: $script"
# Check if it exists
if [[ ! -f "$script" ]]; then
echo "ERROR: ralph_loop.sh not found"
exit 1
fi
# Extract the check_claude_updates function
echo -e "\n=== check_claude_updates() function ==="
sed -n '/^check_claude_updates()/,/^}/p' "$script" | head -60Repository: frankbria/ralph-claude-code
Length of output: 1892
🏁 Script executed:
#!/bin/bash
# Check current state of lines 651-657 specifically
script=$(fd -a ralph_loop.sh | head -1)
echo "=== Lines 640-670 of ralph_loop.sh ==="
sed -n '640,670p' "$script" | nl -ba -v 640Repository: frankbria/ralph-claude-code
Length of output: 169
🏁 Script executed:
#!/bin/bash
# Check if set -e is active when check_claude_updates is called
script=$(fd -a ralph_loop.sh | head -1)
echo "=== Checking set -e usage in script ==="
grep -n "set -e\|set +e" "$script" | head -20Repository: frankbria/ralph-claude-code
Length of output: 499
🏁 Script executed:
#!/bin/bash
# Verify the actual impact of npm command failure under set -e
echo "=== Testing npm failure behavior under set -e ==="
bash -c 'set -e; x=$(npm view 2>/dev/null); echo "After npm call, x=[$x]"; echo "REACHED_HERE"'
echo "Exit code: $?"Repository: frankbria/ralph-claude-code
Length of output: 133
🏁 Script executed:
#!/bin/bash
# Check where check_claude_updates is called
script=$(fd -a ralph_loop.sh | head -1)
echo "=== Calls to check_claude_updates ==="
grep -n "check_claude_updates" "$script"Repository: frankbria/ralph-claude-code
Length of output: 167
🏁 Script executed:
#!/bin/bash
# Check if npm is documented as a requirement
echo "=== Checking README and CLAUDE.md for npm requirement ==="
for file in README.md CLAUDE.md readme.md; do
if [[ -f "$file" ]]; then
echo "=== $file ==="
grep -i "npm\|nodejs\|node.js\|install\|require" "$file" | head -20
fi
doneRepository: frankbria/ralph-claude-code
Length of output: 3112
🏁 Script executed:
#!/bin/bash
# Check what context check_claude_updates is called in
script=$(fd -a ralph_loop.sh | head -1)
echo "=== Context around check_claude_updates call ==="
grep -B5 -A5 "check_claude_updates" "$script" | grep -v "^check_claude_updates()"Repository: frankbria/ralph-claude-code
Length of output: 776
Guard npm command availability in check_claude_updates() to prevent startup hard-exit.
At line 653, the command substitution latest_version=$(npm view ...) will terminate the entire script under set -e when npm is not available, regardless of stderr redirection. This breaks the intended non-blocking startup behavior — the function is designed to gracefully handle missing updates but currently fails if the npm binary itself doesn't exist.
The fix requires two changes:
- Check
npmavailability before attempting the command - Guard the assignment with proper error handling to prevent subshell exit propagation
Proposed fix
check_claude_updates() {
if [[ "${CLAUDE_AUTO_UPDATE:-true}" != "true" ]]; then
return 0
fi
+ if ! command -v npm >/dev/null 2>&1; then
+ log_status "INFO" "Skipping Claude CLI auto-update check (npm not available)"
+ return 0
+ fi
local installed_version
installed_version=$($CLAUDE_CODE_CMD --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
if [[ -z "$installed_version" ]]; then
return 0
fi
# Query latest version from npm registry
local latest_version
- latest_version=$(npm view `@anthropic-ai/claude-code` version 2>/dev/null)
- if [[ -z "$latest_version" ]]; then
+ if ! latest_version=$(npm view `@anthropic-ai/claude-code` version 2>/dev/null) || [[ -z "$latest_version" ]]; then
log_status "INFO" "Could not check for Claude CLI updates (npm registry unreachable)"
return 0
fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ralph_loop.sh` around lines 651 - 657, In check_claude_updates(), avoid
hard-exit when npm is missing by first checking for the npm binary (e.g., if !
command -v npm >/dev/null; then log_status "INFO" "npm not found; skipping
Claude CLI update check" and return 0), and guard the command substitution so a
failing npm invocation doesn’t propagate under set -e (for example run the npm
view call in a subshell with its exit ignored or capture its exit/status and set
latest_version to empty on failure); update references to latest_version
assignment and any early returns in check_claude_updates() accordingly so
missing or failing npm simply logs and returns 0 rather than terminating the
script.
| local analysis_exit_code=0 | ||
| analyze_response "$output_file" "$loop_count" || analysis_exit_code=$? | ||
|
|
||
| # Update exit signals based on analysis | ||
| update_exit_signals | ||
| update_exit_signals || true | ||
|
|
||
| # Log analysis summary | ||
| log_analysis_summary | ||
| log_analysis_summary || true |
There was a problem hiding this comment.
Handle analyze_response failure before using derived analysis signals.
analysis_exit_code is captured but ignored. If analysis fails, downstream calls can consume stale .response_analysis state and skew exit/circuit decisions.
🔧 Proposed fix
log_status "INFO" "🔍 Analyzing Claude Code response..."
local analysis_exit_code=0
analyze_response "$output_file" "$loop_count" || analysis_exit_code=$?
-
- # Update exit signals based on analysis
- update_exit_signals || true
-
- # Log analysis summary
- log_analysis_summary || true
+ if [[ $analysis_exit_code -eq 0 ]]; then
+ # Update exit signals based on analysis
+ update_exit_signals || true
+ # Log analysis summary
+ log_analysis_summary || true
+ else
+ log_status "WARN" "Response analysis failed (exit $analysis_exit_code); skipping analysis-derived signal updates"
+ rm -f "$RESPONSE_ANALYSIS_FILE"
+ fi🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 1490-1490: analysis_exit_code appears unused. Verify use (or export if used externally).
(SC2034)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ralph_loop.sh` around lines 1489 - 1496, The code currently ignores a failing
analyze_response call (analysis_exit_code is set but not acted upon), which can
let update_exit_signals and log_analysis_summary operate on stale
.response_analysis state; modify the control flow in the block that calls
analyze_response, update_exit_signals, and log_analysis_summary so that if
analyze_response returns non-zero you either (a) skip calling
update_exit_signals and log_analysis_summary and propagate or handle the error
immediately, or (b) refresh/clear the .response_analysis state before calling
them; specifically change the sequence around the analyze_response invocation in
the function that declares analysis_exit_code so that analyze_response's exit
code is checked and handled (using analyze_response, update_exit_signals, and
log_analysis_summary identifiers to locate the code).
Closed: Superseded by #208 (
refactor/remove-set-e), which removesset -eentirely — making the bare call guards in this PR unnecessary.