Skip to content

[BUG] validate-hook-schema.sh fails on plugin hook manifests and non-tool hooks (Fix included) #90292

Description

@Kuldeeep18

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

The validate-hook-schema.sh utility in plugins/plugin-dev fails when validating all official plugin hooks.json files in the repository, outputting dozens of false error messages.

Root causes:

  1. The script inspects top-level keys directly, failing on plugin hook files that use the standard {"description": "...", "hooks": { ... }} wrapper format documented in SKILL.md. It treats "description" and "hooks" as event names and iterates over characters in the description string as hook items.
  2. The script enforces "matcher" as a mandatory field for all hook events, even though non-tool events (Stop, SessionStart, UserPromptSubmit, etc.) do not use or require a matcher.

What Should Happen?

The validator should support both the plugin wrapper format {"hooks": { ... }} (with optional "description") and the settings format { "<Event>": [ ... ] }, and should only require matcher for tool events (PreToolUse and PostToolUse).

Error Messages/Logs

Steps to Reproduce

  1. Run ./plugins/plugin-dev/skills/hook-development/scripts/validate-hook-schema.sh plugins/explanatory-output-style/hooks/hooks.json (or on any plugin hooks.json in the repository).
  2. Observe validation failure with 38+ false errors.

Claude Model

None

Is this a regression?

No, this never worked

Last Working Version

No response

Claude Code Version

Latest (main)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Terminal.app (macOS)

Additional Information

I have prepared and verified a minimal fix on my fork:

Proposed Diff:

--- a/plugins/plugin-dev/skills/hook-development/scripts/validate-hook-schema.sh
+++ b/plugins/plugin-dev/skills/hook-development/scripts/validate-hook-schema.sh
@@ -35,12 +35,24 @@ if ! jq empty "$HOOKS_FILE" 2>/dev/null; then
 fi
 echo "✅ Valid JSON"
 
+# Determine hook root expression (support both plugin wrapper format {"hooks": {...}} and settings direct format)
+HOOK_ROOT_JQ='if (type == "object" and has("hooks") and (.hooks | type == "object")) then .hooks else . end'
+HAS_HOOKS_WRAPPER=$(jq -r 'if (type == "object" and has("hooks") and (.hooks | type == "object")) then "true" else "false" end' "$HOOKS_FILE")
+
 # Check 2: Root structure
 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
+if [ "$HAS_HOOKS_WRAPPER" = "true" ]; then
+  for key in $(jq -r 'keys[]' "$HOOKS_FILE" | tr -d '\r'); do
+    if [ "$key" != "hooks" ] && [ "$key" != "description" ]; then
+      echo "⚠️  Unknown top-level field in plugin format: $key"
+    fi
+  done
+fi
+
+for event in $(jq -r "($HOOK_ROOT_JQ) | keys[]" "$HOOKS_FILE" | tr -d '\r'); do
   found=false
   for valid_event in "${VALID_EVENTS[@]}"; do
     if [ "$event" = "$valid_event" ]; then
@@ -62,20 +74,22 @@ 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 "($HOOK_ROOT_JQ) | keys[]" "$HOOKS_FILE" | tr -d '\r'); do
+  hook_count=$(jq -r "($HOOK_ROOT_JQ) | .[\"$event\"] | length" "$HOOKS_FILE" | tr -d '\r')
 
   for ((i=0; i<hook_count; i++)); do
-    # Check matcher exists
-    matcher=$(jq -r ".\"$event\"[$i].matcher // empty" "$HOOKS_FILE")
+    # Check matcher (only required for tool-level events: PreToolUse, PostToolUse)
+    matcher=$(jq -r "($HOOK_ROOT_JQ) | .[\"$event\"][$i].matcher // empty" "$HOOKS_FILE")
     if [ -z "$matcher" ]; then
-      echo "❌ $event[$i]: Missing 'matcher' field"
-      ((error_count++))
-      continue
+      if [ "$event" = "PreToolUse" ] || [ "$event" = "PostToolUse" ]; then
+        echo "❌ $event[$i]: Missing 'matcher' field"
+        ((error_count++))
+        continue
+      fi
     fi

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions