Advanced configuration for Claude Code hooks.
| File | Scope | Priority |
|---|---|---|
~/.claude/settings.json |
Global (all projects) | Lower |
.claude/settings.local.json |
Project-specific | Higher |
Project settings are additive to global settings.
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "path/to/script.sh",
"timeout": 5000
}
]
}
]
}
}
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{ "type": "command", "command": "validate-write.sh" }
]
},
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "validate-bash.sh" }
]
},
{
"matcher": "*",
"hooks": [
{ "type": "command", "command": "log-all-tools.sh" }
]
}
]
}
}
{"matcher": "Write"} // Exact tool name
{"matcher": "Bash"} // Bash commands
{"matcher": "Read"} // File reads
{"matcher": "*"} // All tools
{"matcher": ""} // All tools (empty = wildcard)
{"matcher": "mcp__*"} // All MCP tools
{"matcher": "mcp__filesystem__*"} // All filesystem MCP tools
{"matcher": "mcp__github__create_*"} // GitHub create operations
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{ "type": "command", "command": "lint-check.sh", "timeout": 3000 },
{ "type": "command", "command": "security-scan.sh", "timeout": 10000 }
]
}
]
}
}
Hooks execute sequentially. If any hook exits with code 2, execution stops.
{
"type": "command",
"command": "slow-check.sh",
"timeout": 30000 // 30 seconds (milliseconds)
}
Default timeout: 5000ms (5 seconds)
Use $CLAUDE_PROJECT_DIR for portable paths:
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate.sh"
}]
}]
}
}
Handle conditions in the script, not configuration:
#!/bin/bash
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
# Only process Write and Edit
case "$TOOL" in
Write|Edit)
# Validation logic
;;
*)
exit 0 # Skip other tools
;;
esac
#!/bin/bash
if [[ "${CLAUDE_ENV:-development}" == "production" ]]; then
# Stricter validation
strict_validate.sh
else
# Lenient for development
exit 0
fi
Project .claude/settings.local.json can add project-specific hooks:
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/scripts/project-validate.sh"
}]
}]
}
}
{
"hooks": {
"PostToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/audit-log.sh"
}]
}]
}
}
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous.sh",
"timeout": 1000
}]
}]
}
}
{
"hooks": {
"SessionStart": [{
"hooks": [{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/init-session.sh"
}]
}]
}
}
Check JSON validity:
jq '.' ~/.claude/settings.json
Test hook script:
echo '{"tool_name":"Bash","tool_input":{}}' | ./hook.sh
echo $? # Check exit code
Enable debug mode:
claude --debug
List registered hooks:
/hooks