#!/bin/bash # Stop hook management for the agent loop. # # The Stop hook sends SIGINT to the loop's parent process when a claude session # finishes. This is how claude signals "I'm done" back to the bash loop — # the Ralph pattern (ghuntley.com/ralph). # # Without this hook, claude would exit to an interactive prompt instead of # returning control to the loop script. # # IMPORTANT: The hook is scoped to only fire inside the agent-loop tmux session. # Without this guard, ANY Claude Code session opened in the same project directory # would pick up the hook and kill its own parent shell on exit. SETTINGS_FILE="${PROJECT_ROOT}/.claude/settings.local.json" # The hook command checks we're inside the agent-loop tmux session before killing. # This prevents the hook from affecting other CC sessions open in the same project. # IMPORTANT: Must check $TMUX first — tmux display-message can succeed even outside # tmux by falling back to the most recently created session, which would be agent-loop. HOOK_COMMAND='[ -n "$TMUX" ] && tmux display-message -p "#{session_name}" 2>/dev/null | grep -q "^agent-loop$" && kill -INT $PPID || true' install_hooks() { if [ ! -f "$SETTINGS_FILE" ]; then mkdir -p "$(dirname "$SETTINGS_FILE")" echo '{}' > "$SETTINGS_FILE" fi if command -v jq &>/dev/null; then jq --arg cmd "$HOOK_COMMAND" \ '.hooks.Stop = [{"matcher": "", "hooks": [{"type": "command", "command": $cmd}]}]' \ "$SETTINGS_FILE" > "${SETTINGS_FILE}.tmp" && mv "${SETTINGS_FILE}.tmp" "$SETTINGS_FILE" else LOOP_HOOK_CMD="$HOOK_COMMAND" LOOP_SETTINGS="$SETTINGS_FILE" python3 -c " import json, os p = os.environ['LOOP_SETTINGS'] cmd = os.environ['LOOP_HOOK_CMD'] s = json.load(open(p)) if os.path.exists(p) else {} s.setdefault('hooks', {})['Stop'] = [{'matcher': '', 'hooks': [{'type': 'command', 'command': cmd}]}] json.dump(s, open(p, 'w'), indent=2) " fi log "Stop hook installed" } remove_hooks() { if [ -f "$SETTINGS_FILE" ]; then if command -v jq &>/dev/null; then jq 'del(.hooks.Stop)' "$SETTINGS_FILE" > "${SETTINGS_FILE}.tmp" && mv "${SETTINGS_FILE}.tmp" "$SETTINGS_FILE" jq 'if .hooks == {} then del(.hooks) else . end' "$SETTINGS_FILE" > "${SETTINGS_FILE}.tmp" && mv "${SETTINGS_FILE}.tmp" "$SETTINGS_FILE" else LOOP_SETTINGS="$SETTINGS_FILE" python3 -c " import json, os p = os.environ['LOOP_SETTINGS'] s = json.load(open(p)) s.get('hooks', {}).pop('Stop', None) if not s.get('hooks'): s.pop('hooks', None) json.dump(s, open(p, 'w'), indent=2) " fi log "Stop hook removed" fi }