Skip to content

Commit 97fb96c

Browse files
fix(hooks): stop invalid-JSON PreToolUse/Stop hook errors (hotfix)
Three hooks emitted output that failed Claude Code's hook JSON validation ("Hook JSON output validation failed — (root): Invalid input"): - subagent-stop-universal.sh: a stale `cat <<EOF` leaked the literal text ": # FIXED: bare JSON decision approve removed" to stdout. SubagentStop approves on a clean exit 0 with empty stdout, so the heredoc is removed. - stop-slop-hook.sh: `trap 'echo {...}' ERR EXIT` plus early-return branches that exited without clearing the trap emitted `{"continue": true}` TWICE ("Extra data"). Rewritten so success = clean exit 0, advisories to stderr. - action-report-tracker.sh: same trap double-emit in the missing-library early branch. Clear the trap before the early echo. Adds scripts/validate-hooks-settings.py: validates every settings source is parseable JSON, that each hook command resolves to an existing executable, and that each hook (run with cwd in-repo AND out-of-repo) returns stdout that is empty or valid hook JSON per tests/HOOK_FORMAT_REFERENCE.md. Note: the related stale context-mode `latest/` path was a duplicate in the global ~/.claude/settings.json (not versioned); removed separately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8daf245 commit 97fb96c

4 files changed

Lines changed: 343 additions & 52 deletions

File tree

.claude/hooks/action-report-tracker.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ trap 'echo "{\"continue\": true}"' ERR EXIT
2020
# Load report generator library
2121
REPORT_GENERATOR=".claude/lib/action-report-generator.sh"
2222
if [[ ! -f "$REPORT_GENERATOR" ]]; then
23-
echo "{\"continue\": true}"
23+
trap - ERR EXIT # clear trap first so it does not emit a SECOND JSON object
24+
echo '{"continue": true}'
2425
exit 0
2526
fi
2627

.claude/hooks/stop-slop-hook.sh

Lines changed: 42 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,55 @@
1-
#!/bin/bash
2-
umask 077
31
#!/usr/bin/env bash
2+
umask 077
43
# Stop-Slop Hook - Detects AI writing patterns
5-
# VERSION: 1.0.2
6-
# Purpose: Detect filler phrases and AI writing patterns in prose
4+
# VERSION: 1.1.0
5+
# Purpose: Detect filler phrases / AI writing patterns in prose (advisory only).
76
#
8-
# FIX v1.0.2: LOW-003 - Added consistent error handling with JSON output
9-
# FIX v1.0.1: HIGH-001 - Fixed ReDoS vulnerability using -F flag for fixed string matching
10-
11-
set -euo pipefail
12-
13-
# LOW-003 FIX: Error trap ensures valid JSON output on failure
14-
trap 'echo "{\"continue\": true}"' ERR EXIT
7+
# Output contract (see tests/HOOK_FORMAT_REFERENCE.md):
8+
# - This hook never blocks. "Allow" == clean exit 0 with EMPTY stdout, which
9+
# is valid for Stop AND PostToolUse/PreToolUse events.
10+
# - Any advisory message goes to STDERR, never stdout (stdout must be JSON or
11+
# empty, otherwise CC reports "(root): Invalid input").
12+
#
13+
# FIX v1.1.0: removed the `trap ... ERR EXIT` double-emit bug (early-return
14+
# branches exited without clearing the EXIT trap, printing a 2nd JSON object →
15+
# "Hook JSON output validation failed — (root): Invalid input"). Success is now
16+
# a clean exit 0 with no stdout, so there is nothing to duplicate.
1517

16-
readonly VERSION="1.0.0"
18+
set -uo pipefail
1719

18-
# Read stdin
19-
INPUT=$(head -c 100000)
20+
# Read stdin (length-limited per SEC-111). Never fail the hook on read issues.
21+
INPUT=$(head -c 100000 || true)
2022

21-
# Parse input
22-
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
23+
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null || true)
2324

24-
if [[ -z "$FILE_PATH" ]] || [[ ! -f "$FILE_PATH" ]]; then
25-
echo '{"continue": true}'
25+
# No file to inspect (e.g. Stop event, or non-file tool) → allow.
26+
if [[ -z "$FILE_PATH" || ! -f "$FILE_PATH" ]]; then
2627
exit 0
2728
fi
2829

29-
# Check for common AI filler phrases
30-
if [[ -f "$FILE_PATH" ]]; then
31-
FILLER_PHRASES=(
32-
"Certainly!"
33-
"It is important to note"
34-
"It's worth noting"
35-
"It's important to remember"
36-
"Please note"
37-
"Keep in mind"
38-
"It should be noted"
39-
"It's worth mentioning"
40-
)
41-
42-
FINDINGS=0
43-
for phrase in "${FILLER_PHRASES[@]}"; do
44-
# HIGH-001 FIX: Use -F for fixed string matching (prevents ReDoS via regex injection)
45-
if grep -qiF -- "$phrase" "$FILE_PATH"; then
46-
FINDINGS=$((FINDINGS + 1))
47-
fi
48-
done
49-
50-
if [[ $FINDINGS -gt 0 ]]; then
51-
echo "⚠️ Stop-Slop: Found $FINDINGS filler phrases"
52-
# LOW-003 FIX: Clear trap before normal exit to prevent duplicate JSON
53-
trap - ERR EXIT
54-
echo '{"continue": true}'
55-
exit 0
30+
FILLER_PHRASES=(
31+
"Certainly!"
32+
"It is important to note"
33+
"It's worth noting"
34+
"It's important to remember"
35+
"Please note"
36+
"Keep in mind"
37+
"It should be noted"
38+
"It's worth mentioning"
39+
)
40+
41+
findings=0
42+
for phrase in "${FILLER_PHRASES[@]}"; do
43+
# -F fixed-string match (prevents ReDoS via regex injection — HIGH-001)
44+
if grep -qiF -- "$phrase" "$FILE_PATH" 2>/dev/null; then
45+
findings=$((findings + 1))
5646
fi
47+
done
48+
49+
if [[ "$findings" -gt 0 ]]; then
50+
# Advisory only — to STDERR so stdout stays empty/valid.
51+
echo "⚠️ Stop-Slop: found $findings filler phrase pattern(s) in $FILE_PATH" >&2
5752
fi
5853

59-
# LOW-003 FIX: Clear trap and output success
60-
trap - ERR EXIT
61-
echo '{"continue": true}'
54+
# Allow: clean exit 0, empty stdout.
55+
exit 0

.claude/hooks/subagent-stop-universal.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ fi
116116
# ============================================
117117

118118
log "APPROVE: Subagent ${agent_id} (${agent_type}) can stop"
119-
cat <<EOF
120-
: # FIXED: bare JSON decision approve removed
121-
EOF
119+
# SubagentStop: a clean exit 0 with EMPTY stdout means "approve stop".
120+
# Emitting any non-JSON text (or {"decision":"approve"}) triggers
121+
# "Hook JSON output validation failed — (root): Invalid input".
122122
exit 0

0 commit comments

Comments
 (0)