Compare commits

..

1 Commits

Author SHA1 Message Date
Claude
411381bf3e 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
2025-12-15 18:14:58 +00:00
8 changed files with 92 additions and 29 deletions

View File

@@ -26,24 +26,19 @@ Performs automated code review on a pull request using multiple specialized agen
**Usage:**
```bash
/code-review [--comment]
/code-review
```
**Options:**
- `--comment`: Post the review as a PR comment on GitHub. By default, the review is output to the terminal only.
**Example workflow:**
```bash
# On a PR branch, run (outputs to terminal only):
# On a PR branch, run:
/code-review
# Claude will:
# - Launch 4 review agents in parallel
# - Score each issue for confidence
# - Output review with issues ≥80 confidence to terminal
# To post review as a GitHub PR comment:
/code-review --comment
# - Post comment with issues ≥80 confidence
# - Skip posting if no high-confidence issues found
```
**Features:**
@@ -54,7 +49,6 @@ Performs automated code review on a pull request using multiple specialized agen
- Historical context analysis via git blame
- Automatic skipping of closed, draft, or already-reviewed PRs
- Links directly to code with full SHA and line ranges
- Local-only output by default; use `--comment` flag to post to GitHub PR
**Review comment format:**
```markdown
@@ -202,7 +196,6 @@ https://github.com/owner/repo/blob/[full-sha]/path/file.ext#L[start]-L[end]
- **Iterate on guidelines**: Update CLAUDE.md based on patterns
- **Review automatically**: Set up as part of PR workflow
- **Trust the filtering**: Threshold prevents noise
- **Use `--comment` for CI/CD**: Post review to PR when running in automated workflows
## Configuration

View File

@@ -5,9 +5,6 @@ description: Code review a pull request
Provide a code review for the given pull request.
**Arguments:**
- `--comment`: Post the review as a PR comment on GitHub. By default, the review is output to the terminal only.
To do this, follow these steps precisely:
1. Launch a haiku agent to check if any of the following are true:
@@ -55,11 +52,8 @@ Note: Still review Claude generated PR's.
6. Filter out any issues that were not validated in step 5. This step will give us our list of high signal issues for our review.
7. Finally, output the review:
- If `--comment` was passed as an argument, post the review as a comment on the pull request using `gh pr comment`
- Otherwise, output the review directly to the terminal (do NOT post to GitHub)
When writing your review, follow these guidelines:
7. Finally, comment on the pull request.
When writing your comment, follow these guidelines:
a. Keep your output brief
b. Avoid emojis
c. Link and cite relevant code, files, and URLs for each issue

View File

@@ -3,6 +3,7 @@
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",

View File

@@ -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",

View File

@@ -3,6 +3,7 @@
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",

View File

@@ -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<hook_count; i++)); do
# Check matcher exists
matcher=$(jq -r ".\"$event\"[$i].matcher // empty" "$HOOKS_FILE")
matcher=$(jq -r "$HOOKS_PATH.\"$event\"[$i].matcher // empty" "$HOOKS_FILE")
if [ -z "$matcher" ]; then
echo "$event[$i]: Missing 'matcher' field"
((error_count++))
@@ -75,7 +95,7 @@ for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do
fi
# Check hooks array exists
hooks=$(jq -r ".\"$event\"[$i].hooks // empty" "$HOOKS_FILE")
hooks=$(jq -r "$HOOKS_PATH.\"$event\"[$i].hooks // empty" "$HOOKS_FILE")
if [ -z "$hooks" ] || [ "$hooks" = "null" ]; then
echo "$event[$i]: Missing 'hooks' array"
((error_count++))
@@ -83,10 +103,10 @@ for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do
fi
# Validate each hook in the array
hook_array_count=$(jq -r ".\"$event\"[$i].hooks | length" "$HOOKS_FILE")
hook_array_count=$(jq -r "$HOOKS_PATH.\"$event\"[$i].hooks | length" "$HOOKS_FILE")
for ((j=0; j<hook_array_count; j++)); do
hook_type=$(jq -r ".\"$event\"[$i].hooks[$j].type // empty" "$HOOKS_FILE")
hook_type=$(jq -r "$HOOKS_PATH.\"$event\"[$i].hooks[$j].type // empty" "$HOOKS_FILE")
if [ -z "$hook_type" ]; then
echo "$event[$i].hooks[$j]: Missing 'type' field"
@@ -102,7 +122,7 @@ for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do
# Check type-specific fields
if [ "$hook_type" = "command" ]; then
command=$(jq -r ".\"$event\"[$i].hooks[$j].command // empty" "$HOOKS_FILE")
command=$(jq -r "$HOOKS_PATH.\"$event\"[$i].hooks[$j].command // empty" "$HOOKS_FILE")
if [ -z "$command" ]; then
echo "$event[$i].hooks[$j]: Command hooks must have 'command' field"
((error_count++))
@@ -114,7 +134,7 @@ for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do
fi
fi
elif [ "$hook_type" = "prompt" ]; then
prompt=$(jq -r ".\"$event\"[$i].hooks[$j].prompt // empty" "$HOOKS_FILE")
prompt=$(jq -r "$HOOKS_PATH.\"$event\"[$i].hooks[$j].prompt // empty" "$HOOKS_FILE")
if [ -z "$prompt" ]; then
echo "$event[$i].hooks[$j]: Prompt hooks must have 'prompt' field"
((error_count++))
@@ -128,7 +148,7 @@ for event in $(jq -r 'keys[]' "$HOOKS_FILE"); do
fi
# Check timeout
timeout=$(jq -r ".\"$event\"[$i].hooks[$j].timeout // empty" "$HOOKS_FILE")
timeout=$(jq -r "$HOOKS_PATH.\"$event\"[$i].hooks[$j].timeout // empty" "$HOOKS_FILE")
if [ -n "$timeout" ] && [ "$timeout" != "null" ]; then
if ! [[ "$timeout" =~ ^[0-9]+$ ]]; then
echo "$event[$i].hooks[$j]: Timeout must be a number"

View File

@@ -3,6 +3,7 @@
"hooks": {
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",

49
scripts/validate-all-hooks.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/bin/bash
# Validate all hooks.json files in the repository
# This script can be run in CI to ensure all plugins have valid hook configurations
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
VALIDATOR="$REPO_ROOT/plugins/plugin-dev/skills/hook-development/scripts/validate-hook-schema.sh"
echo "🔍 Validating all hooks.json files in the repository..."
echo ""
# Find all hooks.json files
mapfile -t HOOKS_FILES < <(find "$REPO_ROOT/plugins" -name "hooks.json" -type f 2>/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