diff --git a/ralph_loop.sh b/ralph_loop.sh index 18c1df6..61059dc 100755 --- a/ralph_loop.sh +++ b/ralph_loop.sh @@ -1048,21 +1048,20 @@ execute_claude_code() { session_id=$(init_claude_session) fi + # Live mode requires JSON output (stream-json) — override text format + if [[ "$LIVE_OUTPUT" == "true" && "$CLAUDE_OUTPUT_FORMAT" == "text" ]]; then + log_status "WARN" "Live mode requires JSON output format. Overriding text → json for this session." + CLAUDE_OUTPUT_FORMAT="json" + fi + # Build the Claude CLI command with modern flags - # Note: We use the modern CLI with -p flag when CLAUDE_OUTPUT_FORMAT is "json" - # For backward compatibility, fall back to stdin piping for text mode local use_modern_cli=false - if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then - # Modern approach: use CLI flags (builds CLAUDE_CMD_ARGS array) - if build_claude_command "$PROMPT_FILE" "$loop_context" "$session_id"; then - use_modern_cli=true - log_status "INFO" "Using modern CLI mode (JSON output)" - else - log_status "WARN" "Failed to build modern CLI command, falling back to legacy mode" - fi + if build_claude_command "$PROMPT_FILE" "$loop_context" "$session_id"; then + use_modern_cli=true + log_status "INFO" "Using modern CLI mode (${CLAUDE_OUTPUT_FORMAT} output)" else - log_status "INFO" "Using legacy CLI mode (text output)" + log_status "WARN" "Failed to build modern CLI command, falling back to legacy mode" fi # Execute Claude Code @@ -1091,6 +1090,14 @@ execute_claude_code() { fi fi + if [[ "$LIVE_OUTPUT" == "true" ]]; then + # Safety check: CLAUDE_CMD_ARGS must be populated for live mode + if [[ ${#CLAUDE_CMD_ARGS[@]} -eq 0 ]]; then + log_status "ERROR" "CLAUDE_CMD_ARGS is empty — cannot build live mode command. Falling back to background mode." + LIVE_OUTPUT=false + fi + fi + if [[ "$LIVE_OUTPUT" == "true" ]]; then log_status "INFO" "📺 Live output mode enabled - showing Claude Code streaming..." echo -e "${PURPLE}━━━━━━━━━━━━━━━━ Claude Code Output ━━━━━━━━━━━━━━━━${NC}" @@ -1613,7 +1620,7 @@ Options: -s, --status Show current status and exit -m, --monitor Start with tmux session and live monitor (requires tmux) -v, --verbose Show detailed progress updates during execution - -l, --live Show Claude Code output in real-time (streaming mode) + -l, --live Show Claude Code output in real-time (auto-switches to JSON output) -t, --timeout MIN Set Claude Code execution timeout in minutes (default: $CLAUDE_TIMEOUT_MINUTES) --reset-circuit Reset circuit breaker to CLOSED state --circuit-status Show circuit breaker status and exit @@ -1622,6 +1629,7 @@ Options: Modern CLI Options (Phase 1.1): --output-format FORMAT Set Claude output format: json or text (default: $CLAUDE_OUTPUT_FORMAT) + Note: --live mode requires JSON and will auto-switch --allowed-tools TOOLS Comma-separated list of allowed tools (default: $CLAUDE_ALLOWED_TOOLS) --no-continue Disable session continuity across loops --session-expiry HOURS Set session expiration time in hours (default: $CLAUDE_SESSION_EXPIRY_HOURS) diff --git a/tests/unit/test_cli_modern.bats b/tests/unit/test_cli_modern.bats index 7c0017d..ea5d8f1 100644 --- a/tests/unit/test_cli_modern.bats +++ b/tests/unit/test_cli_modern.bats @@ -667,3 +667,98 @@ EOF [[ "$output" == *'_env_MAX_CALLS_PER_HOUR'* ]] [[ "$output" == *'_env_CLAUDE_TIMEOUT_MINUTES'* ]] } + +# ============================================================================= +# LIVE MODE + TEXT FORMAT FIX TESTS (Issue #164) +# Tests for: live mode format override, always-call build_claude_command, +# and safety check for empty CLAUDE_CMD_ARGS +# ============================================================================= + +@test "build_claude_command works for text format (populates CLAUDE_CMD_ARGS)" { + export CLAUDE_CODE_CMD="claude" + export CLAUDE_OUTPUT_FORMAT="text" + export CLAUDE_ALLOWED_TOOLS="Write,Read" + export CLAUDE_USE_CONTINUE="false" + + echo "Test prompt content" > "$PROMPT_FILE" + + build_claude_command "$PROMPT_FILE" "" "" + + # CLAUDE_CMD_ARGS should be populated even in text mode + [[ ${#CLAUDE_CMD_ARGS[@]} -gt 0 ]] + + local cmd_string="${CLAUDE_CMD_ARGS[*]}" + + # Should contain claude command and -p flag + [[ "$cmd_string" == *"claude"* ]] + [[ "$cmd_string" == *"-p"* ]] + [[ "$cmd_string" == *"Test prompt content"* ]] + + # Should NOT contain --output-format (text mode omits it) + [[ "$cmd_string" != *"--output-format"* ]] + + # Should still include allowed tools + [[ "$cmd_string" == *"--allowedTools"* ]] + [[ "$cmd_string" == *"Write"* ]] +} + +@test "build_claude_command works for json format (includes --output-format json)" { + export CLAUDE_CODE_CMD="claude" + export CLAUDE_OUTPUT_FORMAT="json" + export CLAUDE_ALLOWED_TOOLS="" + export CLAUDE_USE_CONTINUE="false" + + echo "Test prompt" > "$PROMPT_FILE" + + build_claude_command "$PROMPT_FILE" "" "" + + local cmd_string="${CLAUDE_CMD_ARGS[*]}" + + # Should contain --output-format json + [[ "$cmd_string" == *"--output-format"* ]] + [[ "$cmd_string" == *"json"* ]] + [[ "$cmd_string" == *"-p"* ]] +} + +@test "live mode overrides text to json format in ralph_loop.sh" { + # Verify ralph_loop.sh contains the live mode format override logic + run grep -A3 'LIVE_OUTPUT.*true.*CLAUDE_OUTPUT_FORMAT.*text' "${BATS_TEST_DIRNAME}/../../ralph_loop.sh" + + # Should find the override block + [[ "$output" == *"CLAUDE_OUTPUT_FORMAT"* ]] + [[ "$output" == *"json"* ]] +} + +@test "live mode format override preserves json format unchanged" { + # The override should only trigger when format is "text", not "json" + # Verify the condition checks for text specifically + run grep 'CLAUDE_OUTPUT_FORMAT.*text' "${BATS_TEST_DIRNAME}/../../ralph_loop.sh" + + # Should check specifically for "text" (not a blanket override) + [[ "$output" == *'"text"'* ]] +} + +@test "safety check prevents live mode with empty CLAUDE_CMD_ARGS" { + # Verify ralph_loop.sh has the safety check for empty CLAUDE_CMD_ARGS + run grep -A3 'CLAUDE_CMD_ARGS.*-eq 0' "${BATS_TEST_DIRNAME}/../../ralph_loop.sh" + + # Should find safety check that falls back to background mode + [[ "$output" == *"LIVE_OUTPUT"* ]] || [[ "$output" == *"empty"* ]] || [[ "$output" == *"background"* ]] +} + +@test "build_claude_command is called regardless of output format in ralph_loop.sh" { + # Verify that build_claude_command is NOT gated behind JSON-only check + # The old pattern was: if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then build_claude_command... + # The new pattern should call build_claude_command unconditionally + + # Check that build_claude_command call is NOT inside a JSON-only conditional + # Look for the actual call site (not the function definition or comments) + local script="${BATS_TEST_DIRNAME}/../../ralph_loop.sh" + + # The old pattern: "json" check immediately followed by build_claude_command + # should no longer exist as a gate + run bash -c "sed -n '/# Build the Claude CLI command/,/execute Claude Code/p' '$script' | grep -c 'CLAUDE_OUTPUT_FORMAT.*json.*build_claude_command'" + + # Should find 0 matches (the gate has been removed) + [[ "$output" == "0" ]] +}