Fixing Claude Code Hooks: The New Matcher Format
Claude Code recently changed their hooks format, and if you haven’t updated your project settings, you’ll see this cryptic error:
hooks: Expected array, but received undefined
Files with errors are skipped entirely, not just the invalid settings.
The Problem
The old hook format put command at the top level:
{
"hooks": {
"Stop": [
{
"matcher": "",
"command": "bash .claude/hooks/my-script.sh"
}
]
}
}
This silently breaks - your entire settings file gets skipped.
The Fix
The new format requires a nested hooks array with explicit type:
{
"hooks": {
"Stop": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/my-script.sh"
}
]
}
]
}
}
Key changes:
matcher- Must be a valid regex (".*"matches everything, empty string doesn’t work)hooks- Now an array inside the matcher objecttype- Required field, set to"command"for shell commands
Quick Migration
Find all your .claude/settings.json files and update them:
# Find all Claude settings files
find ~/projects -name "settings.json" -path "*/.claude/*" 2>/dev/null
Then update each one to the new format.