fix: progress detection improvements (#141, #144) (#158)

* fix: progress detection improvements (#141, #144)

- Fix checkbox regex to exclude date entries like [2026-01-29] (#144)
- Add git commit detection: files changed in commits now count as progress (#141)
- Add 13 regression tests for progress detection and checkbox regex
- Update test count from 452 to 465

Fixes #141, Fixes #144

* fix(test): increase grep context to capture echo -1 line

* fix: count both committed and working tree changes as progress

When commits are made, now unions:
- Files changed in commits (loop_start_sha..current_sha)
- Unstaged changes (git diff HEAD)
- Staged changes (git diff --cached)

Uses sort -u to deduplicate before counting.

---------

Co-authored-by: Test User <test@example.com>
This commit is contained in:
Frank Bria 2026-02-02 11:30:17 -07:00 committed by GitHub
parent 3366ac64d8
commit aa753c9158
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 474 additions and 28 deletions

View file

@ -432,7 +432,7 @@ Ralph uses advanced error detection with two-stage filtering to eliminate false
| `test_cli_modern.bats` | 29 | Modern CLI commands (Phase 1.1) + build_claude_command fix |
| `test_json_parsing.bats` | 45 | JSON output format parsing + Claude CLI format + session management + array format |
| `test_session_continuity.bats` | 28 | Session lifecycle management + circuit breaker integration + issue #91 fix |
| `test_exit_detection.bats` | 35 | Exit signal detection + EXIT_SIGNAL-based completion indicators |
| `test_exit_detection.bats` | 53 | Exit signal detection + EXIT_SIGNAL-based completion indicators + progress detection |
| `test_rate_limiting.bats` | 15 | Rate limiting behavior |
| `test_loop_execution.bats` | 20 | Integration tests |
| `test_edge_cases.bats` | 20 | Edge case handling |

View file

@ -3,7 +3,7 @@
[![CI](https://github.com/frankbria/ralph-claude-code/actions/workflows/test.yml/badge.svg)](https://github.com/frankbria/ralph-claude-code/actions/workflows/test.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
![Version](https://img.shields.io/badge/version-0.11.4-blue)
![Tests](https://img.shields.io/badge/tests-452%20passing-green)
![Tests](https://img.shields.io/badge/tests-465%20passing-green)
[![GitHub Issues](https://img.shields.io/github/issues/frankbria/ralph-claude-code)](https://github.com/frankbria/ralph-claude-code/issues)
[![Mentioned in Awesome Claude Code](https://awesome.re/mentioned-badge.svg)](https://github.com/hesreallyhim/awesome-claude-code)
[![Follow on X](https://img.shields.io/twitter/follow/FrankBria18044?style=social)](https://x.com/FrankBria18044)
@ -18,7 +18,7 @@ Ralph is an implementation of the Geoffrey Huntley's technique for Claude Code t
**Version**: v0.11.4 - Active Development
**Core Features**: Working and tested
**Test Coverage**: 452 tests, 100% pass rate
**Test Coverage**: 465 tests, 100% pass rate
### What's Working Now
- Autonomous development loops with intelligent exit detection
@ -43,6 +43,8 @@ Ralph is an implementation of the Geoffrey Huntley's technique for Claude Code t
### Recent Improvements
**v0.11.4 - Bug Fixes & Compatibility** (latest)
- Fixed progress detection: Git commits within a loop now count as progress (#141)
- Fixed checkbox regex: Date entries `[2026-01-29]` no longer counted as checkboxes (#144)
- Fixed session hijacking: Use `--resume <session_id>` instead of `--continue` (#151)
- Fixed EXIT_SIGNAL override: `STATUS: COMPLETE` with `EXIT_SIGNAL: false` now continues working (#146)
- Fixed ralph-import hanging indefinitely (added `--print` flag for non-interactive mode)
@ -50,6 +52,7 @@ Ralph is an implementation of the Geoffrey Huntley's technique for Claude Code t
- Fixed cross-platform date commands for macOS with Homebrew coreutils
- Added configurable circuit breaker thresholds via environment variables (#99)
- Added tmux support for non-zero `base-index` configurations
- Added 13 new regression tests for progress detection and checkbox regex
**v0.11.3 - Live Streaming & Beads Fix**
- Added live streaming output mode with `--live` flag for real-time Claude Code visibility (#125)
@ -608,7 +611,7 @@ If you want to run the test suite:
# Install BATS testing framework
npm install -g bats bats-support bats-assert
# Run all tests (452 tests)
# Run all tests (465 tests)
npm test
# Run specific test suites
@ -633,7 +636,7 @@ bats tests/integration/test_installation.bats
```
Current test status:
- **452 tests** across 15 test files
- **465 tests** across 15 test files
- **100% pass rate** (452/452 passing)
- Comprehensive unit and integration tests
- Specialized tests for JSON parsing, CLI flags, circuit breaker, EXIT_SIGNAL behavior, enable wizard, and installation workflows
@ -740,7 +743,7 @@ cd ralph-claude-code
# Install dependencies and run tests
npm install
npm test # All 452 tests must pass
npm test # All 465 tests must pass
```
### Priority Contribution Areas

View file

@ -204,11 +204,13 @@ should_exit_gracefully() {
fi
# 4. Check fix_plan.md for completion
# Bug #3 Fix: Support indented markdown checkboxes with [[:space:]]* pattern
# Fix #144: Only match valid markdown checkboxes, not date entries like [2026-01-29]
# Valid patterns: "- [ ]" (uncompleted) and "- [x]" or "- [X]" (completed)
if [[ -f "$RALPH_DIR/fix_plan.md" ]]; then
local total_items=$(grep -cE "^[[:space:]]*- \[" "$RALPH_DIR/fix_plan.md" 2>/dev/null || echo "0")
local completed_items=$(grep -cE "^[[:space:]]*- \[x\]" "$RALPH_DIR/fix_plan.md" 2>/dev/null || echo "0")
local uncompleted_items=$(grep -cE "^[[:space:]]*- \[ \]" "$RALPH_DIR/fix_plan.md" 2>/dev/null || echo "0")
local completed_items=$(grep -cE "^[[:space:]]*- \[[xX]\]" "$RALPH_DIR/fix_plan.md" 2>/dev/null || echo "0")
local total_items=$((uncompleted_items + completed_items))
if [[ $total_items -gt 0 ]] && [[ $completed_items -eq $total_items ]]; then
log_status "WARN" "Exit condition: All fix_plan.md items completed ($completed_items/$total_items)"
echo "plan_complete"

View file

@ -358,8 +358,37 @@ analyze_response() {
fi
# Check for file changes via git (supplements JSON data)
# Fix #141: Detect both uncommitted changes AND committed changes
if command -v git &>/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then
local git_files=$(git diff --name-only 2>/dev/null | wc -l)
local git_files=0
local loop_start_sha=""
local current_sha=""
if [[ -f "$RALPH_DIR/.loop_start_sha" ]]; then
loop_start_sha=$(cat "$RALPH_DIR/.loop_start_sha" 2>/dev/null || echo "")
fi
current_sha=$(git rev-parse HEAD 2>/dev/null || echo "")
# Check if commits were made (HEAD changed)
if [[ -n "$loop_start_sha" && -n "$current_sha" && "$loop_start_sha" != "$current_sha" ]]; then
# Commits were made - count union of committed files AND working tree changes
git_files=$(
{
git diff --name-only "$loop_start_sha" "$current_sha" 2>/dev/null
git diff --name-only HEAD 2>/dev/null # unstaged changes
git diff --name-only --cached 2>/dev/null # staged changes
} | sort -u | wc -l
)
else
# No commits - check for uncommitted changes (staged + unstaged)
git_files=$(
{
git diff --name-only 2>/dev/null # unstaged changes
git diff --name-only --cached 2>/dev/null # staged changes
} | sort -u | wc -l
)
fi
if [[ $git_files -gt 0 ]]; then
has_progress=true
files_modified=$git_files
@ -500,8 +529,36 @@ analyze_response() {
done
# 6. Check for file changes (git integration)
# Fix #141: Detect both uncommitted changes AND committed changes
if command -v git &>/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then
files_modified=$(git diff --name-only 2>/dev/null | wc -l)
local loop_start_sha=""
local current_sha=""
if [[ -f "$RALPH_DIR/.loop_start_sha" ]]; then
loop_start_sha=$(cat "$RALPH_DIR/.loop_start_sha" 2>/dev/null || echo "")
fi
current_sha=$(git rev-parse HEAD 2>/dev/null || echo "")
# Check if commits were made (HEAD changed)
if [[ -n "$loop_start_sha" && -n "$current_sha" && "$loop_start_sha" != "$current_sha" ]]; then
# Commits were made - count union of committed files AND working tree changes
files_modified=$(
{
git diff --name-only "$loop_start_sha" "$current_sha" 2>/dev/null
git diff --name-only HEAD 2>/dev/null # unstaged changes
git diff --name-only --cached 2>/dev/null # staged changes
} | sort -u | wc -l
)
else
# No commits - check for uncommitted changes (staged + unstaged)
files_modified=$(
{
git diff --name-only 2>/dev/null # unstaged changes
git diff --name-only --cached 2>/dev/null # staged changes
} | sort -u | wc -l
)
fi
if [[ $files_modified -gt 0 ]]; then
has_progress=true
((confidence_score+=20))

View file

@ -492,15 +492,12 @@ should_exit_gracefully() {
fi
# 5. Check fix_plan.md for completion
# Bug #3 Fix: Support indented markdown checkboxes with [[:space:]]* pattern
# Fix #144: Only match valid markdown checkboxes, not date entries like [2026-01-29]
# Valid patterns: "- [ ]" (uncompleted) and "- [x]" or "- [X]" (completed)
if [[ -f "$RALPH_DIR/fix_plan.md" ]]; then
local total_items=$(grep -cE "^[[:space:]]*- \[" "$RALPH_DIR/fix_plan.md" 2>/dev/null)
local completed_items=$(grep -cE "^[[:space:]]*- \[x\]" "$RALPH_DIR/fix_plan.md" 2>/dev/null)
# Handle case where grep returns no matches (exit code 1)
[[ -z "$total_items" ]] && total_items=0
[[ -z "$completed_items" ]] && completed_items=0
local uncompleted_items=$(grep -cE "^[[:space:]]*- \[ \]" "$RALPH_DIR/fix_plan.md" 2>/dev/null || echo "0")
local completed_items=$(grep -cE "^[[:space:]]*- \[[xX]\]" "$RALPH_DIR/fix_plan.md" 2>/dev/null || echo "0")
local total_items=$((uncompleted_items + completed_items))
if [[ $total_items -gt 0 ]] && [[ $completed_items -eq $total_items ]]; then
log_status "WARN" "Exit condition: All fix_plan.md items completed ($completed_items/$total_items)" >&2
@ -1013,6 +1010,14 @@ execute_claude_code() {
local calls_made=$(cat "$CALL_COUNT_FILE" 2>/dev/null || echo "0")
calls_made=$((calls_made + 1))
# Fix #141: Capture git HEAD SHA at loop start to detect commits as progress
# Store in file for access by progress detection after Claude execution
local loop_start_sha=""
if command -v git &>/dev/null && git rev-parse --git-dir &>/dev/null 2>&1; then
loop_start_sha=$(git rev-parse HEAD 2>/dev/null || echo "")
fi
echo "$loop_start_sha" > "$RALPH_DIR/.loop_start_sha"
log_status "LOOP" "Executing Claude Code (Call $calls_made/$MAX_CALLS_PER_HOUR)"
local timeout_seconds=$((CLAUDE_TIMEOUT_MINUTES * 60))
log_status "INFO" "⏳ Starting Claude Code execution... (timeout: ${CLAUDE_TIMEOUT_MINUTES}m)"
@ -1279,7 +1284,41 @@ EOF
log_analysis_summary
# Get file change count for circuit breaker
local files_changed=$(git diff --name-only 2>/dev/null | wc -l || echo 0)
# Fix #141: Detect both uncommitted changes AND committed changes
local files_changed=0
local loop_start_sha=""
local current_sha=""
if [[ -f "$RALPH_DIR/.loop_start_sha" ]]; then
loop_start_sha=$(cat "$RALPH_DIR/.loop_start_sha" 2>/dev/null || echo "")
fi
if command -v git &>/dev/null && git rev-parse --git-dir &>/dev/null 2>&1; then
current_sha=$(git rev-parse HEAD 2>/dev/null || echo "")
# Check if commits were made (HEAD changed)
if [[ -n "$loop_start_sha" && -n "$current_sha" && "$loop_start_sha" != "$current_sha" ]]; then
# Commits were made - count union of committed files AND working tree changes
# This catches cases where Claude commits some files but still has other modified files
files_changed=$(
{
git diff --name-only "$loop_start_sha" "$current_sha" 2>/dev/null
git diff --name-only HEAD 2>/dev/null # unstaged changes
git diff --name-only --cached 2>/dev/null # staged changes
} | sort -u | wc -l
)
[[ "$VERBOSE_PROGRESS" == "true" ]] && log_status "DEBUG" "Detected $files_changed unique files changed (commits + working tree) since loop start"
else
# No commits - check for uncommitted changes (staged + unstaged)
files_changed=$(
{
git diff --name-only 2>/dev/null # unstaged changes
git diff --name-only --cached 2>/dev/null # staged changes
} | sort -u | wc -l
)
fi
fi
local has_errors="false"
# Two-stage error detection to avoid JSON field false positives

View file

@ -75,13 +75,15 @@ should_exit_gracefully() {
fi
# 4. Check fix_plan.md for completion
# Fix #144: Only match valid markdown checkboxes, not date entries like [2026-01-29]
if [[ -f "$RALPH_DIR/fix_plan.md" ]]; then
local total_items=$(grep -c "^- \[" "$RALPH_DIR/fix_plan.md" 2>/dev/null)
local completed_items=$(grep -c "^- \[x\]" "$RALPH_DIR/fix_plan.md" 2>/dev/null)
# Handle case where grep returns no matches (exit code 1)
[[ -z "$total_items" ]] && total_items=0
[[ -z "$completed_items" ]] && completed_items=0
local uncompleted_items
local completed_items
uncompleted_items=$(grep -cE "^[[:space:]]*- \[ \]" "$RALPH_DIR/fix_plan.md" 2>/dev/null || echo "0")
uncompleted_items=$(echo "$uncompleted_items" | tr -d '[:space:]')
completed_items=$(grep -cE "^[[:space:]]*- \[[xX]\]" "$RALPH_DIR/fix_plan.md" 2>/dev/null || echo "0")
completed_items=$(echo "$completed_items" | tr -d '[:space:]')
local total_items=$((uncompleted_items + completed_items))
if [[ $total_items -gt 0 ]] && [[ $completed_items -eq $total_items ]]; then
echo "plan_complete"
@ -878,3 +880,346 @@ EOF
result=$(should_exit_gracefully_with_denials || true)
assert_equal "$result" ""
}
# =============================================================================
# CHECKBOX REGEX FIX TESTS (Issue #144)
# =============================================================================
# These tests verify that date entries like [2026-01-29] are NOT counted as
# checkboxes, preventing false "plan_complete" exits when fix_plan.md contains
# dated entries that match the old [*] pattern.
# Test 41: Date entries should NOT be counted as checkboxes
@test "fix_plan.md date entries are not counted as checkboxes" {
cat > "$RALPH_DIR/fix_plan.md" << 'EOF'
# Fix Plan
## Changelog
- [2026-01-29] Initial version
- [2026-01-30] Added feature X
- [2026-01-31] Bug fixes
## Tasks
- [ ] Task 1 pending
- [ ] Task 2 pending
EOF
result=$(should_exit_gracefully || true)
# Should NOT exit - there are 2 uncompleted tasks
assert_equal "$result" ""
}
# Test 42: Date entries mixed with completed tasks should not cause false exit
@test "fix_plan.md with dates and completed tasks counts correctly" {
cat > "$RALPH_DIR/fix_plan.md" << 'EOF'
# Fix Plan
## Changelog
- [2026-01-29] Initial version
## Tasks
- [x] Task 1 complete
- [ ] Task 2 pending
EOF
result=$(should_exit_gracefully || true)
# 1 completed, 1 pending - should NOT exit
assert_equal "$result" ""
}
# Test 43: Non-checkbox bracket patterns (NOTE, TODO, FIXME) should be excluded
@test "fix_plan.md bracket patterns like [NOTE] are not checkboxes" {
cat > "$RALPH_DIR/fix_plan.md" << 'EOF'
# Fix Plan
## Notes
- [NOTE] Remember to update docs
- [TODO] Consider refactoring later
- [FIXME] Known issue with edge case
- [WIP] Work in progress
## Tasks
- [ ] Task 1 pending
EOF
result=$(should_exit_gracefully || true)
# Only 1 real task (pending) - should NOT exit
assert_equal "$result" ""
}
# Test 44: Case-insensitive completed checkboxes ([x] and [X])
@test "fix_plan.md counts both [x] and [X] as completed" {
cat > "$RALPH_DIR/fix_plan.md" << 'EOF'
# Fix Plan
- [x] Task 1 with lowercase x
- [X] Task 2 with uppercase X
- [x] Task 3 with lowercase x
EOF
result=$(should_exit_gracefully)
# All 3 tasks completed - should exit with plan_complete
assert_equal "$result" "plan_complete"
}
# Test 45: Indented date entries should not be counted
@test "fix_plan.md indented date entries are not checkboxes" {
cat > "$RALPH_DIR/fix_plan.md" << 'EOF'
# Fix Plan
## Releases
- [2026-01-29] v1.0.0 released
- [2026-01-30] v1.0.1 patch
## Tasks
- [x] All done
EOF
result=$(should_exit_gracefully)
# Only 1 real task (completed) - should exit
assert_equal "$result" "plan_complete"
}
# Test 46: Empty checkbox [ ] with spaces should be counted as uncompleted
@test "fix_plan.md empty checkboxes with extra spaces" {
cat > "$RALPH_DIR/fix_plan.md" << 'EOF'
# Fix Plan
- [ ] Task with single space (valid)
- [ ] Task with double space (invalid format, not counted)
- [x] Completed task
EOF
result=$(should_exit_gracefully || true)
# 1 uncompleted, 1 completed - should NOT exit
assert_equal "$result" ""
}
# Test 47: Version numbers in brackets should not be counted
@test "fix_plan.md version numbers like [v1.0] are not checkboxes" {
cat > "$RALPH_DIR/fix_plan.md" << 'EOF'
# Fix Plan
## Version History
- [v1.0] Initial release
- [v1.1] Added features
- [v2.0] Major update
## Tasks
- [x] Task complete
EOF
result=$(should_exit_gracefully)
# Only 1 real task (completed) - should exit
assert_equal "$result" "plan_complete"
}
# Test 48: Issue/PR references should not be counted
@test "fix_plan.md issue references like [#123] are not checkboxes" {
cat > "$RALPH_DIR/fix_plan.md" << 'EOF'
# Fix Plan
## Related Issues
- [#141] Progress detection bug
- [#144] Checkbox regex false positives
- [PR#155] Setup improvements
## Tasks
- [ ] Fix issue #141
- [ ] Fix issue #144
EOF
result=$(should_exit_gracefully || true)
# 2 uncompleted tasks - should NOT exit
assert_equal "$result" ""
}
# =============================================================================
# GIT COMMIT DETECTION TESTS (Issue #141)
# =============================================================================
# These tests verify that when Claude commits within a loop, the committed files
# are counted as progress even though there are no uncommitted changes.
# Helper function to detect progress including git commits
detect_progress_with_commits() {
local loop_start_sha="$1"
local current_sha="$2"
local files_changed=0
# Check for committed changes since loop start
if [[ -n "$loop_start_sha" && -n "$current_sha" && "$loop_start_sha" != "$current_sha" ]]; then
# Files changed in commits between loop start and current HEAD
files_changed=$(git diff --name-only "$loop_start_sha" "$current_sha" 2>/dev/null | wc -l || echo 0)
files_changed=$(echo "$files_changed" | tr -d ' ')
else
# Fall back to uncommitted changes
files_changed=$(git diff --name-only 2>/dev/null | wc -l || echo 0)
files_changed=$(echo "$files_changed" | tr -d ' ')
fi
echo "$files_changed"
}
# Test 49: Git commit detection - files changed in commit count as progress
@test "git commit detection counts committed files as progress" {
# Skip if git is not available
if ! command -v git &>/dev/null; then
skip "git not available"
fi
# Initialize a git repo
git init --quiet
git config user.email "test@test.com"
git config user.name "Test"
# Create initial commit
echo "initial" > file1.txt
git add file1.txt
git commit --quiet -m "Initial commit"
local loop_start_sha=$(git rev-parse HEAD)
# Simulate Claude making changes and committing within the loop
echo "modified" > file1.txt
echo "new file" > file2.txt
git add file1.txt file2.txt
git commit --quiet -m "Claude's work"
local current_sha=$(git rev-parse HEAD)
# Detect progress
local files_changed=$(detect_progress_with_commits "$loop_start_sha" "$current_sha")
# Should detect 2 files changed
[ "$files_changed" -eq 2 ]
}
# Test 50: Git commit detection - no progress when SHA unchanged
@test "git commit detection returns 0 when no commits made" {
# Skip if git is not available
if ! command -v git &>/dev/null; then
skip "git not available"
fi
# Initialize a git repo
git init --quiet
git config user.email "test@test.com"
git config user.name "Test"
# Create initial commit
echo "initial" > file1.txt
git add file1.txt
git commit --quiet -m "Initial commit"
local loop_start_sha=$(git rev-parse HEAD)
local current_sha=$(git rev-parse HEAD)
# No uncommitted changes either
local files_changed=$(detect_progress_with_commits "$loop_start_sha" "$current_sha")
# Should detect 0 files (no commits, no uncommitted changes)
[ "$files_changed" -eq 0 ]
}
# Test 51: Git commit detection - falls back to uncommitted when no commit
@test "git commit detection falls back to uncommitted changes" {
# Skip if git is not available
if ! command -v git &>/dev/null; then
skip "git not available"
fi
# Initialize a git repo
git init --quiet
git config user.email "test@test.com"
git config user.name "Test"
# Create initial commit with two tracked files
echo "initial1" > file1.txt
echo "initial2" > file2.txt
git add file1.txt file2.txt
git commit --quiet -m "Initial commit"
local loop_start_sha=$(git rev-parse HEAD)
# Make uncommitted changes to tracked files (no commit)
echo "modified1" > file1.txt
echo "modified2" > file2.txt
local current_sha=$(git rev-parse HEAD) # Same as loop_start_sha
# Detect progress - should fall back to uncommitted changes
# Note: git diff only shows modified tracked files, not untracked files
local files_changed=$(detect_progress_with_commits "$loop_start_sha" "$current_sha")
# Should detect 2 uncommitted modified files
[ "$files_changed" -eq 2 ]
}
# Test 52: Git commit detection - multiple commits within loop
@test "git commit detection counts files across multiple commits" {
# Skip if git is not available
if ! command -v git &>/dev/null; then
skip "git not available"
fi
# Initialize a git repo
git init --quiet
git config user.email "test@test.com"
git config user.name "Test"
# Create initial commit
echo "initial" > file1.txt
git add file1.txt
git commit --quiet -m "Initial commit"
local loop_start_sha=$(git rev-parse HEAD)
# First commit within loop
echo "change1" > file1.txt
git add file1.txt
git commit --quiet -m "First change"
# Second commit within loop
echo "new" > file2.txt
git add file2.txt
git commit --quiet -m "Second change"
# Third commit within loop
echo "another" > file3.txt
git add file3.txt
git commit --quiet -m "Third change"
local current_sha=$(git rev-parse HEAD)
# Detect progress
local files_changed=$(detect_progress_with_commits "$loop_start_sha" "$current_sha")
# Should detect 3 files (one per commit)
[ "$files_changed" -eq 3 ]
}
# Test 53: Git commit detection handles empty loop_start_sha
@test "git commit detection handles missing loop_start_sha" {
# Skip if git is not available
if ! command -v git &>/dev/null; then
skip "git not available"
fi
# Initialize a git repo
git init --quiet
git config user.email "test@test.com"
git config user.name "Test"
# Create initial commit
echo "initial" > file1.txt
git add file1.txt
git commit --quiet -m "Initial commit"
# Make uncommitted changes
echo "modified" > file1.txt
local current_sha=$(git rev-parse HEAD)
# Detect progress with empty loop_start_sha
local files_changed=$(detect_progress_with_commits "" "$current_sha")
# Should fall back to uncommitted changes (1 file)
[ "$files_changed" -eq 1 ]
}

View file

@ -412,7 +412,7 @@ EOF
echo "test" > "$test_file"
# Verify the function code handles stat failure by checking the implementation
run grep -A25 'get_session_file_age_hours' "${BATS_TEST_DIRNAME}/../../ralph_loop.sh"
run grep -A35 'get_session_file_age_hours' "${BATS_TEST_DIRNAME}/../../ralph_loop.sh"
[[ "$output" == *'echo "-1"'* ]]
}