Guide
Claude Code hooks, explained with examples
Hooks are shell commands Claude Code runs at fixed points in a session. They let you log, notify, block or approve actions without changing how you prompt.
Where hooks live
Hooks are defined in Claude Code settings files. Later files override earlier ones.
Run /hooks inside Claude Code to view and edit them interactively.
Hook events
The shape of a hook
Each event takes a list of matchers. A matcher filters by tool name, such as Bash or Edit|Write. An empty matcher matches everything. Claude Code passes event details to your command as JSON on stdin.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "~/.claude/hooks/check-bash.sh" }
]
}
]
}
}Example: log every Bash command
jq -r '.tool_input.command' >> ~/.claude/bash-log.txt
Example: block dangerous commands
Exit code 2 blocks the tool call and sends stderr back to Claude.
#!/bin/sh
cmd=$(jq -r '.tool_input.command')
case "$cmd" in
*"rm -rf /"*|*"terraform destroy"*)
echo "Blocked by hook: $cmd" >&2
exit 2 ;;
esac
exit 0Example: a desktop alert when Claude waits
"Notification": [{ "matcher": "", "hooks": [{ "type": "command",
"command": "osascript -e 'display notification \"Claude Code needs you\"'" }] }]See Claude Code notifications for all the options.
Turning hooks into one-click approvals
The PermissionRequest hook can answer a prompt by printing JSON with a decision of allow or deny. Exit codes do not decide it.
{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}}Agent Attention uses this hook. When Claude Code asks permission, it posts the request to the app's local API on 127.0.0.1 and shows an Approve, Deny, Always card that never takes focus. Your choice goes straight back to the waiting session.
Nothing leaves your Mac. Any script can use the same API with curl.