Skip to content

Commit d762562

Browse files
duyetclaude
andcommitted
feat(ralph-wiggum): add session isolation for multi-session support
State files now stored with session_id suffix to prevent cross-session interference when multiple Claude Code sessions run concurrently. Added: - session-start-hook.sh: Captures session_id at session start - lib/utils.sh: Session isolation utilities (get_session_id, get_state_file_path, etc.) Changed: - State file location: .claude/ralph-*.local.* → .claude/ralph-session.local/{name}.{session_id}.{ext} - All lib modules use dynamic path resolution via utils.sh - Updated docs to reflect new session-based state file naming - Bump version to 1.1.0 Fixed: - Cross-session interference: Stop hook now only processes state belonging to its session, preventing multiple Claude Code sessions from hijacking each other's loops Co-Authored-By: duyetbot <duyetbot@users.noreply.github.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent b18884c commit d762562

14 files changed

Lines changed: 196 additions & 193 deletions

.claude/iteration-log.md

Lines changed: 0 additions & 142 deletions
This file was deleted.

ralph-wiggum/.claude-plugin/plugin.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ralph-wiggum",
3-
"version": "1.0.1",
4-
"description": "Self-referential development loop for Claude Code. Run iterative tasks with automatic progress detection and safety controls.",
3+
"version": "1.1.0",
4+
"description": "Self-referential development loop for Claude Code with session isolation. Run iterative tasks with automatic progress detection and safety controls.",
55
"author": {
66
"name": "duyet",
77
"contributors": ["Daisy Hollman <daisy@anthropic.com>"]

ralph-wiggum/README.md

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -144,20 +144,19 @@ Output <promise>REFACTORED</promise> when complete.
144144
Use the status command or inspect files directly:
145145

146146
```bash
147-
# Quick status check
147+
# Quick status check (shows session ID)
148148
/status
149149

150-
# Loop state file
151-
cat .claude/ralph-loop.local.md
150+
# List all session state files
151+
ls -la .claude/ralph-session.local/
152152

153-
# Circuit breaker state
154-
cat .claude/ralph-circuit.json
155-
156-
# Response analysis
157-
cat .claude/ralph-analysis.json
153+
# View specific session state (session_id shown in /status)
154+
cat .claude/ralph-session.local/ralph-loop.{session_id}.local.md
155+
cat .claude/ralph-session.local/ralph-circuit.{session_id}.json
156+
cat .claude/ralph-session.local/ralph-analysis.{session_id}.json
158157
```
159158

160-
State files are stored in `.claude/ralph-*` in your project directory.
159+
State files are stored in `.claude/ralph-session.local/` with session_id as filename suffix. Each Claude Code session has isolated state. The `.local` suffix ensures gitignore compatibility.
161160

162161
## Configuration
163162

@@ -183,8 +182,10 @@ ralph-wiggum/
183182
│ └── help.md # Plugin documentation
184183
├── hooks/
185184
│ ├── hooks.json # Hook configuration
185+
│ ├── session-start-hook.sh # Session ID capture
186186
│ └── stop-hook.sh # Main loop interceptor
187187
├── lib/
188+
│ ├── utils.sh # Session isolation utilities
188189
│ ├── circuit_breaker.sh # Stagnation detection
189190
│ ├── response_analyzer.sh # Completion analysis
190191
│ ├── api_limit_handler.sh # Rate limit handling
@@ -193,6 +194,14 @@ ralph-wiggum/
193194
├── setup-ralph-loop.sh # Loop initialization
194195
├── status.sh # Status display
195196
└── cancel-ralph.sh # Cancellation handler
197+
198+
Project directory (.claude/):
199+
├── ralph-session.local/
200+
│ ├── ralph-loop.{session_id}.local.md
201+
│ ├── ralph-circuit.{session_id}.json
202+
│ ├── ralph-analysis.{session_id}.json
203+
│ ├── ralph-limits.{session_id}.json
204+
│ └── ralph-tasks.{session_id}.json
196205
```
197206

198207
## When to Use
@@ -220,7 +229,7 @@ ralph-wiggum/
220229
The smart exit feature may trigger on completion keywords. Options:
221230
- Use `--no-smart-exit` to disable completion detection
222231
- Add more specific requirements to your prompt
223-
- Check `.claude/ralph-analysis.json` to see what triggered the exit
232+
- Check `.claude/ralph-session.local/ralph-analysis.{session_id}.json` to see what triggered the exit
224233

225234
### Loop runs forever
226235

@@ -233,7 +242,7 @@ Ensure your prompt includes:
233242

234243
The circuit breaker stops loops that aren't making progress:
235244
- Ensure each iteration changes files
236-
- Check `.claude/ralph-circuit.json` for details
245+
- Check `.claude/ralph-session.local/ralph-circuit.{session_id}.json` for details
237246
- Use `--reset-circuit` to reset state if needed
238247

239248
### Rate limits
@@ -246,19 +255,41 @@ If you hit Claude's API limits:
246255
### Debugging
247256

248257
```bash
249-
# Check all state files
250-
ls -la .claude/ralph-*
258+
# Check all session state files
259+
ls -la .claude/ralph-session.local/
251260

252-
# View loop configuration
253-
cat .claude/ralph-loop.local.md
261+
# View loop configuration (use session_id from /status)
262+
cat .claude/ralph-session.local/ralph-loop.{session_id}.local.md
254263

255264
# Check circuit breaker state
256-
cat .claude/ralph-circuit.json
265+
cat .claude/ralph-session.local/ralph-circuit.{session_id}.json
257266

258267
# Review completion analysis
259-
cat .claude/ralph-analysis.json
268+
cat .claude/ralph-session.local/ralph-analysis.{session_id}.json
260269
```
261270

271+
## Changelog
272+
273+
### [1.1.0] - Session Isolation
274+
275+
**Added**
276+
- Session isolation: State files now stored as `.claude/ralph-session.local/{name}.{session_id}.{ext}`
277+
- SessionStart hook: Captures session_id at session start
278+
- Session ID display: `/status` shows current session ID
279+
- lib/utils.sh: New utility module for session isolation
280+
281+
**Fixed**
282+
- Cross-session interference: Stop hook now only processes state belonging to its session, preventing multiple Claude Code sessions from hijacking each other's loops
283+
284+
**Changed**
285+
- State file location moved from `.claude/ralph-*.local.*` to `.claude/ralph-session.local/{name}.{session_id}.{ext}`
286+
- All library modules use dynamic path resolution
287+
- `.local` suffix on directory ensures gitignore compatibility
288+
289+
### [1.0.1] - Initial Release
290+
291+
Self-referential development loop with circuit breaker, smart exit, and API limit handling.
292+
262293
## References
263294

264295
- [ghuntley.com/ralph](https://ghuntley.com/ralph/)

ralph-wiggum/commands/ralph-loop.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ Execute the setup script to initialize the Ralph loop:
1313
"${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh" $ARGUMENTS
1414
1515
# Display completion promise if set
16-
if [ -f .claude/ralph-loop.local.md ]; then
17-
PROMISE=$(grep '^completion_promise:' .claude/ralph-loop.local.md | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/')
16+
if [ -f "${RALPH_STATE_DIR:-.claude}/ralph-loop.md" ]; then
17+
PROMISE=$(grep '^completion_promise:' "${RALPH_STATE_DIR:-.claude}/ralph-loop.md" | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/')
1818
if [ -n "$PROMISE" ] && [ "$PROMISE" != "null" ]; then
1919
echo ""
2020
echo "==============================================================="

ralph-wiggum/hooks/hooks.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
{
22
"hooks": {
3+
"SessionStart": [
4+
{
5+
"hooks": [
6+
{
7+
"type": "command",
8+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start-hook.sh"
9+
}
10+
]
11+
}
12+
],
313
"Stop": [
414
{
515
"hooks": [
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
3+
# Ralph Wiggum Session Start Hook
4+
# Captures session ID and persists it for use in commands and stop hook
5+
6+
set -euo pipefail
7+
8+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9+
PLUGIN_ROOT="$(dirname "$SCRIPT_DIR")"
10+
LIB_DIR="$PLUGIN_ROOT/lib"
11+
12+
# Load utilities
13+
[[ -f "$LIB_DIR/utils.sh" ]] && source "$LIB_DIR/utils.sh"
14+
15+
# Read hook input from stdin
16+
HOOK_INPUT=$(cat)
17+
18+
# Extract and persist session ID
19+
load_session_from_hook_input "$HOOK_INPUT"
20+
21+
# Session start hook must exit 0 and produce no output
22+
exit 0

ralph-wiggum/hooks/stop-hook.sh

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
99
PLUGIN_ROOT="$(dirname "$SCRIPT_DIR")"
1010
LIB_DIR="$PLUGIN_ROOT/lib"
1111

12-
export RALPH_STATE_DIR=".claude"
12+
# Load utilities for session isolation
13+
[[ -f "$LIB_DIR/utils.sh" ]] && source "$LIB_DIR/utils.sh"
14+
15+
export RALPH_STATE_DIR
16+
RALPH_STATE_DIR=$(get_ralph_state_dir)
1317

1418
load_modules() {
1519
local modules_loaded=0
@@ -21,13 +25,27 @@ load_modules() {
2125
}
2226

2327
HOOK_INPUT=$(cat)
24-
RALPH_STATE_FILE=".claude/ralph-loop.local.md"
28+
HOOK_SESSION_ID=$(echo "$HOOK_INPUT" | jq -r '.session_id')
29+
30+
# Get current session ID from env file if not in hook input
31+
if [[ -z "$HOOK_SESSION_ID" ]] || [[ "$HOOK_SESSION_ID" == "null" ]]; then
32+
HOOK_SESSION_ID=$(get_session_id)
33+
fi
34+
35+
RALPH_STATE_FILE=$(get_state_file_path "ralph-loop" "md")
2536

26-
# No active loop - allow exit
37+
# No active loop in this session - allow exit
2738
if [[ ! -f "$RALPH_STATE_FILE" ]]; then
2839
exit 0
2940
fi
3041

42+
# Verify state file belongs to this session
43+
STATE_SESSION_ID=$(grep '^session_id:' "$RALPH_STATE_FILE" 2>/dev/null | sed 's/session_id: *//' || echo "")
44+
if [[ -n "$STATE_SESSION_ID" ]] && [[ "$STATE_SESSION_ID" != "$HOOK_SESSION_ID" ]]; then
45+
# State file belongs to different session - ignore it
46+
exit 0
47+
fi
48+
3149
MODULES_LOADED=$(load_modules)
3250

3351
# Parse state file frontmatter
@@ -215,8 +233,9 @@ fi
215233

216234
ANALYSIS_STATUS=""
217235
if [[ "$ENABLE_SMART_EXIT" == "true" ]] && type should_exit &>/dev/null; then
218-
[[ -f "${RALPH_STATE_DIR:-.claude}/ralph-analysis.json" ]] && {
219-
LAST_CONF=$(jq -r '.last_confidence // 0' "${RALPH_STATE_DIR:-.claude}/ralph-analysis.json")
236+
ANALYSIS_FILE=$(get_state_file_path "ralph-analysis.json")
237+
[[ -f "$ANALYSIS_FILE" ]] && {
238+
LAST_CONF=$(jq -r '.last_confidence // 0' "$ANALYSIS_FILE")
220239
ANALYSIS_STATUS=" | Confidence: ${LAST_CONF}/40"
221240
}
222241
fi

ralph-wiggum/lib/api_limit_handler.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55

66
set -eo pipefail
77

8-
LIMIT_STATE_FILE="${RALPH_STATE_DIR:-.claude}/ralph-limits.json"
8+
# Use get_state_file_path utility if available, otherwise use RALPH_STATE_DIR
9+
if type get_state_file_path &>/dev/null; then
10+
LIMIT_STATE_FILE=$(get_state_file_path "ralph-limits" "json")
11+
else
12+
LIMIT_STATE_FILE="${RALPH_STATE_DIR:-.claude}/ralph-limits.json"
13+
fi
914
DEFAULT_WAIT_SECONDS=${RALPH_WAIT_SECONDS:-3600}
1015

1116
# Rate limit patterns as space-separated string (portable across bash versions)

0 commit comments

Comments
 (0)