From 8644df9ad51fe1dadc5d067e806c3c686efeafff Mon Sep 17 00:00:00 2001 From: sfishman Date: Mon, 2 Mar 2026 22:36:12 +0000 Subject: [PATCH] fix(ralph-loop): isolate loop state to the session that started it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The state file lives at .claude/ralph-loop.local.md — project-scoped, not session-scoped. The plugin's Stop hook fires in every Claude Code session open in that project directory. So if session A starts a loop, session B's Stop events also find the state file and block, feeding A's prompt into B and consuming A's iteration budget. This was masked by the transcript-parsing bug fixed in the previous commit: that bug deleted the state file on the first Stop in any session, so neither session looped. Fixing it exposed the leak. Fix: setup writes CLAUDE_CODE_SESSION_ID into the frontmatter; the hook compares against .session_id from its stdin JSON and exits silently on mismatch. State files without session_id (written by old setup scripts) fall through to preserve existing behavior. --- plugins/ralph-loop/hooks/stop-hook.sh | 10 ++++++++++ plugins/ralph-loop/scripts/setup-ralph-loop.sh | 1 + 2 files changed, 11 insertions(+) diff --git a/plugins/ralph-loop/hooks/stop-hook.sh b/plugins/ralph-loop/hooks/stop-hook.sh index bf3a1ff..ef83af4 100755 --- a/plugins/ralph-loop/hooks/stop-hook.sh +++ b/plugins/ralph-loop/hooks/stop-hook.sh @@ -24,6 +24,16 @@ MAX_ITERATIONS=$(echo "$FRONTMATTER" | grep '^max_iterations:' | sed 's/max_iter # Extract completion_promise and strip surrounding quotes if present COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/') +# Session isolation: the state file is project-scoped, but the Stop hook +# fires in every Claude Code session in that project. If another session +# started the loop, this session must not block (or touch the state file). +# Legacy state files without session_id fall through (preserves old behavior). +STATE_SESSION=$(echo "$FRONTMATTER" | grep '^session_id:' | sed 's/session_id: *//' || true) +HOOK_SESSION=$(echo "$HOOK_INPUT" | jq -r '.session_id // ""') +if [[ -n "$STATE_SESSION" ]] && [[ "$STATE_SESSION" != "$HOOK_SESSION" ]]; then + exit 0 +fi + # Validate numeric fields before arithmetic operations if [[ ! "$ITERATION" =~ ^[0-9]+$ ]]; then echo "⚠️ Ralph loop: State file corrupted" >&2 diff --git a/plugins/ralph-loop/scripts/setup-ralph-loop.sh b/plugins/ralph-loop/scripts/setup-ralph-loop.sh index 3d41db4..c0897d4 100755 --- a/plugins/ralph-loop/scripts/setup-ralph-loop.sh +++ b/plugins/ralph-loop/scripts/setup-ralph-loop.sh @@ -141,6 +141,7 @@ cat > .claude/ralph-loop.local.md <