Skip to content

Commit 4e86bf4

Browse files
Andymendez100claude
andcommitted
fix(loop): fix set -e crash, monitor counter, daily cap and not-logged-in detection
- Fix set -e killing script when execute_claude_code returns non-zero: use `execute_claude_code "$loop_count" || exec_result=$?` pattern - Fix monitor call counter display: use increment_call_counter() to persist immediately instead of deferring to successful execution only - Add 'hit your limit' to Layer 3 text fallback pattern (#198) - Add Layer 4 detection: 'not logged in' triggers rotation when ACCOUNT_ROTATION=true - Add daily cap fixture, 'hit your limit' test, and two 'not logged in' tests - Update CLAUDE.md test counts (630 total) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent df6b3fe commit 4e86bf4

File tree

4 files changed

+74
-12
lines changed

4 files changed

+74
-12
lines changed

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ tmux attach -t <session-name>
194194

195195
### Running Tests
196196
```bash
197-
# Run all tests (627 tests)
197+
# Run all tests (630 tests)
198198
npm test
199199

200200
# Run specific test suites
@@ -538,13 +538,13 @@ Ralph uses a multi-layered strategy to prevent Claude from accidentally deleting
538538

539539
## Test Suite
540540

541-
### Test Files (627 tests total)
541+
### Test Files (630 tests total)
542542

543543
| File | Tests | Description |
544544
|------|-------|-------------|
545545
| `test_circuit_breaker_recovery.bats` | 19 | Cooldown timer, auto-reset, parse_iso_to_epoch, CLI flag (Issue #160) |
546546
| `test_cli_parsing.bats` | 35 | CLI argument parsing for all flags + monitor parameter forwarding |
547-
| `test_cli_modern.bats` | 66 | Modern CLI commands (Phase 1.1) + build_claude_command fix + live mode text format fix (#164) + errexit pipeline guard (#175) + ALLOWED_TOOLS tightening (#149) + API limit false positive detection (#183) + Claude CLI command validation (#97) |
547+
| `test_cli_modern.bats` | 69 | Modern CLI commands (Phase 1.1) + build_claude_command fix + live mode text format fix (#164) + errexit pipeline guard (#175) + ALLOWED_TOOLS tightening (#149) + API limit false positive detection (#183) + Claude CLI command validation (#97) + daily cap + account rotation Layer 4 detection (#81) |
548548
| `test_json_parsing.bats` | 52 | JSON output format parsing + Claude CLI format + session management + array format |
549549
| `test_session_continuity.bats` | 44 | Session lifecycle management + expiration + circuit breaker integration + issue #91 fix |
550550
| `test_exit_detection.bats` | 53 | Exit signal detection + EXIT_SIGNAL-based completion indicators + progress detection |

ralph_loop.sh

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,8 +1089,10 @@ execute_claude_code() {
10891089
local timestamp=$(date '+%Y-%m-%d_%H-%M-%S')
10901090
local output_file="$LOG_DIR/claude_output_${timestamp}.log"
10911091
local loop_count=$1
1092-
local calls_made=$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")
1093-
calls_made=$((calls_made + 1))
1092+
# Persist call count immediately so monitor dashboard reflects actual usage
1093+
# API calls consume rate limits regardless of success/failure
1094+
local calls_made
1095+
calls_made=$(increment_call_counter)
10941096

10951097
# Fix #141: Capture git HEAD SHA at loop start to detect commits as progress
10961098
# Store in file for access by progress detection after Claude execution
@@ -1395,9 +1397,6 @@ EOF
13951397
fi
13961398

13971399
if [ $exit_code -eq 0 ]; then
1398-
# Only increment counter on successful execution
1399-
echo "$calls_made" > "$CALL_COUNT_FILE"
1400-
14011400
# Clear progress file
14021401
echo '{"status": "completed", "timestamp": "'$(date '+%Y-%m-%d %H:%M:%S')'"}' > "$PROGRESS_FILE"
14031402

@@ -1512,11 +1511,17 @@ EOF
15121511

15131512
# Layer 3: Filtered text fallback — only check tail, excluding tool result lines
15141513
# Filters out type:user, tool_result, and tool_use_id lines which contain echoed file content
1515-
if tail -30 "$output_file" 2>/dev/null | grep -vE '"type"\s*:\s*"user"' | grep -v '"tool_result"' | grep -v '"tool_use_id"' | grep -qi "5.*hour.*limit\|limit.*reached.*try.*back\|usage.*limit.*reached"; then
1514+
if tail -30 "$output_file" 2>/dev/null | grep -vE '"type"\s*:\s*"user"' | grep -v '"tool_result"' | grep -v '"tool_use_id"' | grep -qi "5.*hour.*limit\|limit.*reached.*try.*back\|usage.*limit.*reached\|hit your limit"; then
15161515
log_status "ERROR" "🚫 Claude API 5-hour usage limit reached"
15171516
return 2 # API limit detected via text fallback
15181517
fi
15191518

1519+
# Layer 4: Account not logged in — treat as rotatable error when rotation is configured
1520+
if [[ "${ACCOUNT_ROTATION:-false}" == "true" ]] && grep -qi "not logged in\|please run /login" "$output_file" 2>/dev/null; then
1521+
log_status "ERROR" "🔐 Claude account not logged in — triggering rotation"
1522+
return 2 # Use same code as API limit so rotation logic fires
1523+
fi
1524+
15201525
log_status "ERROR" "❌ Claude Code execution failed, check: $output_file"
15211526
return 1
15221527
fi
@@ -1721,8 +1726,9 @@ main() {
17211726
update_status "$loop_count" "$calls_made" "executing" "running"
17221727

17231728
# Execute Claude Code
1724-
execute_claude_code "$loop_count"
1725-
local exec_result=$?
1729+
# Use || to prevent set -e from exiting on non-zero return codes (2=API limit, 3=circuit breaker)
1730+
local exec_result=0
1731+
execute_claude_code "$loop_count" || exec_result=$?
17261732

17271733
if [ $exec_result -eq 0 ]; then
17281734
update_status "$loop_count" "$(cat "$CALL_COUNT_FILE")" "completed" "success"

tests/helpers/fixtures.bash

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,3 +398,21 @@ create_sample_stream_json_with_prompt_echo() {
398398
{"type":"result","subtype":"rate_limit_event","rate_limit_event":{"type":"rate_limit","status":"allowed","remaining":42}}
399399
EOF
400400
}
401+
402+
# Sample output for daily usage cap: "You've hit your limit · resets 6pm"
403+
# This is the result JSON line after session extraction in live mode.
404+
create_sample_daily_limit_result() {
405+
local file=${1:-"claude_output.log"}
406+
cat > "$file" << 'EOF'
407+
{"type":"result","subtype":"error","is_error":true,"result":"You've hit your limit · resets 6pm"}
408+
EOF
409+
}
410+
411+
# Sample output when Claude account is not logged in.
412+
# Occurs when CLAUDE_CONFIG_DIR points to an account that hasn't run /login.
413+
create_sample_not_logged_in_result() {
414+
local file=${1:-"claude_output.log"}
415+
cat > "$file" << 'EOF'
416+
{"type":"result","subtype":"success","is_error":true,"duration_ms":34,"result":"Not logged in · Please run /login","session_id":"abc123"}
417+
EOF
418+
}

tests/unit/test_cli_modern.bats

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,12 @@ _detect_api_limit() {
10511051
fi
10521052

10531053
# Layer 3: Filtered text fallback
1054-
if tail -30 "$output_file" 2>/dev/null | grep -vE '"type"\s*:\s*"user"' | grep -v '"tool_result"' | grep -v '"tool_use_id"' | grep -qi "5.*hour.*limit\|limit.*reached.*try.*back\|usage.*limit.*reached"; then
1054+
if tail -30 "$output_file" 2>/dev/null | grep -vE '"type"\s*:\s*"user"' | grep -v '"tool_result"' | grep -v '"tool_use_id"' | grep -qi "5.*hour.*limit\|limit.*reached.*try.*back\|usage.*limit.*reached\|hit your limit"; then
1055+
return 2
1056+
fi
1057+
1058+
# Layer 4: Account not logged in — rotatable error when rotation is configured
1059+
if [[ "${ACCOUNT_ROTATION:-false}" == "true" ]] && grep -qi "not logged in\|please run /login" "$output_file" 2>/dev/null; then
10551060
return 2
10561061
fi
10571062

@@ -1090,6 +1095,39 @@ _detect_api_limit() {
10901095
[[ "$status" -eq 1 ]]
10911096
}
10921097

1098+
@test "behavioral: daily usage cap 'hit your limit' message returns 2 not 1" {
1099+
# Scenario: Claude hit the daily usage cap (not 5-hour API limit)
1100+
# Error: "You've hit your limit · resets 6pm" with is_error:true
1101+
# This is the result JSON after session extraction in live mode
1102+
local output_file="$TEST_DIR/claude_output_daily_limit.log"
1103+
create_sample_daily_limit_result "$output_file"
1104+
1105+
# exit_code=1 (non-zero — Claude exited with error) — should detect as API limit
1106+
run _detect_api_limit 1 "$output_file"
1107+
[[ "$status" -eq 2 ]]
1108+
}
1109+
1110+
@test "behavioral: 'not logged in' with ACCOUNT_ROTATION=true returns 2 (triggers rotation)" {
1111+
# Scenario: CLAUDE_CONFIG_DIR points to an account that hasn't run /login yet.
1112+
# With rotation enabled, this should rotate to the next account (return 2)
1113+
# rather than treating it as a generic failure (return 1).
1114+
local output_file="$TEST_DIR/claude_output_not_logged_in.log"
1115+
create_sample_not_logged_in_result "$output_file"
1116+
1117+
ACCOUNT_ROTATION=true run _detect_api_limit 1 "$output_file"
1118+
[[ "$status" -eq 2 ]]
1119+
}
1120+
1121+
@test "behavioral: 'not logged in' with ACCOUNT_ROTATION=false returns 1 (not rotatable)" {
1122+
# Scenario: Without rotation configured, a 'not logged in' error is a regular failure.
1123+
# The user needs to fix their auth manually — no automatic rotation.
1124+
local output_file="$TEST_DIR/claude_output_not_logged_in.log"
1125+
create_sample_not_logged_in_result "$output_file"
1126+
1127+
ACCOUNT_ROTATION=false run _detect_api_limit 1 "$output_file"
1128+
[[ "$status" -eq 1 ]]
1129+
}
1130+
10931131
# --- Claude Code Command Validation Tests (Issue #97) ---
10941132

10951133
@test "validate_claude_command succeeds for command that exists" {

0 commit comments

Comments
 (0)