fix(loop): respect Claude's EXIT_SIGNAL when checking completion indicators (#90)

* fix(loop): respect Claude's EXIT_SIGNAL when checking completion indicators

The should_exit_gracefully() function was exiting prematurely based solely
on completion_indicators heuristics, ignoring Claude's explicit EXIT_SIGNAL
in the RALPH_STATUS block. This caused premature exits during productive
iterations when Claude reported work in progress.

Changes:
- ralph_loop.sh: Added dual-condition check requiring BOTH completion
  indicators >= 2 AND exit_signal == true before exiting
- response_analyzer.sh: Added explicit_exit_signal_found flag to prevent
  natural language heuristics from overriding Claude's explicit intent
- Added 14 new tests (10 unit + 4 integration) covering EXIT_SIGNAL behavior

Decision matrix:
| indicators >= 2 | EXIT_SIGNAL | Result |
|-----------------|-------------|--------|
| true            | true        | Exit   |
| true            | false       | Continue |
| true            | missing     | Continue (defaults to false) |
| false           | true        | Continue (threshold not met) |

Fixes premature exit bug during productive development iterations.

* Update lib/response_analyzer.sh

Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>

* test(exit): add STATUS=COMPLETE vs EXIT_SIGNAL=false conflict test

docs(exit): update CLAUDE.md with EXIT_SIGNAL gate documentation

- Added test for STATUS=COMPLETE with EXIT_SIGNAL=false conflict
  (EXIT_SIGNAL takes precedence, allowing phase completion without loop exit)
- Updated "Intelligent Exit Detection" section with dual-condition explanation
- Added "Completion Indicators with EXIT_SIGNAL Gate" section with decision table
- Documented conflict resolution behavior and implementation details

---------

Co-authored-by: Test User <test@example.com>
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
This commit is contained in:
Frank Bria 2026-01-12 22:07:24 -07:00 committed by GitHub
parent 3e8d3fdcde
commit aca3670cc7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 539 additions and 14 deletions

View file

@ -165,11 +165,33 @@ Each loop iteration injects context via `build_loop_context()`:
- Disable with `--no-continue` for isolated iterations
### Intelligent Exit Detection
The loop automatically exits when it detects project completion through:
- Multiple consecutive "done" signals from Claude Code
- Too many test-only loops indicating feature completeness
The loop uses a dual-condition check to prevent premature exits during productive iterations:
**Exit requires BOTH conditions:**
1. `recent_completion_indicators >= 2` (heuristic-based detection from natural language patterns)
2. Claude's explicit `EXIT_SIGNAL: true` in the RALPH_STATUS block
The `EXIT_SIGNAL` value is read from `.response_analysis` (at `.analysis.exit_signal`) which is populated by `response_analyzer.sh` from Claude's RALPH_STATUS output block.
**Other exit conditions (checked before completion indicators):**
- Multiple consecutive "done" signals from Claude Code (`done_signals >= 2`)
- Too many test-only loops indicating feature completeness (`test_loops >= 3`)
- All items in @fix_plan.md marked as completed
- Strong completion indicators in responses
**Example behavior when EXIT_SIGNAL is false:**
```
Loop 5: Claude outputs "Phase complete, moving to next feature"
→ completion_indicators: 3 (high confidence from patterns)
→ EXIT_SIGNAL: false (Claude explicitly says more work needed)
→ Result: CONTINUE (respects Claude's explicit intent)
Loop 8: Claude outputs "All tasks complete, project ready"
→ completion_indicators: 4
→ EXIT_SIGNAL: true (Claude confirms project is done)
→ Result: EXIT with "project_complete"
```
**Rationale:** Natural language patterns like "done" or "complete" can trigger false positives during productive work (e.g., "feature done, moving to tests"). By requiring Claude's explicit EXIT_SIGNAL confirmation, Ralph avoids exiting mid-iteration when Claude is still working.
## CI/CD Pipeline
@ -256,6 +278,33 @@ Ralph uses multiple mechanisms to detect when to exit:
- `TEST_PERCENTAGE_THRESHOLD=30%` - Flag if testing dominates recent loops
- Completion detection via @fix_plan.md checklist items
### Completion Indicators with EXIT_SIGNAL Gate
The `completion_indicators` exit condition requires dual verification:
| completion_indicators | EXIT_SIGNAL | .response_analysis | Result |
|-----------------------|-------------|-------------------|--------|
| >= 2 | `true` | exists | **Exit** ("project_complete") |
| >= 2 | `false` | exists | **Continue** (Claude still working) |
| >= 2 | N/A | missing | **Continue** (defaults to false) |
| >= 2 | N/A | malformed | **Continue** (defaults to false) |
| < 2 | `true` | exists | **Continue** (threshold not met) |
**Implementation** (`ralph_loop.sh:312-327`):
```bash
local claude_exit_signal="false"
if [[ -f ".response_analysis" ]]; then
claude_exit_signal=$(jq -r '.analysis.exit_signal // false' ".response_analysis" 2>/dev/null || echo "false")
fi
if [[ $recent_completion_indicators -ge 2 ]] && [[ "$claude_exit_signal" == "true" ]]; then
echo "project_complete"
return 0
fi
```
**Conflict Resolution:** When `STATUS: COMPLETE` but `EXIT_SIGNAL: false` in RALPH_STATUS, the explicit EXIT_SIGNAL takes precedence. This allows Claude to mark a phase complete while indicating more phases remain.
### Circuit Breaker Thresholds
- `CB_NO_PROGRESS_THRESHOLD=3` - Open circuit after 3 loops with no file changes
- `CB_SAME_ERROR_THRESHOLD=5` - Open circuit after 5 loops with repeated errors