fix(session): address code review feedback for session management

- Fix SC2155: separate declare from assign in get_session_id(),
  log_session_transition(), init_session_tracking()
- Use jq for safe JSON generation in reset_session() and init_session_tracking()
  instead of heredocs (prevents special character issues)
- Add corruption tolerance to log_session_transition() with JSON validation
  and fallback to empty array on parse failures
- Add generate_session_id() to create unique session IDs (ralph-<epoch>-<random>)
- Add update_session_last_used() helper called on each loop iteration
- init_session_tracking() now generates unique session_id and sets last_used
- Add session files to .gitignore (.ralph_session, .ralph_session_history,
  .claude_session_id)

All 265 tests pass.
This commit is contained in:
Test User 2026-01-10 10:48:19 -07:00
parent 274f496f77
commit cafaacdc0c
2 changed files with 133 additions and 44 deletions

3
.gitignore vendored
View file

@ -3,6 +3,9 @@
.last_reset .last_reset
.exit_signals .exit_signals
status.json status.json
.ralph_session
.ralph_session_history
.claude_session_id
# Logs # Logs
logs/* logs/*

View file

@ -494,9 +494,13 @@ get_session_id() {
return 0 return 0
fi fi
# Extract session_id from JSON file # Extract session_id from JSON file (SC2155: separate declare from assign)
local session_id=$(jq -r '.session_id // ""' "$RALPH_SESSION_FILE" 2>/dev/null) local session_id
if [[ "$session_id" == "null" ]]; then session_id=$(jq -r '.session_id // ""' "$RALPH_SESSION_FILE" 2>/dev/null)
local jq_status=$?
# Handle jq failure or null/empty results
if [[ $jq_status -ne 0 || -z "$session_id" || "$session_id" == "null" ]]; then
session_id="" session_id=""
fi fi
echo "$session_id" echo "$session_id"
@ -508,22 +512,30 @@ get_session_id() {
reset_session() { reset_session() {
local reason=${1:-"manual_reset"} local reason=${1:-"manual_reset"}
# Always create/overwrite the session file to ensure consistent state # Get current timestamp
cat > "$RALPH_SESSION_FILE" << EOF local reset_timestamp
{ reset_timestamp=$(get_iso_timestamp)
"session_id": "",
"created_at": "", # Always create/overwrite the session file using jq for safe JSON escaping
"last_used": "", jq -n \
"reset_at": "$(get_iso_timestamp)", --arg session_id "" \
"reset_reason": "$reason" --arg created_at "" \
} --arg last_used "" \
EOF --arg reset_at "$reset_timestamp" \
--arg reset_reason "$reason" \
'{
session_id: $session_id,
created_at: $created_at,
last_used: $last_used,
reset_at: $reset_at,
reset_reason: $reset_reason
}' > "$RALPH_SESSION_FILE"
# Also clear the Claude session file for consistency # Also clear the Claude session file for consistency
rm -f "$CLAUDE_SESSION_FILE" 2>/dev/null rm -f "$CLAUDE_SESSION_FILE" 2>/dev/null
# Log the session transition # Log the session transition (non-fatal to prevent script exit under set -e)
log_session_transition "active" "reset" "$reason" "${loop_count:-0}" log_session_transition "active" "reset" "$reason" "${loop_count:-0}" || true
log_status "INFO" "Session reset: $reason" log_status "INFO" "Session reset: $reason"
} }
@ -536,15 +548,14 @@ log_session_transition() {
local reason=$3 local reason=$3
local loop_number=${4:-0} local loop_number=${4:-0}
# Initialize history file if needed # Get timestamp once (SC2155: separate declare from assign)
if [[ ! -f "$RALPH_SESSION_HISTORY_FILE" ]]; then local ts
echo '[]' > "$RALPH_SESSION_HISTORY_FILE" ts=$(get_iso_timestamp)
fi
# Create transition entry # Create transition entry using jq for safe JSON (SC2155: separate declare from assign)
local transition local transition
transition=$(jq -n \ transition=$(jq -n -c \
--arg timestamp "$(get_iso_timestamp)" \ --arg timestamp "$ts" \
--arg from_state "$from_state" \ --arg from_state "$from_state" \
--arg to_state "$to_state" \ --arg to_state "$to_state" \
--arg reason "$reason" \ --arg reason "$reason" \
@ -557,40 +568,107 @@ log_session_transition() {
loop_number: $loop_number loop_number: $loop_number
}') }')
# Append to history and keep only last 50 entries # Read history file defensively - fallback to empty array on any failure
local history=$(cat "$RALPH_SESSION_HISTORY_FILE" 2>/dev/null || echo '[]') local history
history=$(echo "$history" | jq ". += [$transition] | .[-50:]") if [[ -f "$RALPH_SESSION_HISTORY_FILE" ]]; then
echo "$history" > "$RALPH_SESSION_HISTORY_FILE" history=$(cat "$RALPH_SESSION_HISTORY_FILE" 2>/dev/null)
# Validate JSON, fallback to empty array if corrupted
if ! echo "$history" | jq empty 2>/dev/null; then
history='[]'
fi
else
history='[]'
fi
# Append transition and keep only last 50 entries
local updated_history
updated_history=$(echo "$history" | jq ". += [$transition] | .[-50:]" 2>/dev/null)
local jq_status=$?
# Only write if jq succeeded
if [[ $jq_status -eq 0 && -n "$updated_history" ]]; then
echo "$updated_history" > "$RALPH_SESSION_HISTORY_FILE"
else
# Fallback: start fresh with just this transition
echo "[$transition]" > "$RALPH_SESSION_HISTORY_FILE"
fi
}
# Generate a unique session ID using timestamp and random component
generate_session_id() {
local ts
ts=$(date +%s)
local rand
rand=$RANDOM
echo "ralph-${ts}-${rand}"
} }
# Initialize session tracking (called at loop start) # Initialize session tracking (called at loop start)
init_session_tracking() { init_session_tracking() {
local ts
ts=$(get_iso_timestamp)
# Create session file if it doesn't exist # Create session file if it doesn't exist
if [[ ! -f "$RALPH_SESSION_FILE" ]]; then if [[ ! -f "$RALPH_SESSION_FILE" ]]; then
cat > "$RALPH_SESSION_FILE" << EOF local new_session_id
{ new_session_id=$(generate_session_id)
"session_id": "",
"created_at": "$(get_iso_timestamp)", jq -n \
"last_used": "", --arg session_id "$new_session_id" \
"reset_at": "", --arg created_at "$ts" \
"reset_reason": "" --arg last_used "$ts" \
} --arg reset_at "" \
EOF --arg reset_reason "" \
log_status "INFO" "Initialized session tracking" '{
session_id: $session_id,
created_at: $created_at,
last_used: $last_used,
reset_at: $reset_at,
reset_reason: $reset_reason
}' > "$RALPH_SESSION_FILE"
log_status "INFO" "Initialized session tracking (session: $new_session_id)"
return 0
fi fi
# Validate existing session file # Validate existing session file
if ! jq empty "$RALPH_SESSION_FILE" 2>/dev/null; then if ! jq empty "$RALPH_SESSION_FILE" 2>/dev/null; then
log_status "WARN" "Corrupted session file detected, recreating..." log_status "WARN" "Corrupted session file detected, recreating..."
cat > "$RALPH_SESSION_FILE" << EOF local new_session_id
{ new_session_id=$(generate_session_id)
"session_id": "",
"created_at": "$(get_iso_timestamp)", jq -n \
"last_used": "", --arg session_id "$new_session_id" \
"reset_at": "", --arg created_at "$ts" \
"reset_reason": "corrupted_file_recovery" --arg last_used "$ts" \
--arg reset_at "$ts" \
--arg reset_reason "corrupted_file_recovery" \
'{
session_id: $session_id,
created_at: $created_at,
last_used: $last_used,
reset_at: $reset_at,
reset_reason: $reset_reason
}' > "$RALPH_SESSION_FILE"
fi
} }
EOF
# Update last_used timestamp in session file (called on each loop iteration)
update_session_last_used() {
if [[ ! -f "$RALPH_SESSION_FILE" ]]; then
return 0
fi
local ts
ts=$(get_iso_timestamp)
# Update last_used in existing session file
local updated
updated=$(jq --arg last_used "$ts" '.last_used = $last_used' "$RALPH_SESSION_FILE" 2>/dev/null)
local jq_status=$?
if [[ $jq_status -eq 0 && -n "$updated" ]]; then
echo "$updated" > "$RALPH_SESSION_FILE"
fi fi
} }
@ -899,6 +977,10 @@ main() {
while true; do while true; do
loop_count=$((loop_count + 1)) loop_count=$((loop_count + 1))
log_status "INFO" "DEBUG: Successfully incremented loop_count to $loop_count" log_status "INFO" "DEBUG: Successfully incremented loop_count to $loop_count"
# Update session last_used timestamp
update_session_last_used
log_status "INFO" "Loop #$loop_count - calling init_call_tracking..." log_status "INFO" "Loop #$loop_count - calling init_call_tracking..."
init_call_tracking init_call_tracking
@ -1031,6 +1113,10 @@ Files created:
- $LOG_DIR/: All execution logs - $LOG_DIR/: All execution logs
- $DOCS_DIR/: Generated documentation - $DOCS_DIR/: Generated documentation
- $STATUS_FILE: Current status (JSON) - $STATUS_FILE: Current status (JSON)
- .ralph_session: Session lifecycle tracking
- .ralph_session_history: Session transition history (last 50)
- .call_count: API call counter for rate limiting
- .last_reset: Timestamp of last rate limit reset
Example workflow: Example workflow:
ralph-setup my-project # Create project ralph-setup my-project # Create project