Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Guidance for developing lifecycle hooks for Claude Code plugins from the official Anthropic repository.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/validate-hook-schema.sh
1#!/bin/bash2# Hook Schema Validator3# Validates hooks.json structure and checks for common issues45set -euo pipefail67# Usage8if [ $# -eq 0 ]; then9echo "Usage: $0 <path/to/hooks.json>"10echo ""11echo "Validates hook configuration file for:"12echo " - Valid JSON syntax"13echo " - Required fields"14echo " - Hook type validity"15echo " - Matcher patterns"16echo " - Timeout ranges"17exit 118fi1920HOOKS_FILE="$1"2122if [ ! -f "$HOOKS_FILE" ]; then23echo "โ Error: File not found: $HOOKS_FILE"24exit 125fi2627echo "๐ Validating hooks configuration: $HOOKS_FILE"28echo ""2930# Check 1: Valid JSON31echo "Checking JSON syntax..."32if ! jq empty "$HOOKS_FILE" 2>/dev/null; then33echo "โ Invalid JSON syntax"34exit 135fi36echo "โ Valid JSON"3738# Check 2: Root structure39echo ""40echo "Checking root structure..."41VALID_EVENTS=("PreToolUse" "PostToolUse" "UserPromptSubmit" "Stop" "SubagentStop" "SessionStart" "SessionEnd" "PreCompact" "Notification")4243for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do44found=false45for valid_event in "${VALID_EVENTS[@]}"; do46if [ "$event" = "$valid_event" ]; then47found=true48break49fi50done5152if [ "$found" = false ]; then53echo "โ ๏ธ Unknown event type: $event"54fi55done56echo "โ Root structure valid"5758# Check 3: Validate each hook59echo ""60echo "Validating individual hooks..."6162error_count=063warning_count=06465for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do66hook_count=$(jq -r ".\"$event\" | length" "$HOOKS_FILE")6768for ((i=0; i<hook_count; i++)); do69# Check matcher exists70matcher=$(jq -r ".\"$event\"[$i].matcher // empty" "$HOOKS_FILE")71if [ -z "$matcher" ]; then72echo "โ $event[$i]: Missing 'matcher' field"73((error_count++))74continue75fi7677# Check hooks array exists78hooks=$(jq -r ".\"$event\"[$i].hooks // empty" "$HOOKS_FILE")79if [ -z "$hooks" ] || [ "$hooks" = "null" ]; then80echo "โ $event[$i]: Missing 'hooks' array"81((error_count++))82continue83fi8485# Validate each hook in the array86hook_array_count=$(jq -r ".\"$event\"[$i].hooks | length" "$HOOKS_FILE")8788for ((j=0; j<hook_array_count; j++)); do89hook_type=$(jq -r ".\"$event\"[$i].hooks[$j].type // empty" "$HOOKS_FILE")9091if [ -z "$hook_type" ]; then92echo "โ $event[$i].hooks[$j]: Missing 'type' field"93((error_count++))94continue95fi9697if [ "$hook_type" != "command" ] && [ "$hook_type" != "prompt" ]; then98echo "โ $event[$i].hooks[$j]: Invalid type '$hook_type' (must be 'command' or 'prompt')"99((error_count++))100continue101fi102103# Check type-specific fields104if [ "$hook_type" = "command" ]; then105command=$(jq -r ".\"$event\"[$i].hooks[$j].command // empty" "$HOOKS_FILE")106if [ -z "$command" ]; then107echo "โ $event[$i].hooks[$j]: Command hooks must have 'command' field"108((error_count++))109else110# Check for hardcoded paths111if [[ "$command" == /* ]] && [[ "$command" != *'${CLAUDE_PLUGIN_ROOT}'* ]]; then112echo "โ ๏ธ $event[$i].hooks[$j]: Hardcoded absolute path detected. Consider using \${CLAUDE_PLUGIN_ROOT}"113((warning_count++))114fi115fi116elif [ "$hook_type" = "prompt" ]; then117prompt=$(jq -r ".\"$event\"[$i].hooks[$j].prompt // empty" "$HOOKS_FILE")118if [ -z "$prompt" ]; then119echo "โ $event[$i].hooks[$j]: Prompt hooks must have 'prompt' field"120((error_count++))121fi122123# Check if prompt-based hooks are used on supported events124if [ "$event" != "Stop" ] && [ "$event" != "SubagentStop" ] && [ "$event" != "UserPromptSubmit" ] && [ "$event" != "PreToolUse" ]; then125echo "โ ๏ธ $event[$i].hooks[$j]: Prompt hooks may not be fully supported on $event (best on Stop, SubagentStop, UserPromptSubmit, PreToolUse)"126((warning_count++))127fi128fi129130# Check timeout131timeout=$(jq -r ".\"$event\"[$i].hooks[$j].timeout // empty" "$HOOKS_FILE")132if [ -n "$timeout" ] && [ "$timeout" != "null" ]; then133if ! [[ "$timeout" =~ ^[0-9]+$ ]]; then134echo "โ $event[$i].hooks[$j]: Timeout must be a number"135((error_count++))136elif [ "$timeout" -gt 600 ]; then137echo "โ ๏ธ $event[$i].hooks[$j]: Timeout $timeout seconds is very high (max 600s)"138((warning_count++))139elif [ "$timeout" -lt 5 ]; then140echo "โ ๏ธ $event[$i].hooks[$j]: Timeout $timeout seconds is very low"141((warning_count++))142fi143fi144done145done146done147148echo ""149echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"150if [ $error_count -eq 0 ] && [ $warning_count -eq 0 ]; then151echo "โ All checks passed!"152exit 0153elif [ $error_count -eq 0 ]; then154echo "โ ๏ธ Validation passed with $warning_count warning(s)"155exit 0156else157echo "โ Validation failed with $error_count error(s) and $warning_count warning(s)"158exit 1159fi160