[BUG] validate-hook-schema.sh fails on plugin hook manifests and non-tool hooks (Fix included)
Status Open
Maintainer reply None cached
Activity 1 comment · opened Aug 28, 2026
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] 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:
- The script inspects top-level keys directly, failing on plugin hook files that use the standard
{"description": "...", "hooks": { ... }}wrapper format documented inSKILL.md. It treats"description"and"hooks"as event names and iterates over characters in the description string as hook items. - 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 amatcher.
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
- 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). - 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:
- Fork Branch: https://github.com/Kuldeeep18/claude-code/tree/fix/validate-hook-schema
- Commit: https://github.com/Kuldeeep18/claude-code/commit/7fe9694
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
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗