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.

FileScope
~/.claude/settings.jsonEvery project on this Mac
.claude/settings.jsonThis project, committed to git
.claude/settings.local.jsonThis project, just for you

Run /hooks inside Claude Code to view and edit them interactively.

Hook events

EventFires when
PreToolUseBefore a tool runs. Can allow, deny, ask or defer.
PermissionRequestA permission prompt is about to show. Can allow or deny it.
PostToolUseAfter a tool finishes.
NotificationA prompt has waited about 6 seconds, or Claude is idle. Alert only.
UserPromptSubmitYou send a prompt, before Claude sees it.
StopClaude finishes responding.
SubagentStopA subagent finishes.
SessionStartA session starts or resumes.

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.

~/.claude/settings.json
{
  "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.

~/.claude/hooks/check-bash.sh
#!/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 0

Example: 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.

Answer every agent from the menu bar

Agent Attention for macOS. One-click approvals for Claude Code and Codex CLI, live session status and cache warnings. $9.99 once.