Claude Code reference

Claude Code hooks explained, with practical examples

What Claude Code hooks are, the events most people use, how a hook reads its input and blocks an action, and six example hooks you can adapt.

Want this done for you? Book a free 30-minute AI audit

In short

Hooks are commands Claude Code runs automatically at set points in a session, such as before a tool runs, after a file is edited, or when a session ends. They give you guardrails that always happen regardless of what the model decides: formatters, blocks on sensitive files, audit logs and notifications.

Slash commands are shortcuts you choose to run. Hooks run themselves. If “every time Claude edits a file, format it” sounds useful, you want a hook.

The events most people use

Claude Code supports more than 30 hook events as at 03/10/2026; the hooks reference lists them all. These are the ones that cover most needs:

EventFiresCan it block?Common use
PreToolUseBefore a tool call (Edit, Write, Bash and so on)Yes, with exit code 2Block edits to sensitive files, gate risky commands
PostToolUseAfter a tool call succeedsNo, the tool already ranFormatter, linter, audit log
UserPromptSubmitWhen you send a messageYesAdd context, such as today’s date
SessionStartA session beginsNoLoad context, log the session
SessionEndA session endsNoNotify a channel, tidy up
NotificationClaude needs your attentionNoDesktop or chat alert

How a hook gets its input

A command hook receives a JSON object on standard input describing the event: for tool events, the tool name and its input, such as tool_input.file_path for an edit. Claude Code also sets CLAUDE_PROJECT_DIR to the project folder. The examples below use jq, a small free tool for reading JSON, to pull out the field they need.

A minimal .claude/settings.json:

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

Exit code 0 means carry on. On a PreToolUse hook, exit code 2 blocks the tool call and whatever the hook printed to standard error is shown to Claude. Other non-zero codes are reported as errors but don’t block.

Six example hooks

These are examples to adapt, written for macOS, Linux or Git Bash on Windows.

1. Format files after every edit

{
  "PostToolUse": [
    {
      "matcher": "Edit|Write",
      "hooks": [
        { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write 2>/dev/null || true" }
      ]
    }
  ]
}

The || true stops errors on file types Prettier doesn’t handle.

2. Block edits to secrets and production files

{
  "PreToolUse": [
    {
      "matcher": "Edit|Write",
      "hooks": [
        {
          "type": "command",
          "command": "f=$(jq -r '.tool_input.file_path'); case \"$f\" in *.env*|*credentials*|*prod-*) echo 'Blocked: secrets or production file' >&2; exit 2;; esac"
        }
      ]
    }
  ]
}

For simple cases, a deny rule in your permissions does the same job with less to maintain; see the Claude Code safety guide.

3. Post to a chat channel when a session ends

{
  "SessionEnd": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "curl -s -X POST \"$SLACK_WEBHOOK_URL\" -H 'Content-Type: application/json' -d \"{\\\"text\\\":\\\"Claude Code session ended in $(basename \"$CLAUDE_PROJECT_DIR\")\\\"}\""
        }
      ]
    }
  ]
}

Keep the webhook address in an environment variable, not in the settings file.

4. Save each edit as a small Git commit

{
  "PostToolUse": [
    {
      "matcher": "Edit|Write",
      "hooks": [
        { "type": "command", "command": "f=$(jq -r '.tool_input.file_path'); git add \"$f\" && git commit -m \"wip: $f\" --no-verify || true" }
      ]
    }
  ]
}

Every edit becomes a tiny commit that is easy to inspect or undo. Squash them before you share the branch.

5. Add today’s date and house rules to every prompt

{
  "UserPromptSubmit": [
    {
      "hooks": [
        { "type": "command", "command": "echo \"Today is $(date '+%A %d/%m/%Y'). We are in Melbourne. Use Australian English.\"" }
      ]
    }
  ]
}

Plain text a UserPromptSubmit hook prints is added as context Claude can see.

6. Keep an audit log of tool calls

{
  "PostToolUse": [
    {
      "hooks": [
        { "type": "command", "command": "jq -c '{time: (now|todate), tool: .tool_name, input: .tool_input}' >> \"$CLAUDE_PROJECT_DIR/.claude/audit.log\"" }
      ]
    }
  ]
}

A searchable record of what Claude did, useful when you let it work for long stretches.

Matchers

The matcher is matched against the tool name. Common values:

  • Edit|Write for file changes
  • Bash for shell commands
  • WebFetch|WebSearch for anything that goes online
  • omitted, or *, for every tool

Performance and ordering

  • Hooks run synchronously by default, so Claude waits for them. Keep PreToolUse hooks fast.
  • Matching hooks for the same event run in parallel, not in the order you list them.
  • Add "async": true to a hook that can run in the background, and a timeout to anything that calls the network.

When to use a hook, a slash command or a skill

  • Hook: this must always happen when X occurs. No model judgement.
  • Slash command: you want to fire a set prompt with a few keystrokes. You decide when.
  • Skill: Claude should decide when to use a capability. The model decides.

Common gotchas

  • Quoting on Windows. PowerShell treats quotes and $ differently from bash. If a hook works on a Mac but not on Windows, quoting is the usual cause; run hooks through Git Bash or write them in PowerShell syntax.
  • Hooks run as you. They have your permissions and credentials. Treat them like scheduled tasks.
  • Missing tools. jq isn’t installed by default everywhere. Install it first (on Windows, winget install jqlang.jq).

Common questions

Do hooks count against my token budget?

A command hook runs on your machine, so it costs no tokens itself. Any text a hook adds to the conversation, such as context from a UserPromptSubmit hook, is read by Claude like any other input.

Can a hook prevent Claude from doing something?

Yes. A PreToolUse hook that exits with code 2 blocks the tool call, and its error message is shown to Claude. Other non-zero exit codes are treated as non-blocking errors.

Where do I configure hooks?

In a settings file: .claude/settings.json in a project, or the settings.json in the .claude folder of your user profile for every project. The hooks key maps event names to matchers and commands.

Want this built for your business?

Book a free 30-minute AI audit call with Jenn Yang. We map where your time goes, name the first systems worth building and give you fixed AUD prices. No obligation.

Book a free AI audit

Or have us run it for you, end to end: On Autopilot is Australia's outsourced AI department.