Setting the file. One moment. Validate Hook Schema · Hook Development · anthropics/claude-code · Skills Docs(opens in a new tab)
scripts/validate-hook-schema.sh
Shell·159 lines·5 KB
13 echo " - Required fields"
14 echo " - Hook type validity"
15 echo " - Matcher patterns"
16 echo " - Timeout ranges"
17 exit 1
18fi
19
20HOOKS_FILE="$1"
21
22if [ ! -f "$HOOKS_FILE" ]; then
23 echo "❌ Error: File not found: $HOOKS_FILE"
24 exit 1
25fi
26
27echo "🔍 Validating hooks configuration: $HOOKS_FILE"
28echo ""
29
30# Check 1: Valid JSON
31echo "Checking JSON syntax..."
32if ! jq empty "$HOOKS_FILE" 2>/dev/null; then
33 echo "❌ Invalid JSON syntax"
34 exit 1
35fi
36echo "✅ Valid JSON"
37
38# Check 2: Root structure
39echo ""
40echo "Checking root structure..."
41VALID_EVENTS=("PreToolUse" "PostToolUse" "UserPromptSubmit" "Stop" "SubagentStop" "SessionStart" "SessionEnd" "PreCompact" "Notification")
42
43for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do
44 found=false
45 for valid_event in "${VALID_EVENTS[@]}"; do
46 if [ "$event" = "$valid_event" ]; then
47 found=true
48 break
49 fi
50 done
51
52 if [ "$found" = false ]; then
53 echo "⚠️ Unknown event type: $event"
54 fi
55done
56echo "✅ Root structure valid"
57
58# Check 3: Validate each hook
59echo ""
60echo "Validating individual hooks..."
61
62error_count=0
63warning_count=0
64
65for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do
66 hook_count=$(jq -r ".\"$event\" | length" "$HOOKS_FILE")
67
68 for ((i=0; i<hook_count; i++)); do
69 # Check matcher exists
70 matcher=$(jq -r ".\"$event\"[$i].matcher // empty" "$HOOKS_FILE")
71 if [ -z "$matcher" ]; then
72 echo "❌ $event[$i]: Missing 'matcher' field"
73 ((error_count++))
74 continue
75 fi
76
77 # Check hooks array exists
78 hooks=$(jq -r ".\"$event\"[$i].hooks // empty" "$HOOKS_FILE")
79 if [ -z "$hooks" ] || [ "$hooks" = "null" ]; then
80 echo "❌ $event[$i]: Missing 'hooks' array"
81 ((error_count++))
82 continue
83 fi
84
85 # Validate each hook in the array
86 hook_array_count=$(jq -r ".\"$event\"[$i].hooks | length" "$HOOKS_FILE")
87
88 for ((j=0; j<hook_array_count; j++)); do
89 hook_type=$(jq -r ".\"$event\"[$i].hooks[$j].type // empty" "$HOOKS_FILE")
90
91 if [ -z "$hook_type" ]; then
92 echo "❌ $event[$i].hooks[$j]: Missing 'type' field"
93 ((error_count++))
94 continue
95 fi
96
97 if [ "$hook_type" != "command" ] && [ "$hook_type" != "prompt" ]; then
98 echo "❌ $event[$i].hooks[$j]: Invalid type '$hook_type' (must be 'command' or 'prompt')"
99 ((error_count++))
100 continue
101 fi
102
103 # Check type-specific fields
104 if [ "$hook_type" = "command" ]; then
105 command=$(jq -r ".\"$event\"[$i].hooks[$j].command // empty" "$HOOKS_FILE")
106 if [ -z "$command" ]; then
107 echo "❌ $event[$i].hooks[$j]: Command hooks must have 'command' field"
108 ((error_count++))
109 else
110 # Check for hardcoded paths
111 if [[ "$command" == /* ]] && [[ "$command" != *'${CLAUDE_PLUGIN_ROOT}'* ]]; then
112 echo "⚠️ $event[$i].hooks[$j]: Hardcoded absolute path detected. Consider using \${CLAUDE_PLUGIN_ROOT}"
113 ((warning_count++))
114 fi
115 fi
116 elif [ "$hook_type" = "prompt" ]; then
117 prompt=$(jq -r ".\"$event\"[$i].hooks[$j].prompt // empty" "$HOOKS_FILE")
118 if [ -z "$prompt" ]; then
119 echo "❌ $event[$i].hooks[$j]: Prompt hooks must have 'prompt' field"
120 ((error_count++))
121 fi
122
123 # Check if prompt-based hooks are used on supported events
124 if [ "$event" != "Stop" ] && [ "$event" != "SubagentStop" ] && [ "$event" != "UserPromptSubmit" ] && [ "$event" != "PreToolUse" ]; then
125 echo "⚠️ $event[$i].hooks[$j]: Prompt hooks may not be fully supported on $event (best on Stop, SubagentStop, UserPromptSubmit, PreToolUse)"
126 ((warning_count++))
127 fi
128 fi
129
130 # Check timeout
131 timeout=$(jq -r ".\"$event\"[$i].hooks[$j].timeout // empty" "$HOOKS_FILE")
132 if [ -n "$timeout" ] && [ "$timeout" != "null" ]; then
133 if ! [[ "$timeout" =~ ^[0-9]+$ ]]; then
134 echo "❌ $event[$i].hooks[$j]: Timeout must be a number"
135 ((error_count++))
136 elif [ "$timeout" -gt 600 ]; then
137 echo "⚠️ $event[$i].hooks[$j]: Timeout $timeout seconds is very high (max 600s)"
138 ((warning_count++))
139 elif [ "$timeout" -lt 5 ]; then
140 echo "⚠️ $event[$i].hooks[$j]: Timeout $timeout seconds is very low"
141 ((warning_count++))
142 fi
143 fi
144 done
145 done
146done
147
148echo ""
149echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
150if [ $error_count -eq 0 ] && [ $warning_count -eq 0 ]; then
151 echo "✅ All checks passed!"
152 exit 0
153elif [ $error_count -eq 0 ]; then
154 echo "⚠️ Validation passed with $warning_count warning(s)"
155 exit 0
156else
157 echo "❌ Validation failed with $error_count error(s) and $warning_count warning(s)"
158 exit 1
159fi