[BUG] ralph-wiggum stop hook: all four state-file error handlers are unreachable
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
Note: this is a bug in the ralph-wiggum plugin's stop hook in this repo, not in the Claude Code CLI. The version / platform / terminal fields below are required by the template but don't apply — filled in as accurately as I can.
What's Wrong?
plugins/ralph-wiggum/hooks/stop-hook.sh contains four carefully written error handlers for a corrupted state file. None of them can run.
Line 7 sets set -euo pipefail. Line 22 then assigns from a pipeline whose failure is an expected condition:
ITERATION=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//')
If iteration: is absent, grep exits 1. Under pipefail that fails the pipeline; under set -e the assignment aborts the script. Execution never reaches the validation block on line 28 that was written to report exactly this.
The same pattern appears at lines 23 and 25 (max_iterations, completion_promise), at line 58 (jq on the hook input), and at line 90:
LAST_OUTPUT=$(echo "$LAST_LINE" | jq -r '...' 2>&1)
# Check if jq succeeded
if [[ $? -ne 0 ]]; then # line 98 — never reached
If jq fails, set -e kills the script at line 90. The $? check on line 98 is dead code. ($? would hold the right value there — the problem is purely that set -e fires first.)
Why this matters beyond the missing message: each of those handlers ends with rm "$RALPH_STATE_FILE", removing the broken state file so the user can start fresh. Since the handlers never run, the corrupt file is left on disk and the hook exits non-zero with no output at all. The user gets no explanation, and the same silent failure recurs on every subsequent stop.
What Should Happen?
A corrupt or hand-edited state file should produce the diagnostic the script already contains, and the state file should be removed so /ralph-loop can be run again cleanly.
Steps to Reproduce
mkdir -p /tmp/ralphtest/.claude && cd /tmp/ralphtest
# state file with the 'iteration' field missing
printf -- '---\nmax_iterations: 5\ncompletion_promise: null\n---\nDo the thing\n' \
> .claude/ralph-loop.local.md
echo '{"transcript_path":"/nonexistent"}' | bash /path/to/plugins/ralph-wiggum/hooks/stop-hook.sh
echo "exit=$?"
ls .claude/
Actual:
exit=1
ralph-loop.local.md <- still there
No output. The state file remains, so the loop stays stuck.
Expected (what the code on lines 28-37 was written to do):
⚠️ Ralph loop: State file corrupted
File: .claude/ralph-loop.local.md
Problem: 'iteration' field is not a valid number (got: '')
This usually means the state file was manually edited or corrupted.
Ralph loop is stopping. Run /ralph-loop again to start fresh.
exit=0
with the state file removed.
Error Messages/Logs
None — that is the bug. The hook exits 1 silently.
Is this a regression?
I don't know
Claude Code Version
N/A — plugins/ralph-wiggum/hooks/stop-hook.sh in this repository, not the CLI
Platform
Other — not applicable
Operating System
macOS (reproduced on bash 3.2 and bash 5)
Terminal/Shell
Terminal.app (macOS)
Additional Information
The fix is small: guard the frontmatter extractions with || true, and convert the jq parse to if ! LAST_OUTPUT=$(...); then, so failures reach the handlers already written for them.
I have this implemented and verified. Against the reproduction above: before, exit 1 with no output and the state file left in place; after, the intended message prints and the file is removed. The normal loop path produces byte-identical output before and after, so nothing changes for a healthy state file.
Happy to open a PR if the approach looks right.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗