[BUG] ralph-loop plugin fails when last assistant message is tool_use only (no text content)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The ralph-loop stop hook fails intermittently because it only checks the last assistant message for text type content. When the assistant's last message contains only tool_use or thinking blocks (no text), the hook outputs:
⚠️ Ralph loop: Assistant message contained no text content
Ralph loop is stopping.
...and deletes the state file, breaking the loop unexpectedly.
Root Cause
In plugins/ralph-wiggum/hooks/stop-hook.sh lines 90-112:
LAST_OUTPUT=$(echo "$LAST_LINE" | jq -r '
.message.content |
map(select(.type == "text")) |
map(.text) |
join("\n")
' 2>&1)
# ...
if [[ -z "$LAST_OUTPUT" ]]; then
echo "⚠️ Ralph loop: Assistant message contained no text content" >&2
echo " Ralph loop is stopping." >&2
rm "$RALPH_STATE_FILE"
exit 0
fi
The hook assumes the last assistant message always has text type content. However, when Claude does tool calls, the message often contains only tool_use blocks without any text block.
What Should Happen?
The hook should:
- Search backwards through recent assistant messages to find one with text content, OR
- Recognize
tool_useas valid activity (assistant is still working) and continue the loop
Suggested Fix
Replace lines 80-112 with:
# FIX: Search backwards through recent assistant messages to find one with text content
# The last message might be tool_use only, so we need to look further back
LAST_OUTPUT=""
MESSAGES_TO_CHECK=10 # Check last 10 assistant messages
# Get last N assistant messages and search for text content
while IFS= read -r line; do
# Try to extract text content from this message
TEXT_CONTENT=$(echo "$line" | jq -r '
.message.content // [] |
map(select(.type == "text")) |
map(.text) |
join("\n")
' 2>/dev/null || echo "")
if [[ -n "$TEXT_CONTENT" ]]; then
LAST_OUTPUT="$TEXT_CONTENT"
break
fi
done < <(grep '"role":"assistant"' "$TRANSCRIPT_PATH" | tail -"$MESSAGES_TO_CHECK" | tac)
# If still no text found, check if there's at least tool_use activity (assistant is active)
if [[ -z "$LAST_OUTPUT" ]]; then
# Check if last message has tool_use - this means assistant is actively working
LAST_LINE=$(grep '"role":"assistant"' "$TRANSCRIPT_PATH" | tail -1)
HAS_TOOL_USE=$(echo "$LAST_LINE" | jq -r '
.message.content // [] |
map(select(.type == "tool_use")) |
length
' 2>/dev/null || echo "0")
if [[ "$HAS_TOOL_USE" -gt 0 ]]; then
# Assistant is doing tool calls - continue the loop without needing text
LAST_OUTPUT="[Assistant is executing tools]"
else
echo "⚠️ Ralph loop: No text content found in recent assistant messages" >&2
echo " Checked last $MESSAGES_TO_CHECK messages" >&2
echo " Ralph loop is stopping." >&2
rm "$RALPH_STATE_FILE"
exit 0
fi
fi
Steps to Reproduce
- Install ralph-loop plugin
- Start a ralph loop:
/ralph-loop "Do some task" --max-iterations 5 - During iteration, do multiple tool calls without text output between them
- Exit (loop should continue)
- Observe: Loop breaks with "Assistant message contained no text content"
Environment
- Claude Code version: 2.1.31
- Platform: Linux
- The bug is in the plugin code itself, so it affects all platforms
Additional Context
This bug causes ralph-loop to appear "unreliable" or "intermittent" - it works when the last message has text, fails when it's tool_use only. The fix above has been tested and successfully runs 5+ iterations without breaking.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗