From 411381bf3e91034245c53b57fd88476cd43797b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Dec 2025 18:14:58 +0000 Subject: [PATCH] fix: Add missing matcher fields to hooks.json files Stop hooks (and other hook types) were not firing because they were missing the required "matcher" field. According to the hook development documentation, all hooks must have a matcher field - "*" for wildcard matching. Changes: - Add matcher: "*" to all hooks in hookify, ralph-wiggum, explanatory-output-style, and learning-output-style plugins - Update validate-hook-schema.sh to properly handle plugin format (with 'hooks' wrapper) vs settings format (events at root) - Add validate-all-hooks.sh script to validate all hooks.json files Fixes: https://anthropic.slack.com/archives/C08EHE6JF3L/p1765822035850959 --- .../explanatory-output-style/hooks/hooks.json | 1 + plugins/hookify/hooks/hooks.json | 4 ++ .../learning-output-style/hooks/hooks.json | 1 + .../scripts/validate-hook-schema.sh | 40 +++++++++++---- plugins/ralph-wiggum/hooks/hooks.json | 1 + scripts/validate-all-hooks.sh | 49 +++++++++++++++++++ 6 files changed, 86 insertions(+), 10 deletions(-) create mode 100755 scripts/validate-all-hooks.sh diff --git a/plugins/explanatory-output-style/hooks/hooks.json b/plugins/explanatory-output-style/hooks/hooks.json index d1fb8a57..32521439 100644 --- a/plugins/explanatory-output-style/hooks/hooks.json +++ b/plugins/explanatory-output-style/hooks/hooks.json @@ -3,6 +3,7 @@ "hooks": { "SessionStart": [ { + "matcher": "*", "hooks": [ { "type": "command", diff --git a/plugins/hookify/hooks/hooks.json b/plugins/hookify/hooks/hooks.json index d65daca7..a246ccf5 100644 --- a/plugins/hookify/hooks/hooks.json +++ b/plugins/hookify/hooks/hooks.json @@ -3,6 +3,7 @@ "hooks": { "PreToolUse": [ { + "matcher": "*", "hooks": [ { "type": "command", @@ -14,6 +15,7 @@ ], "PostToolUse": [ { + "matcher": "*", "hooks": [ { "type": "command", @@ -25,6 +27,7 @@ ], "Stop": [ { + "matcher": "*", "hooks": [ { "type": "command", @@ -36,6 +39,7 @@ ], "UserPromptSubmit": [ { + "matcher": "*", "hooks": [ { "type": "command", diff --git a/plugins/learning-output-style/hooks/hooks.json b/plugins/learning-output-style/hooks/hooks.json index b3ab7ce9..e7f7036c 100644 --- a/plugins/learning-output-style/hooks/hooks.json +++ b/plugins/learning-output-style/hooks/hooks.json @@ -3,6 +3,7 @@ "hooks": { "SessionStart": [ { + "matcher": "*", "hooks": [ { "type": "command", diff --git a/plugins/plugin-dev/skills/hook-development/scripts/validate-hook-schema.sh b/plugins/plugin-dev/skills/hook-development/scripts/validate-hook-schema.sh index fed0a1f1..da368bad 100755 --- a/plugins/plugin-dev/skills/hook-development/scripts/validate-hook-schema.sh +++ b/plugins/plugin-dev/skills/hook-development/scripts/validate-hook-schema.sh @@ -40,7 +40,27 @@ echo "" echo "Checking root structure..." VALID_EVENTS=("PreToolUse" "PostToolUse" "UserPromptSubmit" "Stop" "SubagentStop" "SessionStart" "SessionEnd" "PreCompact" "Notification") -for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do +# Detect format: plugin format has { description?, hooks: {...} } wrapper +# Settings format has events directly at root level +is_plugin_format=false +if jq -e '.hooks' "$HOOKS_FILE" >/dev/null 2>&1; then + is_plugin_format=true + HOOKS_PATH=".hooks" + echo "Detected plugin format (with 'hooks' wrapper)" + + # Validate allowed root keys for plugin format + for key in $(jq -r 'keys[]' "$HOOKS_FILE"); do + if [ "$key" != "hooks" ] && [ "$key" != "description" ]; then + echo "⚠️ Unknown root key in plugin format: $key (expected: 'hooks', 'description')" + fi + done +else + HOOKS_PATH="." + echo "Detected settings format (events at root)" +fi + +# Validate event types +for event in $(jq -r "$HOOKS_PATH | keys[]" "$HOOKS_FILE"); do found=false for valid_event in "${VALID_EVENTS[@]}"; do if [ "$event" = "$valid_event" ]; then @@ -62,12 +82,12 @@ echo "Validating individual hooks..." error_count=0 warning_count=0 -for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do - hook_count=$(jq -r ".\"$event\" | length" "$HOOKS_FILE") +for event in $(jq -r "$HOOKS_PATH | keys[]" "$HOOKS_FILE"); do + hook_count=$(jq -r "$HOOKS_PATH.\"$event\" | length" "$HOOKS_FILE") for ((i=0; i/dev/null) + +if [ ${#HOOKS_FILES[@]} -eq 0 ]; then + echo "No hooks.json files found" + exit 0 +fi + +echo "Found ${#HOOKS_FILES[@]} hooks.json file(s)" +echo "" + +errors=0 +for hooks_file in "${HOOKS_FILES[@]}"; do + relative_path="${hooks_file#$REPO_ROOT/}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "📄 $relative_path" + echo "" + + if bash "$VALIDATOR" "$hooks_file"; then + echo "" + else + echo "" + ((errors++)) + fi +done + +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +if [ $errors -eq 0 ]; then + echo "✅ All ${#HOOKS_FILES[@]} hooks.json file(s) are valid!" + exit 0 +else + echo "❌ $errors hooks.json file(s) have validation errors" + exit 1 +fi