// topics
patriola.com

Topic guide

Claude Code Hooks Tutorial


Hooks turn Claude Code from a tool you prompt into a system that runs behavior automatically. Here's how they work.

What hooks actually are

Persistent behavior without persistent prompting

Every Claude Code session starts fresh. Claude doesn't remember that you always want it to run tests before committing, or that you want a summary posted to a log file when it finishes, or that it should load your session memory file at the start of every conversation. If you want those behaviors, you have to ask for them every time — unless you use hooks.

Claude Code hooks are shell commands that fire automatically at defined points in Claude's operation. Configure them once in settings.json and they run in every subsequent session without any prompting. They're the bridge between "Claude does things when I ask" and "Claude does things as part of a system."

A deeper walkthrough of hooks in real automations is in the getting started guide on the blog. Book 2 (coming soon) covers the full automation system that hooks plug into.

The four lifecycle events

When hooks can fire

PreToolUse fires before Claude runs any tool — a Bash command, a file read, a web search. This is the enforcement point: you can use a PreToolUse hook to log what Claude is about to do, validate it against a policy, or block categories of actions entirely. If your hook exits with a non-zero code, the tool call is cancelled.

PostToolUse fires after a tool completes. This is the reaction point: run a linter after a file edit, update a log after a Bash command runs, trigger a notification after a web fetch. The hook receives the tool name and result as environment variables, so it can branch based on what actually happened.

Notification fires when Claude sends a status event — when it's waiting for a long operation, when it encounters an error, when it wants to surface something to you without interrupting the flow. Use this to pipe Claude's status into a notification system, a log, or a status file another process monitors.

Stop fires when Claude finishes responding and returns control. This is the completion point: write a summary, send a notification, update a session file, or trigger a downstream process that acts on Claude's output. If you only ever configure one hook, Stop is the most generally useful one.

Configuration

What the settings.json structure looks like

Hooks live under the hooks key in settings.json. Each hook definition specifies the event type, an optional tool matcher (to restrict which tool triggers it), and the command to run. The command is a shell string executed by the system shell — anything you can run in a terminal works.

settings.json — three concrete hook examples

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "python3 ~/.claude/hooks/pre-bash-log.py" }]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [{ "type": "command", "command": "npm run lint --silent 2>&1 | tee -a .claude/lint.log" }]
      }
    ],
    "Stop": [
      {
        "hooks": [{ "type": "command", "command": "python3 ~/.claude/hooks/session-close.py" }]
      }
    ]
  }
}
Three examples in practice

What real hooks do

Pre-commit test runner. A PreToolUse hook matching the Bash tool can intercept any command that looks like a git commit and run the test suite first. If tests fail, the hook exits non-zero and Claude's commit command is cancelled. You never accidentally commit broken code because you forgot to ask Claude to run tests — the hook enforces it.

Session-start memory loader. A hook on the Notification event can detect session-start signals and inject your session file into the conversation context. This is how you get Claude to "remember" where you left off without manually pasting a context block at the start of every session — the hook does it for you.

Notification on task complete. A Stop hook that calls a notification script means you don't have to watch Claude work. Send the session output to your phone via a push notification service, write a completion record to a log file, or POST the result to a webhook. Claude finishes, the hook fires, you find out — wherever you are.

You — setting up hooks for the first time I want Claude to automatically run my linter after every file edit and send me a desktop notification when it finishes a session. How do I configure that?
Claude Two hooks in your project's .claude/settings.json will do it. A PostToolUse hook matching the Edit tool runs your linter command after every file change — pipe output to a log file so you can review it later. A Stop hook runs a script that fires a desktop notification using your OS's notification API (notify-send on Linux, osascript on macOS, a PowerShell toast on Windows). Want me to write both hook scripts for your setup?
Go deeper

The full hooks system is in Book 2

The blog guide covers the basics. Patriola's Guide to Claude: Automations covers hooks in full depth: per-tool targeting, hook chaining, error handling, project vs user hooks, and the complete automation patterns they enable.

Related

More on this

Hooks are one building block of a larger automation system — see Claude Automations for scheduling, background tasks, and the discipline that keeps unattended runs reliable. For the daily-use patterns hooks often support, see Claude Productivity.

Book 2 is coming

Read the book

Every hook pattern on this page, plus the full automation system built on top of them.