Context
Running RLCR on a multi-session setup (two Claude Code instances in separate terminals, same user). The stop hook from the humanize RLCR loop bled into a completely unrelated Claude Code session, hijacking it and causing both instances to interfere with the humanize project.
- Humanize version: 1.14.0
- Claude Code version: 2.1.91
- Platform: Linux (NFS-mounted home directory,
~/.claude is a symlink to NFS path)
Root Cause
The session_id field in state.md is never populated, despite the session_id plumbing (signal file + PostToolUse hook) being present in v1.14.0. This causes the backward-compatibility fallback in find_active_loop() to match any session, breaking session isolation.
The double-slash path mismatch
The failure chain:
-
CLAUDE_PLUGIN_ROOT has a trailing slash. The Claude Code framework sets it to:
~/.claude/plugins/marketplaces/humania/
-
Command template concatenation produces a double slash. commands/start-rlcr-loop.md line 65:
"${CLAUDE_PLUGIN_ROOT}/scripts/setup-rlcr-loop.sh" $ARGUMENTS
Resulting command (confirmed from session transcript):
"~/.claude/plugins/marketplaces/humania//scripts/setup-rlcr-loop.sh" plan.md ...
Note: humania//scripts — double slash.
-
Setup script normalizes its own path. setup-rlcr-loop.sh lines 26, 822-823:
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
SCRIPT_SELF_PATH="$SCRIPT_DIR/$(basename "${BASH_SOURCE[0]:-$0}")"
cd + pwd normalizes the path, removing the double slash:
~/.claude/plugins/marketplaces/humania/scripts/setup-rlcr-loop.sh
This normalized path is written to .humanize/.pending-session-id as COMMAND_SIGNATURE.
-
PostToolUse hook does strict string comparison. loop-post-bash-hook.sh lines 82-86:
if [[ "$HOOK_COMMAND" == "\"${COMMAND_SIGNATURE}\"" ]] || \
[[ "$HOOK_COMMAND" == "\"${COMMAND_SIGNATURE}\""[[:space:]]* ]]; then
IS_SETUP="true"
HOOK_COMMAND (from tool_input.command): "...humania//scripts/..." (double slash, as Claude typed it)
COMMAND_SIGNATURE (from signal file): "...humania/scripts/..." (single slash, normalized by cd+pwd)
- Result: no match →
exit 0 → signal file not consumed → session_id never written.
-
Empty session_id matches any session. loop-common.sh line 278:
if [[ -z "$stored_session_id" ]] || [[ "$stored_session_id" == "$filter_session_id" ]]; then
Any Claude Code instance's Stop event matches the loop → cross-session hijacking.
Evidence
Signal file still unconsumed (never accessed since creation):
$ stat .humanize/.pending-session-id
Access: 2026-04-03 23:28:47
Modify: 2026-04-03 23:28:47
$ cat .humanize/.pending-session-id
<project_dir>/.humanize/rlcr/2026-04-03_23-28-47/state.md
<home>/.claude/plugins/marketplaces/humania/scripts/setup-rlcr-loop.sh
state.md session_id is empty (all 6 historical loops have the same problem):
Session transcript proving the double-slash invocation:
{
"tool_input": {
"command": "\"<home>/.claude/plugins/marketplaces/humania//scripts/setup-rlcr-loop.sh\" plan.md --codex-model gpt-5.4:high --max 20 --codex-timeout 10800 --track-plan-file"
}
}
Reproduction of the matching failure:
COMMAND_SIGNATURE="<home>/.claude/plugins/marketplaces/humania/scripts/setup-rlcr-loop.sh"
HOOK_COMMAND='"<home>/.claude/plugins/marketplaces/humania//scripts/setup-rlcr-loop.sh" plan.md ...'
[[ "$HOOK_COMMAND" == "\"${COMMAND_SIGNATURE}\""* ]] # → false (double slash ≠ single slash)
Impact
- All RLCR loops started via
/start-rlcr-loop have empty session_id when CLAUDE_PLUGIN_ROOT has a trailing slash.
- Cross-session stop hook hijacking: any concurrent Claude Code instance's Stop event can claim the RLCR loop.
- Confirmed on all 6 historical loops in this project (2026-03-27 through 2026-04-03).
Suggested Fixes
Option A: Normalize paths before comparison (minimal, recommended)
In loop-post-bash-hook.sh, normalize both paths before the boundary-aware match:
# Normalize consecutive slashes for robust comparison
COMMAND_SIGNATURE=$(printf '%s' "$COMMAND_SIGNATURE" | tr -s '/')
HOOK_COMMAND=$(printf '%s' "$HOOK_COMMAND" | tr -s '/')
Option B: Use realpath for canonical comparison
COMMAND_SIGNATURE=$(realpath -m "$COMMAND_SIGNATURE" 2>/dev/null || echo "$COMMAND_SIGNATURE")
# And normalize HOOK_COMMAND similarly after extracting the path portion
Option C: Don't depend on command string matching at all
Write session_id directly inside setup-rlcr-loop.sh using $CLAUDE_SESSION_ID env var (if available), or accept the session_id as a CLI argument passed from the command template.
Framework-side fix
Claude Code should strip trailing slashes from CLAUDE_PLUGIN_ROOT before setting it, or normalize the path.
Quantitative Summary
| Metric |
Value |
| Affected loops |
6/6 (100%) |
session_id populated |
0/6 |
| Signal files consumed |
0/6 |
| Cross-session hijack events observed |
≥1 confirmed |
| Root cause |
1 character: trailing / in CLAUDE_PLUGIN_ROOT |
Context
Running RLCR on a multi-session setup (two Claude Code instances in separate terminals, same user). The stop hook from the humanize RLCR loop bled into a completely unrelated Claude Code session, hijacking it and causing both instances to interfere with the humanize project.
~/.claudeis a symlink to NFS path)Root Cause
The
session_idfield instate.mdis never populated, despite the session_id plumbing (signal file + PostToolUse hook) being present in v1.14.0. This causes the backward-compatibility fallback infind_active_loop()to match any session, breaking session isolation.The double-slash path mismatch
The failure chain:
CLAUDE_PLUGIN_ROOThas a trailing slash. The Claude Code framework sets it to:Command template concatenation produces a double slash.
commands/start-rlcr-loop.mdline 65:Resulting command (confirmed from session transcript):
Note:
humania//scripts— double slash.Setup script normalizes its own path.
setup-rlcr-loop.shlines 26, 822-823:cd + pwdnormalizes the path, removing the double slash:This normalized path is written to
.humanize/.pending-session-idasCOMMAND_SIGNATURE.PostToolUse hook does strict string comparison.
loop-post-bash-hook.shlines 82-86:HOOK_COMMAND(fromtool_input.command):"...humania//scripts/..."(double slash, as Claude typed it)COMMAND_SIGNATURE(from signal file):"...humania/scripts/..."(single slash, normalized bycd+pwd)exit 0→ signal file not consumed →session_idnever written.Empty
session_idmatches any session.loop-common.shline 278:Any Claude Code instance's Stop event matches the loop → cross-session hijacking.
Evidence
Signal file still unconsumed (never accessed since creation):
state.md
session_idis empty (all 6 historical loops have the same problem):session_id:Session transcript proving the double-slash invocation:
{ "tool_input": { "command": "\"<home>/.claude/plugins/marketplaces/humania//scripts/setup-rlcr-loop.sh\" plan.md --codex-model gpt-5.4:high --max 20 --codex-timeout 10800 --track-plan-file" } }Reproduction of the matching failure:
Impact
/start-rlcr-loophave emptysession_idwhenCLAUDE_PLUGIN_ROOThas a trailing slash.Suggested Fixes
Option A: Normalize paths before comparison (minimal, recommended)
In
loop-post-bash-hook.sh, normalize both paths before the boundary-aware match:Option B: Use
realpathfor canonical comparisonOption C: Don't depend on command string matching at all
Write
session_iddirectly insidesetup-rlcr-loop.shusing$CLAUDE_SESSION_IDenv var (if available), or accept the session_id as a CLI argument passed from the command template.Framework-side fix
Claude Code should strip trailing slashes from
CLAUDE_PLUGIN_ROOTbefore setting it, or normalize the path.Quantitative Summary
session_idpopulated/inCLAUDE_PLUGIN_ROOT