Skip to content

fix(ralph-loop): session isolation bypass when CLAUDE_CODE_SESSION_ID is empty#1318

Closed
pure-maple wants to merge 1 commit intoanthropics:mainfrom
pure-maple:fix/ralph-loop-session-isolation
Closed

fix(ralph-loop): session isolation bypass when CLAUDE_CODE_SESSION_ID is empty#1318
pure-maple wants to merge 1 commit intoanthropics:mainfrom
pure-maple:fix/ralph-loop-session-isolation

Conversation

@pure-maple
Copy link
Copy Markdown

Summary

  • Fix Ralph Loop cross-session interference when CLAUDE_CODE_SESSION_ID is empty in Bash execution context
  • Fix completion promise false positive when no <promise> tag exists in output

Problem

When CLAUDE_CODE_SESSION_ID is not set (which happens in the Bash tool execution context), setup-ralph-loop.sh writes an empty session_id to the state file. The stop hook's isolation check ([[ -n "$STATE_SESSION" ]]) treats empty as "legacy" and falls through, causing the Ralph Loop to intercept all sessions in the same project directory — not just the session that started it.

In our case, a Ralph Loop started in a learning-system research session leaked into 2 unrelated sessions (work tasks and a migration task), burning hundreds of conversation turns before being noticed.

Additionally, the Perl -pe substitution for <promise> tag extraction preserves the full input text when no tag is found. If the raw assistant output happens to equal the completion promise string, the loop exits prematurely (false positive).

Fix

setup-ralph-loop.sh (line 144):

  • Fallback to uuidgen when CLAUDE_CODE_SESSION_ID is empty, ensuring session_id is always non-empty

stop-hook.sh (lines 27-35):

  • Treat empty STATE_SESSION as invalid — exit 0 instead of falling through
  • Split compound conditional into two clear checks for readability

stop-hook.sh (line 136):

  • Replace perl -pe substitution with perl -ne extraction that only prints content when <promise> tag actually exists

Test plan

  • Start Ralph Loop in session A, verify session B in same project is not intercepted
  • Start Ralph Loop with CLAUDE_CODE_SESSION_ID unset, verify fallback UUID is written
  • Verify completion promise detection still works with <promise> tags
  • Verify no false positive when output contains promise text without <promise> tags

…positive

Session isolation bug:
- CLAUDE_CODE_SESSION_ID is empty in Bash tool execution context
- setup-ralph-loop.sh writes empty session_id to state file
- stop-hook.sh treats empty session_id as "legacy" and skips isolation
- Result: Ralph Loop in one session intercepts ALL sessions in the same project

Fix:
- setup: fallback to uuidgen when CLAUDE_CODE_SESSION_ID is empty
- hook: treat empty STATE_SESSION as invalid, exit 0 (don't intercept)

Completion promise false positive:
- Perl -pe substitution preserves full input when no <promise> tag exists
- If raw output happens to equal the promise text, loop exits prematurely

Fix:
- Use -ne with explicit match check, only print extracted tag content
Copilot AI review requested due to automatic review settings April 9, 2026 10:03
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

Thanks for your interest! This repo only accepts contributions from Anthropic team members. If you'd like to submit a plugin to the marketplace, please submit your plugin here.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes session isolation and completion-promise detection in the Ralph Loop plugin to prevent cross-session interception and premature loop termination when <promise> tags are absent.

Changes:

  • Ensure session_id in Ralph Loop state is never empty by generating a fallback ID during setup.
  • Tighten Stop-hook session isolation logic to avoid intercepting when isolation can’t be enforced.
  • Update <promise> extraction to return empty output when no <promise> tag exists (prevents false positives).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
plugins/ralph-loop/scripts/setup-ralph-loop.sh Writes a non-empty session_id into the Ralph Loop state file using a UUID fallback.
plugins/ralph-loop/hooks/stop-hook.sh Enforces session isolation more explicitly and makes <promise> extraction non-matching-safe.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

active: true
iteration: 1
session_id: ${CLAUDE_CODE_SESSION_ID:-}
session_id: ${CLAUDE_CODE_SESSION_ID:-$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null || echo "fallback-$$-$(date +%s)")}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

session_id is written from $CLAUDE_CODE_SESSION_ID, but when that variable is empty you now generate a new UUID. The Stop hook later enforces isolation by comparing STATE_SESSION (from this file) to HOOK_INPUT.session_id; if $CLAUDE_CODE_SESSION_ID is not the same identifier as HOOK_INPUT.session_id (or is unset in the Bash tool context as described), the generated UUID will never match any real session and the loop will never intercept even the session that started it. Consider obtaining the same session identifier the Stop hook uses (e.g., pass it into this script explicitly), or change the Stop hook to "claim"/persist the first non-empty HOOK_INPUT.session_id into the state file instead of generating a random value here.

Copilot uses AI. Check for mistakes.
Comment on lines +136 to +138
# Use a dedicated extraction that returns empty string when no <promise> tag exists,
# preventing false positives when the raw output happens to match the promise text.
PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -ne 'if (/<promise>(.*?)<\/promise>/s) { my $t = $1; $t =~ s/^\s+|\s+$//g; $t =~ s/\s+/ /g; print $t; }' 2>/dev/null || echo "")
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using echo "$LAST_OUTPUT" can mis-handle edge cases where the output begins with -n/-e (option parsing) or contains backslash escapes, which can alter the string being scanned for <promise> and lead to incorrect completion detection. Prefer printf '%s' "$LAST_OUTPUT" (or an equivalent non-echo write) when feeding arbitrary text into Perl.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants