Skip to content

set -e + set -o pipefail causes silent script death on Claude timeout #175

@serejke

Description

@serejke

Bug Description

When Claude Code times out during live streaming mode, the entire Ralph loop script silently exits with no error message, no log entry, and no recovery. The tmux session shows the shell prompt but Ralph is no longer running.

Root Cause

ralph_loop.sh line 6 sets set -e (exit on any error). The live streaming pipeline at lines 1152-1154:

set -o pipefail
portable_timeout ${timeout_seconds}s stdbuf -oL "${LIVE_CMD_ARGS[@]}" \
    < /dev/null 2>&1 | stdbuf -oL tee "$output_file" | stdbuf -oL jq --unbuffered -j "$jq_filter" 2>/dev/null | tee "$LIVE_LOG_FILE"

When the timeout fires, portable_timeout kills the Claude process with a non-zero exit code (124). With set -o pipefail, the pipeline's exit code is non-zero. With set -e, bash immediately exits the entire script before reaching:

  • Line 1157: PIPESTATUS capture
  • Line 1158: set +o pipefail
  • Any timeout handling or loop continuation logic

Reproduction

  1. Set CLAUDE_TIMEOUT_MINUTES=15 in .ralphrc
  2. Start Ralph with ralph --monitor
  3. Wait for Claude Code to hit a task that takes longer than 15 minutes (e.g., Playwright E2E test development with iterative debugging)
  4. Observe: Ralph silently exits, no error in ralph.log, the last log entry is "📺 Live output mode enabled - showing Claude Code streaming..."

Evidence from real session:

  • Loop started at 23:02:33
  • Log file modification time: 23:17:32 (exactly 15 minutes later)
  • Last ralph.log entry: [2026-02-11 23:02:33] [INFO] 📺 Live output mode enabled - showing Claude Code streaming...
  • No completion, timeout, or error entry follows — the script just died

Suggested Fix

Protect the streaming pipeline from set -e by temporarily disabling it:

set +e  # Disable errexit for pipeline - timeout returns non-zero
set -o pipefail
portable_timeout ${timeout_seconds}s stdbuf -oL "${LIVE_CMD_ARGS[@]}" \
    < /dev/null 2>&1 | stdbuf -oL tee "$output_file" | stdbuf -oL jq --unbuffered -j "$jq_filter" 2>/dev/null | tee "$LIVE_LOG_FILE"

# Capture exit codes from pipeline
local -a pipe_status=("${PIPESTATUS[@]}")
set +o pipefail
set -e  # Re-enable errexit

Impact

  • Ralph loop silently stops, requiring manual restart
  • No indication of what happened — no error log, no status update
  • Circuit breaker and session recovery never trigger
  • Users must manually detect the stall and restart

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions