Layer 1

Hooks: Automatic Actions

Shell commands that fire deterministically on lifecycle events. Unlike asking Claude to remember something, hooks guarantee it always happens. 24+ events, 4 hook types, zero guesswork.

Event Fires

Claude edits a file → PostToolUse event triggers

JSON Piped to stdin

Hook receives {"tool_name":"Edit","tool_input":{"file_path":"..."}}

Hook Command Runs

npx prettier --write "$FILE_PATH" executes

Result Returned

Exit 0 = success. Exit 2 = block action. stdout fed back to Claude as context.

What Are Hooks?

Hooks are deterministic automation — shell commands, LLM prompts, HTTP calls, or even full agent tasks that execute at specific points in Claude Code's lifecycle. They don't rely on Claude "remembering" to do something. They just run.

The key insight: hooks handle the predictable stuff (formatting, validation, notifications) so Claude can focus on the creative problem-solving.

How Data Flows

Every hook receives structured JSON on stdin and communicates back via exit codes and stdout.

Event e.g. PostToolUse
JSON stdin tool_name, inputs
Your Command prettier, jq, curl...
Exit Code 0=ok, 2=block

All 24+ Hook Events

Claude Code exposes events across the entire session lifecycle. Grouped by category:

Session Lifecycle

EventWhen It FiresUse For
SessionStartSession begins or resumesLoad context, validate environment, restore state
SessionEndSession terminatesArchive state, write summaries, cleanup
StopClaude finishes its turnRun tests, verify completeness, send notifications
StopFailureTurn ends due to API errorLog errors, send alerts

Tool Execution

EventWhen It FiresUse For
PreToolUseBefore a tool call executesBlock dangerous commands, validate inputs
PostToolUseAfter a tool call succeedsFormat code, lint, log activity
PostToolUseFailureAfter a tool call failsLog failures, retry logic

User Interaction

EventWhen It FiresUse For
UserPromptSubmitUser sends a messageInject dynamic context, modify prompts, memory recall
PermissionRequestPermission dialog appearsAuto-approve trusted operations
PermissionDeniedTool call denied by auto modeLog denials, suggest alternatives
NotificationClaude sends a notificationCustom notification routing

Agents & Tasks

EventWhen It FiresUse For
SubagentStartSubagent spawnedLog agent activity, resource tracking
SubagentStopSubagent finishesAggregate results, cleanup
TaskCreatedTask created via TaskCreateExternal task tracking integration
TaskCompletedTask marked completeProgress notifications
TeammateIdleAgent team member goes idleRebalance work, notify

Configuration & Context

EventWhen It FiresUse For
CwdChangedWorking directory changesReload environment, switch contexts
FileChangedWatched file changesReload .env, trigger rebuilds
ConfigChangeConfig file modifiedAudit config changes, reload settings
InstructionsLoadedCLAUDE.md/rules loadedValidate instructions, inject extras
PreCompactBefore context compactionSave critical state before compression
PostCompactAfter context compactionRe-inject critical context
WorktreeCreateWorktree createdInitialize worktree environment
WorktreeRemoveWorktree removedCleanup temporary state

Hook Types

Hooks come in four flavors, from simple to sophisticated:

Command

Run a shell command. Exit code 0 = allow, exit code 2 = block. The simplest and most common type. No extra cost.

Prompt

A single LLM call for judgment-based decisions. Uses Haiku by default. Consumes a small number of tokens per invocation.

Agent

Multi-turn verification with tool access. For complex validation that needs to read files, run commands, and reason. Consumes more tokens.

HTTP

POST event data to external endpoints. Send events to Slack, log to a dashboard, trigger CI/CD pipelines. No token cost, but needs an endpoint.

Real Examples

These are production-ready hook configurations. Drop them into your ~/.claude/settings.json.

Auto-format code after every edit
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
      }]
    }]
  }
}

Runs Prettier on every file Claude edits or writes. The matcher filters which tools trigger this hook.

Block edits to protected files
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command": "jq -r '.tool_input.file_path' | grep -qE '(\\.env|package-lock\\.json|yarn\\.lock)$' && exit 2 || exit 0"
      }]
    }]
  }
}

Exit code 2 blocks the action. Prevents Claude from modifying .env, lockfiles, or other protected files.

Block dangerous shell commands
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'rm -rf|DROP TABLE' && exit 2 || exit 0"
      }]
    }]
  }
}

Catches destructive patterns before they run. Add your own patterns to the regex.

Desktop notification when Claude needs input
{
  "hooks": {
    "Notification": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
      }]
    }]
  }
}

macOS notification. Replace osascript with notify-send on Linux.

Audit-log all Bash commands
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"
      }]
    }]
  }
}

Logs every Bash command Claude runs. Useful for security reviews and compliance.

Run tests before Claude stops
{
  "hooks": {
    "Stop": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "npm test 2>&1 | tail -5"
      }]
    }]
  }
}

Runs your test suite every time Claude finishes. Test output is shown to Claude as context.

Audit config changes
{
  "hooks": {
    "ConfigChange": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "jq -c '{timestamp: now | todate, source: .source, file: .file_path}' >> ~/claude-config-audit.log"
      }]
    }]
  }
}

Creates an audit trail of every configuration change. Useful for enterprise environments.

Prompt-based: verify task completion
{
  "hooks": {
    "Stop": [{
      "hooks": [{
        "type": "prompt",
        "prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
      }]
    }]
  }
}

Uses Haiku to judge whether Claude's work is done. If tasks remain, Claude keeps going. Note: consumes tokens per invocation.

Agent-based: verify tests pass before stopping
{
  "hooks": {
    "Stop": [{
      "hooks": [{
        "type": "agent",
        "prompt": "Verify that all unit tests pass. Run the test suite and check the results.",
        "timeout": 120
      }]
    }]
  }
}

A full multi-turn agent that runs tests, reads output, and makes a judgment call. The most powerful hook type. Consumes more tokens.

Auto-approve plan mode exit
{
  "hooks": {
    "PermissionRequest": [{
      "matcher": "ExitPlanMode",
      "hooks": [{
        "type": "command",
        "command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
      }]
    }]
  }
}

Automatically approves exiting plan mode without a permission dialog.

Where to Configure

ScopeFileShareable?Who It Affects
Personal~/.claude/settings.jsonNoAll your projects
Project.claude/settings.jsonYes (commit)Everyone on this project
Local project.claude/settings.local.jsonNo (gitignored)Just you, this project
Pluginplugin-name/hooks/hooks.jsonYesAnyone with the plugin
EnterpriseManaged policy settingsAdmin-controlledOrg-wide
Hooks vs. CLAUDE.md instructions

CLAUDE.md instructions are suggestions — Claude might forget. Hooks are guarantees — they run every time, regardless of context. Use CLAUDE.md for guidelines and hooks for enforcement.

Hook Input & Output

Hooks receive event data as JSON on stdin. You can use jq to extract specific fields:

Hook stdin (PostToolUse event)
{ "tool_name": "Edit", "tool_input": { "file_path": "/src/utils.ts", "old_string": "...", "new_string": "..." }, "tool_output": "File edited successfully" }
Exit codes matter

For PreToolUse hooks: exit 0 = allow, exit 2 = block the action. Any other exit code is treated as a hook error (action still proceeds). Make sure your blocking hooks explicitly exit 2.


What Does It Cost?

Hook TypeCostDetails
Command Included Runs your shell commands. No extra Claude tokens consumed.
HTTP Included POSTs to your endpoint. No Claude tokens, but you need a server to receive.
Prompt Tokens Single Haiku call per invocation. Very cheap but adds up if triggered on every tool use.
Agent Tokens Multi-turn agent. Most expensive hook type. Use sparingly (e.g., on Stop, not PostToolUse).