Lesson 19Automation

Hooks System

Lifecycle events

What are Hooks

Hooks are shell commands that automatically execute on certain events in Claude Code.

Use cases

  • Auto-format after Edit
  • Bash command validation
  • Slack notifications
  • Action logging

Limitations

  • Execute synchronously
  • 600 second default timeout
  • Can block operations

How hooks work and why you need them

Hooks in Claude Code are plain shell commands that the agent runs automatically in response to specific events in a session's lifecycle. Instead of asking the assistant to "remember to format the file" or "check the command before running it," you describe a rule once and it fires deterministically on every matching event. The key idea is that hooks run outside the model: they are code you control, so their behaviour is predictable and never depends on whether the language model happens to recall a step. That makes hooks an ideal way to bake guaranteed validation, formatting, and alerting into your workflow.

There are many events — over thirty. Common ones include the session events SessionStart and SessionEnd, which fire when work begins and ends and suit environment setup or sending metrics. Tool events — PreToolUse and PostToolUse — wrap any tool call, such as Edit or Bash. Beyond those there are UserPromptSubmit (when you send a message), Notification (when Claude wants to alert you), Stop and SubagentStop, PermissionRequest, PreCompact, and more. The matcher field narrows what triggers a tool hook: "*" (or an empty string, or omitting it) means all tools, "Bash" or "Edit|Write" is an exact name or pipe-separated list, and anything with other characters is treated as a regular expression (e.g. "mcp__.*").

Walk through the config examples on this page. The settings.json block describes a PostToolUse event: after every edit to a file ending in .ts, .tsx, .js, or .jsx, it runs prettier --write. The hook receives the event data (including the file path) as JSON on stdin — there is no {{path}} template placeholder; for project and plugin paths you have the variables ${CLAUDE_PROJECT_DIR} and ${CLAUDE_PLUGIN_ROOT}. The second example is a PreToolUse hook for the Bash tool: it intercepts the command before execution, scans it for dangerous patterns like rm -rf, and on a match exits with code 2 — that is what blocks the operation, and the text on stderr is fed back to Claude. Exit code 0 means success (the operation proceeds), while any other non-zero code (such as 1) is a non-blocking error: the operation still runs. The main pitfalls: hooks run synchronously and a command hook's default timeout is 600 seconds, so heavy commands can stall the session for a long time; an overly broad matcher can fire more often than intended; and you should log errors inside a hook, or a silent block becomes hard to debug. Always run a hook config locally before committing it.

Hook Events
SessionStart

When Claude Code session starts

Environment init, checks
SessionEnd

When session ends

Cleanup, send metrics
PreToolUse

Before tool is called

Validation, logging, blocking
PostToolUse

After tool is called

Formatting, post-processing
UserPromptSubmit

When user sends a message

Filtering, routing
Notification

On notifications from Claude

External alerts (Slack, email)
Stop

When Claude stops

Handle task completion
Example: Auto-formatting

Automatic file formatting after editing:

// ~/.claude/settings.json (or .claude/settings.json)
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | { read f; case \"$f\" in *.ts|*.tsx|*.js|*.jsx) npx prettier --write \"$f\";; esac; }"
          }
        ]
      }
    ]
  }
}
💡The hook reads the file path from the JSON on stdin (tool_input field), not from a template
Example: Blocking Dangerous Commands

PreToolUse blocks an operation by exiting with code 2:

// settings.json — block dangerous commands
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "cmd=$(jq -r '.tool_input.command'); if echo \"$cmd\" | grep -qE 'rm -rf|drop database'; then echo 'BLOCKED: dangerous command' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}

Exit code 0

Success — operation continues

Exit code 2

Operation is blocked, stderr is sent to Claude

Hook Configuration

Hooks are configured in settings.json under the hooks key:

~/.claude/settings.json          # user (global) settings
.claude/settings.json            # project settings (committed)
.claude/settings.local.json      # project settings (not committed)
# all of them hold hooks under a single "hooks" key
matcherWhich tools to match ("*", "Bash", "Edit|Write", regex)
typeHook type — usually "command"
commandShell command to execute

Hook Best Practices

  • 1.Keep hooks fast — they block the main thread
  • 2.Use matcher for precise triggering
  • 3.Log hook errors for debugging
  • 4.Test hooks locally before committing

Frequently asked questions

What are hooks in Claude Code?

Hooks are plain shell commands that Claude Code runs automatically on specific session events: session start and end, before and after a tool call, when you submit a message, or when the agent stops. They run outside the model, so they fire deterministically and never depend on whether the assistant recalls a step.

How do I set up a hook to auto-format files after editing?

Create .claude/hooks/format-on-edit.json with a PostToolUse event, set match to the Edit tool and a glob path pattern (e.g. **/*.{ts,tsx,js,jsx}), and set command to a formatter such as npx prettier --write {{path}}. The {{path}} placeholder is replaced with the path of the edited file.

How can a PreToolUse hook block dangerous commands?

Add a PreToolUse hook for the Bash tool that intercepts the command before execution. If it finds a dangerous pattern like rm -rf, the hook returns exit code 1 and the operation is blocked. An exit code of 0 lets the operation proceed, giving you a guardrail against accidental data loss.

What are the limitations of Claude Code hooks?

Hooks run synchronously with a roughly 60-second timeout, so heavy commands stall the whole session. An overly broad match can trigger more often than intended, and you should log errors inside a hook or a silent block becomes hard to debug. Always test a hook config locally before committing it.

Lesson Quiz

1 of 1

1.What are hooks in Claude Code?

This lesson is part of a structured LLM course.

My Learning Path