Newer Claude CLI versions read from stdin even in -p (print) mode.
This causes hangs in both execution modes:
- Background mode: OS sends SIGTTIN to suspend the process; the
progress-monitoring kill -0 loop runs forever since it returns
true for stopped (not just running) processes.
- Live/streaming mode (--monitor/--live): the foreground process
blocks waiting for terminal input it never receives.
Fix: add < /dev/null to both the modern CLI background execution path
and the live streaming pipeline, consistent with legacy mode which
already redirects stdin from $PROMPT_FILE.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Disable live mode when build_claude_command fails (not just empty array)
- Strengthen safety check to verify use_modern_cli=true
- Fix case-sensitive sed range in test (execute→Execute)
- Update CLAUDE.md: auto-switch docs, test counts (484→490)
Live/monitor mode crashed with `stdbuf: unrecognized option '--verbose'`
when CLAUDE_OUTPUT_FORMAT="text" because build_claude_command() was only
called inside a JSON-only gate, leaving CLAUDE_CMD_ARGS empty.
Three coordinated fixes:
- Override text→json when live mode is active (stream-json requires JSON)
- Always call build_claude_command() regardless of output format
- Add safety check for empty CLAUDE_CMD_ARGS before live mode construction
Also updates help text for --live and --output-format to document the
auto-switch behavior.
Update 7 stale per-file test counts in the test table to match actual
counts (total still 484). Add missing test_circuit_breaker_recovery.bats
to the Running Tests listing. Add E2E Testing Philosophy section for
future v2 UI work (Playwright, real services, a11y).
- Move history file init before auto-recovery logic to prevent
log_circuit_transition from writing to nonexistent history file
- Fix BSD date -j timezone handling: normalize tz (Z→+0000, ±HH:MM→±HHMM)
and parse with %z format so UTC timestamps aren't misinterpreted as local
- Update stale test counts in CLAUDE.md (420→484) and README.md (465→484)
across badge, header, and inline comments
The OPEN state was terminal — once triggered, it persisted across
restarts with no automatic recovery. This adds two recovery mechanisms:
1. Cooldown timer (default): OPEN → HALF_OPEN after CB_COOLDOWN_MINUTES
(default 30). The existing HALF_OPEN logic handles recovery or re-trip.
2. Auto-reset option: CB_AUTO_RESET=true bypasses cooldown, resets to
CLOSED on startup for fully unattended operation.
Changes:
- Add parse_iso_to_epoch() to lib/date_utils.sh (cross-platform)
- Add cooldown + auto-reset logic to init_circuit_breaker()
- Add opened_at field to state file when entering/staying OPEN
- Add --auto-reset-circuit CLI flag and .ralphrc config vars
- Add 19 tests in test_circuit_breaker_recovery.bats
- Update CLAUDE.md and README.md documentation
The previous fix (#143) only extracted `.tool_input.command`, which works
for Bash denials but shows nothing for non-Bash tool denials like
AskUserQuestion (which has no command field).
This fix handles both cases:
- Bash denials: shows "Bash(git commit -m ...)" with truncated command
- Non-Bash tool denials: shows the tool name like "AskUserQuestion"
Example output:
🚫 Permission denied for 1 command(s): AskUserQuestion
🚫 Permission denied for 2 command(s): Bash(git commit -m "..."), Bash(npm install)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When `grep -c` finds 0 matches, it outputs "0" to stdout but exits
with code 1 (no matches found). The `|| echo "0"` fallback then
triggers, appending another "0" to the command substitution output.
This results in the variable containing "0\n0" instead of just "0",
causing a bash arithmetic syntax error on the $((...)) expression:
line 500: 0 0: syntax error in expression (error token is "0")
Fix: Replace `|| echo "0"` with `|| true` so the grep output is
preserved as-is, with a fallback to set 0 if the variable is empty
(which handles actual grep errors like missing files).
Affects 3 locations in ralph_loop.sh (lines 498, 499, 605).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* 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>
CLAUDE.md is for development guidance, not changelog tracking.
Version info and changelog now live exclusively in README.md.
Versioning automation tracked in issue #138.
- Use --resume <session_id> instead of --continue to avoid hijacking
active Claude Code sessions in the same directory (Issue #151)
- Write log_status output to stderr to prevent log messages from
interfering with function return values
The --continue flag resumes "most recent session in current directory"
which can inadvertently take over an unrelated Claude Code session.
Now Ralph only resumes its own sessions by explicit ID.
Fixes#151
Credit: @dionny (PR #153)
When Claude outputs STATUS: COMPLETE with EXIT_SIGNAL: false, it means
"this task is complete, but there are more tasks to do". Previously,
STATUS: COMPLETE incorrectly overrode EXIT_SIGNAL, causing premature exit.
The fix:
- Track whether EXIT_SIGNAL was explicitly provided (vs inferred)
- Only use STATUS: COMPLETE as fallback when no EXIT_SIGNAL was specified
- Explicit EXIT_SIGNAL: false is now respected (continues working)
Fixes#146
Credit: @linuxkd (PR #147)
Fixes date command errors on macOS with Homebrew GNU coreutils:
- 'date: invalid option -- v' when GNU date runs BSD syntax
- 'syntax error in expression' when stat returns filesystem metadata
Changes:
- Replace uname-based detection with capability detection (try/fallback)
- Try GNU commands first, fall back to BSD, then ultimate fallbacks
- Add date -r fallback for file mtime (most portable)
This handles mixed environments where uname returns "Darwin" but
GNU coreutils are in PATH from Homebrew.
Credit: @farce1 (PR #119)
Without --print, Claude CLI starts an interactive session that hangs
when stdin comes from a file/pipe. This caused ralph-import to hang
indefinitely when converting PRDs.
Changes:
- Add --print flag for non-interactive (piped) input mode
- Add --strict-mcp-config to skip loading user MCP servers (faster startup)
Credit: @merlinrabens (PR #103)
Previously, ralph-import would prepend '../' to the source file path
after cd'ing into the project directory. This breaks when an absolute
path is provided (e.g., /Users/foo/file.md becomes ..//Users/foo/file.md).
Now checks if the path is absolute (starts with /) before prepending '../'.
Credit: @GiladSchneider (PR #92)
- setup.sh: Skip git init if .git already exists (prevents errors in existing repos)
- install.sh: Copy setup.sh directly instead of hardcoding a simplified version
This ensures global installation uses the same setup logic as local,
including .ralphrc generation via enable_core.sh
Co-authored-by: dawenrenhub <darwinren321@gmail.com>
The permission_denials array from Claude CLI has commands nested under
tool_input.command, not directly as .command:
Before: jq '[.permission_denials[].command]'
After: jq '[.permission_denials[].tool_input.command]'
This fixes the "Permission denied for N command(s)" message to actually
display the denied commands instead of showing empty/unknown.
Also updated test fixture to match real Claude CLI output structure.
- Bump version from v0.11.2 to v0.11.3
- Update test count from 440 to 452 across all badges and references
- Add v0.11.3 release notes for live streaming output (#125) and beads fix (#150)
- Document --live flag for real-time Claude Code visibility
- Add Live Streaming Output configuration section
* feat: Add live streaming output mode with real-time Claude Code visibility
New features:
- Add --live flag for real-time streaming output using stream-json + jq
- Display tool usage with visual indicators (⚡ [Read], ⚡ [Bash], etc.)
- Add line breaks between messages for better readability
- Integrate live mode automatically in --monitor tmux layout
- Add IS_SANDBOX=1 and CLAUDE_CODE_ENABLE_DANGEROUS_PERMISSIONS_IN_SANDBOX=1
environment variables to allow --dangerously-skip-permissions with root
- Remove DEBUG log messages for cleaner output
The live streaming uses Claude Code's --output-format stream-json with jq
to extract and display text in real-time, allowing users to watch Claude
work as it happens.
tmux --monitor layout now shows:
- Left pane: Ralph loop with live streaming
- Right-top pane: tail -f on live.log
- Right-bottom pane: Status monitor
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(live): use CLAUDE_CMD_ARGS for live mode, remove sandbox exports
Live mode now properly uses build_claude_command() to preserve:
- --allowedTools (tool permissions from .ralphrc)
- --append-system-prompt (loop context)
- --continue (session continuity for save_claude_session())
- -p (prompt content)
Changes:
- Remove unconditional CLAUDE_CODE_ENABLE_DANGEROUS_PERMISSIONS_IN_SANDBOX
and IS_SANDBOX exports (tool restrictions via --allowedTools is sufficient)
- Live mode builds LIVE_CMD_ARGS from CLAUDE_CMD_ARGS, replacing
--output-format json with stream-json
- Add --verbose and --include-partial-messages for streaming
- Remove --dangerously-skip-permissions from legacy fallback mode
- Preserves session ID extraction for save_claude_session()
* fix(live): address critical issues in live mode implementation
Issue 1 - Session Continuity:
- Extract session_id from stream-json output after streaming completes
- Find the "result" type message and convert to standard JSON format
- Save to separate session file for save_claude_session() to parse
Issue 2 - Security Model:
- Remove --dangerously-skip-permissions from build_claude_command()
- Tool permissions now properly controlled via --allowedTools
- Preserves permission denial circuit breaker (Issue #101)
Issue 3 - Dependency Checks:
- Add checks for jq and stdbuf before enabling live mode
- Fall back to background mode if dependencies missing
Issue 4 - Pipeline Error Handling:
- Use set -o pipefail to capture all pipeline exit codes
- Capture PIPESTATUS array for each pipeline stage
- Warn on jq parsing issues without failing the loop
- Primary exit code from Claude command preserved
* fix(live): add timeout protection and improve session handling robustness
Issue 1 - Missing Timeout Protection:
- Add portable_timeout to live mode pipeline for consistent timeout behavior
- Prevents indefinite hangs matching background mode behavior
Issue 2 - Session Continuity Robustness:
- Use flexible regex [[:space:]]* to match various JSON formatting styles
- Preserve full stream output in _stream.log BEFORE modifying output_file
- Validate extracted JSON with jq -e before using it
- Restore stream output if validation fails
Issue 3 - Pipeline Error Handling:
- Check pipe_status[1] (first tee) for write failures
- Warn if output file write fails (could break session/logging)
- All four pipeline stages now monitored
---------
Co-authored-by: André Théo LAURET <andrelauret@icloud.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Test User <test@example.com>
The OIDC token exchange fails with pull_request_target events.
Provide explicit github_token to fix authentication.
Also upgrade to pull-requests: write for posting comments.
The OpenCode action doesn't support pull_request_target event.
Revert to pull_request so it works for repo branch PRs.
Fork PRs will be reviewed by claude-code-review.yml instead.
Switch both review workflows from pull_request to pull_request_target
so they run with base repo permissions and can access secrets when
reviewing fork PRs.
Changes:
- claude-code-review.yml: pull_request → pull_request_target
- opencode-review.yml: pull_request → pull_request_target
- Both now explicitly checkout PR head commit for review
- Added security comments explaining the approach
This allows the workflows to run successfully after maintainer approval
for external contributor PRs.
When Claude Code is denied permission to execute commands (e.g., npm install),
Ralph now detects this from the permission_denials array in the JSON output
and halts the loop immediately with clear guidance for the user.
Changes:
- Add permission denial detection to parse_json_response() in response_analyzer.sh
- Extract permission_denials array from Claude Code JSON output
- Track has_permission_denials, permission_denial_count, denied_commands
- Add analyze_response() support for permission denial fields
- Add permission denial exit condition to should_exit_gracefully() in ralph_loop.sh
- Permission denial takes highest priority among exit conditions
- Display helpful guidance for updating ALLOWED_TOOLS in .ralphrc
- Update circuit breaker with CB_PERMISSION_DENIAL_THRESHOLD=2
- Track consecutive_permission_denials in state file
- Open circuit after 2 consecutive loops with permission denials
- Add 11 new TDD tests (6 in test_json_parsing.bats, 5 in test_exit_detection.bats)
- Update documentation in CLAUDE.md and README.md
Test count: 452 (up from 452 - added 11 new tests)
Fixes#101
Co-authored-by: Test User <test@example.com>
* fix: Recognize STATUS: COMPLETE as progress in circuit breaker
The circuit breaker was only detecting progress through git diff changes.
When Claude completed work and committed it, subsequent loops showed 0
uncommitted changes, causing the circuit breaker to trip after 3 loops.
This fix adds multiple progress detection sources:
1. Git diff changes (existing)
2. has_completion_signal from response analysis (STATUS: COMPLETE)
3. files_modified reported by Claude in RALPH_STATUS block
This ensures that completed work is recognized as progress even when
all changes have been committed to git.
Fixes issue where circuit breaker would repeatedly trip on projects
where Claude finishes tasks and commits them immediately.
* Update lib/circuit_breaker.sh
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---------
Co-authored-by: Frank Bria <frank.bria@proton.me>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
New users were confused about which files they write vs. which Ralph
manages, and how PROMPT.md, specs/, and fix_plan.md relate to each other.
Added:
- docs/user-guide/ with quick start tutorial, file reference, and
requirements writing guide
- examples/simple-cli-tool/ showing minimal Ralph configuration
- examples/rest-api/ demonstrating when to use specs/
- README section explaining Ralph files and their relationships
Also documents specs/stdlib/ purpose for reusable patterns.
* fix(setup): create .ralphrc with consistent tool permissions (#136)
- Update default ALLOWED_TOOLS in ralph_loop.sh to include Edit,
Bash(npm *), and Bash(pytest) for test execution capability
- Make setup.sh generate .ralphrc file using same permissions as
ralph-enable, ensuring consistency between initialization paths
- Add 8 new TDD tests for .ralphrc creation and ALLOWED_TOOLS defaults
- Update documentation in README.md and CLAUDE.md
This fixes the mismatch where PROMPT.md instructs the model to run
tests, but the default permissions didn't allow it. Now both
ralph-setup and ralph-enable create projects with identical tool
permissions.
Test count: 440 (up from 424)
* fix: address PR review feedback
- Update version badges from v0.10.1 to v0.11.2 (README.md)
- Update test count badges from 310 to 440 (README.md, CLAUDE.md)
- Fix .ralphrc generator label: use sed to replace "ralph enable"
with "ralph-setup" when using generate_ralphrc() from library
- Add v0.11.2 changelog entry to CLAUDE.md
* docs(readme): comprehensive update for v0.11.2
- Reorganize Recent Improvements with v0.11.x versions prominent
- Add ralph-enable wizard section with full documentation
- Add .ralphrc configuration section with example
- Update Quick Start to show ralph-enable as Option A (recommended)
- Update test counts to 440 across 15 files
- Collapse v0.9.x versions into expandable details section
- Add new features to What's Working Now section
- Link to issue #138 for automated badge updates
- Update Command Reference with new commands
---------
Co-authored-by: Test User <test@example.com>
Cherry-pick of #126 rebased onto current main to pass CI.
Original work by @zerone0x. Fixes#120.
Co-Authored-By: zerone0x <hi@trine.dev>
Co-Authored-By: Claude <noreply@anthropic.com>
When using --monitor flag, the tmux session now correctly forwards all
CLI parameters to the inner ralph_loop.sh execution instead of only
--calls and --prompt.
Added forwarding for:
- --output-format (json/text)
- --verbose
- --timeout
- --allowed-tools
- --no-continue
- --session-expiry
Added 8 new tests for parameter forwarding validation.
Test count: 329 (up from 321)
Fixes#120
Co-Authored-By: Claude <noreply@anthropic.com>
Add concurrency control to all review workflows:
- opencode-review.yml: Cancel in-progress for same PR
- claude-code-review.yml: Cancel in-progress for same PR
- claude.yml: Cancel in-progress for same issue/PR
This prevents duplicate reviews when PRs are updated rapidly
or multiple comments are posted in quick succession.
Co-authored-by: Test User <test@example.com>
Move variable exports before first use in the #91 regression test to
prevent "No such file or directory" failures in CI environments where
setup() state may not persist.
Co-authored-by: Test User <test@example.com>
Replace confidence-based heuristic in update_exit_signals() with explicit
EXIT_SIGNAL checking. JSON mode always has confidence >= 70 due to
deterministic scoring, causing completion_indicators to fill after 5 loops
and triggering premature exits even when Claude sets EXIT_SIGNAL: false.
Changes:
- lib/response_analyzer.sh: Check exit_signal == "true" instead of
confidence >= 60 when updating completion_indicators array
- ralph_loop.sh: Update safety circuit breaker comment to reflect that
completion_indicators now only accumulates on EXIT_SIGNAL=true
- tests/unit/test_exit_detection.bats: Add 4 TDD tests (32-35) validating
the fix for update_exit_signals() behavior
- CLAUDE.md: Document fix as v0.11.1, update test counts (420 → 424)
Test count: 424 passing (100% pass rate)
Co-authored-by: Test User <test@example.com>
* refactor(naming): remove @ prefix from on-disk filenames
BREAKING CHANGE: Renames @fix_plan.md → fix_plan.md and @AGENT.md → AGENT.md
This change improves POSIX compliance and compatibility with command-line tools.
The @ prefix was originally used to avoid naming conflicts, but with the .ralph/
folder structure introduced in v0.10.0, this convention is no longer necessary.
Changes:
- Update all scripts to use new naming (fix_plan.md, AGENT.md)
- Update migration script to handle both old and new naming conventions
- Update templates to use new naming
- Update all documentation references
- Update all 420 tests to expect new naming (TDD approach)
Migration:
- Existing projects with @-prefixed files will be automatically renamed
when running ralph-migrate
- Projects already using the new naming will continue to work unchanged
* docs(claude): update file naming conventions section
* fix(migrate): prevent orphaned @-prefixed files during migration
When both root/@fix_plan.md and .ralph/@fix_plan.md exist, the root file
now takes priority and the .ralph/@fix_plan.md is removed (backup exists).
This prevents orphaned legacy files after migration.
---------
Co-authored-by: Test User <test@example.com>
When wizard prompt functions (prompt_text, prompt_number, select_option,
select_with_default) were used with command substitution, ANSI-colored
prompts were captured along with user responses, corrupting .ralphrc
values like: PROJECT_NAME="[0;36mProject name[0m [value]: value"
Fix:
- Redirect all display output (colored prompts, validation messages) to
stderr using >&2, matching the pattern already used by select_multiple()
- Keep only the actual response/result on stdout for command substitution
Changes:
- lib/wizard_utils.sh: Add >&2 to prompt_text (lines 84-87),
prompt_number (lines 118-121, 131-151), confirm (lines 44, 60),
select_option (lines 190-215), select_with_default (lines 330-364)
- tests/unit/test_wizard_utils.bats: Add 20 new tests for stdout/stderr
separation and clean command substitution results
- CLAUDE.md: Update test count to 420
Test count: 420 (up from 396)
Co-authored-by: Test User <test@example.com>
* feat(enable): add ralph-enable wizard for existing projects (v0.11.0)
Add interactive wizard and CI version for enabling Ralph in existing projects.
New commands:
- ralph-enable: Interactive 5-phase wizard for humans
- ralph-enable-ci: Non-interactive version with JSON output for CI/automation
New library components:
- lib/enable_core.sh: Shared logic for idempotency, project detection, templates
- lib/wizard_utils.sh: Interactive prompt utilities
- lib/task_sources.sh: Task import from beads, GitHub Issues, PRD documents
Features:
- Auto-detects project type (TypeScript, Python, Rust, Go)
- Auto-detects framework (Next.js, FastAPI, Django, Express)
- Imports tasks from beads, GitHub Issues, or PRD documents
- Generates .ralphrc project configuration file
- Idempotent: safe to run multiple times, respects existing files
- Exit codes: 0 (success), 1 (error), 2 (already enabled)
Updated:
- install.sh: Added new commands to global installation
- ralph_loop.sh: Loads .ralphrc configuration at startup
Tests: 75 new tests (30 enable_core + 23 task_sources + 22 integration)
Total: 396 tests passing (100% pass rate)
Closes#85, #121, #64, #87, #99
* fix(enable): address code review feedback
Fixes from PR #124 review:
1. sed -i portability (ralph_enable.sh:456)
- Use portable sed + mv pattern instead of GNU-only sed -i
2. sed regex portability (lib/task_sources.sh)
- Replace \s with POSIX [[:space:]] character class
- Add sed -E flag for extended regex
3. jq availability check (ralph_enable_ci.sh:177)
- Add check for jq when --json flag is used
4. Unused filter parameter (lib/task_sources.sh:44)
- Pass filter to bd list --filter command
5. Word-splitting in select_multiple (ralph_enable.sh:322)
- Return comma-separated indices instead of space-separated text
- Update caller to parse indices correctly
6. Missing || true for check_existing_ralph (ralph_enable.sh:185)
- Prevent set -e from exiting on non-zero return
7. select_multiple stdout corruption (lib/wizard_utils.sh)
- Redirect interactive output to stderr
- Only final result goes to stdout
8. Color variables not exported (lib/wizard_utils.sh:12)
- Export WIZARD_* color variables for subshells
9. select_option infinite loop (lib/wizard_utils.sh:179)
- Add guard for empty options array
* fix(tests): add missing mocks and exports for new enable feature
- Add RESPONSE_ANALYSIS_FILE export to test_session_continuity.bats setup
- Add mock ralph_enable.sh and ralph_enable_ci.sh to test_installation.bats
- Add mock lib files: enable_core.sh, wizard_utils.sh, task_sources.sh, timeout_utils.sh
All 396 tests now pass.
* fix(config): fix critical issues from PR review
1. .ralphrc Configuration Loading Fix:
- Captured env var state BEFORE setting defaults with _env_* variables
- load_ralphrc now only restores values explicitly set by environment
- .ralphrc settings are now properly applied (not overwritten by defaults)
2. sed Command Injection Fix:
- Replaced sed with awk for .ralphrc updates in ralph_enable.sh
- awk -v pattern safely handles user input without shell injection risk
3. Shell Injection Fix in safe_create_file():
- Replaced echo with printf '%s\n' for safer content handling
- Prevents issues with backslashes, -n, and special characters
4. Specific Error Codes:
- Added ENABLE_INVALID_ARGS=3 for argument errors
- Added ENABLE_FILE_NOT_FOUND=4 for missing files
- Added ENABLE_DEPENDENCY_MISSING=5 for missing deps (e.g., jq)
- Added ENABLE_PERMISSION_DENIED=6 for permission errors
- Updated ralph_enable.sh and ralph_enable_ci.sh to use specific codes
5. Added tests for .ralphrc loading pattern verification
Test count: 398 (up from 396)
* fix(enable): make --force flag actually overwrite existing files
The --force flag was accepted but safe_create_file() always skipped
existing files regardless of ENABLE_FORCE value.
Changes:
- safe_create_file() now checks ENABLE_FORCE environment variable
- When ENABLE_FORCE="true", overwrites existing files instead of skipping
- Added proper logging for overwrite operations
Added tests:
- Verify enable_ralph_in_directory actually changes file contents with --force
- Test safe_create_file overwrites when ENABLE_FORCE is true
- Test safe_create_file skips when ENABLE_FORCE is false
Test count: 400 (up from 398)
---------
Co-authored-by: Test User <test@example.com>
Root cause: Stale completion indicators in .exit_signals and .response_analysis
files persisted across sessions, causing premature exit when combined with
normal completion indicator increments.
Changes:
- Enhanced reset_session() to clear .exit_signals file (resets to empty structure)
- Enhanced reset_session() to remove .response_analysis file
- Session reset now comprehensively clears all exit-related state
Added 2 new tests:
- reset_session clears exit_signals file to prevent premature exit
- reset_session prevents issue #91 scenario (stale completion indicators)
Test count: 321 (up from 319)
Fixes#91
Merged origin/main into PR branch and applied review feedback:
Review fixes:
- Guard against empty result_obj if jq fails (Macroscope)
- Prioritize result object's session_id over init message (CodeRabbit)
- Add regression test for arrays with session_id only in result element
Resolved conflicts:
- CLAUDE.md: Updated test count to 319
All 319 tests pass.