Mindket

Claude Code hooks: how to set up your first hook, step by step

A black banner with the Claude logo, titled "Claude Code course" over "Install, plan mode, slash commands and hooks in 4 free lessons".

Claude Code hooks are shell commands that Claude Code runs automatically at set points in a session, such as before a tool call or when Claude needs your input. You add a hooks block to a settings file like ~/.claude/settings.json, check it with /hooks, then trigger the event. A hook always runs, which is why it suits rules that cannot have exceptions.

In this lesson

  • A hook is a shell command tied to an event, so certain actions always happen instead of depending on the model to choose them.
  • You write hooks as JSON in a settings file: an event, a matcher that filters it, and the handler that runs. /hooks shows what loaded.
  • Exit 2 from a PreToolUse hook blocks the tool call and sends your stderr message to Claude. Exit 0 means no objection.
  • Command hooks run with your full user permissions, so read every command before you add it.
All courses
On this page
  1. Before you start
  2. Set up your first hook: a notification when Claude needs you
  3. Events, matchers and where a hook lives
  4. A hook that says no: protect files from edits
  5. How a hook answers: exit codes and input
  6. How Claude Code's creator uses a format hook and notifications
  7. Common mistakes
  8. Try it yourself
  9. Next: Agent Skills
  10. Check yourself
  11. Frequently asked questions
  12. Sources

Before you start

You need Claude Code from lesson 1 and a settings file you can edit. The first hook sends a desktop notification, so it suits a machine with a desktop. The second hook is a Bash script that calls jq, which Anthropic's hooks guide installs with brew install jq on macOS or apt-get install jq on Debian and Ubuntu.

One rule applies to every hook you write. Anthropic's reference warns about what they can reach.

Command hooks execute shell commands with your full user permissions. They can modify, delete, or access any files your user account can access. Review and test all hook commands before adding them to your configuration.

Anthropic, Claude Code documentation · Claude Code docs: Hooks reference (read November 4, 2026)

Set up your first hook: a notification when Claude needs you

Anthropic's own walkthrough starts with a notification, so you do not have to watch the terminal while Claude waits. The block below is the macOS version. It uses the Notification event, which fires when Claude is waiting for your input or permission.

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}
  1. Open ~/.claude/settings.json, creating it if it does not exist, and add the block above. On Linux, use notify-send 'Claude Code' 'Claude Code needs your attention' as the command. On Windows, copy the PowerShell command from the Windows tab of the hooks guide.
  2. If the file already has a hooks key, add Notification next to the event names that are there. Each event is a key inside the one hooks object, so do not replace the whole object.
  3. Save, then type /hooks at the Claude Code prompt. Your hook shows up in the list under Notification.
  4. Press Esc to go back to the prompt. Press Shift+Tab until the status bar shows ⏸ manual mode on, ask Claude to do something that needs permission, then switch to another window. A desktop notification should arrive. Manual mode matters here because in auto mode a classifier reviews actions instead of asking you (lesson 2).

Events, matchers and where a hook lives

A hook has three layers, as the reference describes. First an event, like PreToolUse or Stop. Then a matcher group that filters when it fires, like "only for the Bash tool". Then the handler, which is the shell command. Your first hook used the empty matcher "", which fires on every notification type. Setting it to permission_prompt would fire it only when Claude needs approval, after the prompt has waited about six seconds.

For tool events the matcher is a tool name. Bash matches the Bash tool, and Edit|Write matches either of two tools. The guide says matchers are case-sensitive and must match the tool name exactly.

Where you save the block decides who gets the hook.

Hooks by the numbers in Anthropic's reference

33

hook events, from SessionStart to SessionEnd

Source: Claude Code docs: Hooks reference (read November 5, 2026)

5

handler types: command, http, mcp_tool, prompt and agent

Source: Claude Code docs: Hooks reference (read November 5, 2026)

600 s

is the default timeout for command, http and mcp_tool hooks on most events

Source: Claude Code docs: Hooks reference (read November 5, 2026)

1.5 s

is all a SessionEnd hook gets by default when you exit or run /clear

Source: Claude Code docs: Hooks reference (read November 5, 2026)

  • ~/.claude/settings.json: all your projects, and not shared with anyone.
  • .claude/settings.json: one project, and it can be committed so the team gets it too.
  • .claude/settings.local.json: one project, and not committed.
  • A plugin's hooks/hooks.json, or a skill's frontmatter: the hook travels with that plugin or skill.
Eight of the hook events, from Anthropic's hooks reference
EventWhen it firesWhat the matcher filters
SessionStartWhen a session begins or resumesHow it started, such as startup or resume
UserPromptSubmitWhen a prompt is submitted, before Claude processes itNothing, it always fires
PreToolUseBefore a tool call executes, and it can block the callThe tool name
PostToolUseAfter a tool call succeedsThe tool name
NotificationWhen Claude Code sends a notificationThe notification type
StopWhen Claude finishes respondingNothing, it always fires
PreCompactBefore context compactionmanual or auto
SessionEndWhen a session terminatesWhy it ended

A hook that says no: protect files from edits

A notification only reports. A PreToolUse hook can stop the action. This script, from the guide, reads the file path Claude is about to edit and refuses .env, package-lock.json and anything under .git/. The message it prints to stderr goes back to Claude, so Claude can adjust.

It parses JSON with jq and exits 2 to block. Save it as .claude/hooks/protect-files.sh.

#!/bin/bash
# protect-files.sh

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Normalize Windows backslash separators so the patterns below match
FILE_PATH="${FILE_PATH//\\//}"

PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done

exit 0
  1. Make the script executable on macOS and Linux with chmod +x .claude/hooks/protect-files.sh. Claude Code cannot run a hook script that is not executable.
  2. Register it in .claude/settings.json as a PreToolUse hook with the matcher Edit|Write. The command is "$CLAUDE_PROJECT_DIR"/.claude/hooks/protect-files.sh, written with escaped quotes inside the JSON string.
  3. Ask Claude to add a comment to your .env file. Claude Code blocks the edit before it runs and passes the Blocked: message to Claude.

How a hook answers: exit codes and input

Claude Code sends the event as JSON on the hook's standard input. Every event carries fields like session_id and cwd, and each type adds its own. A PreToolUse hook for Bash sees the tool name and the command, as in the example below. That is why the script above reads .tool_input.file_path for an edit.

The hook answers with its exit code. Exit 0 from a PreToolUse hook is not an approval, because the tool call still goes through the normal permission flow. For a hook that must decide with more detail, Claude Code also reads a JSON object from stdout when you exit 0. Pick one approach per hook, exit 2 or JSON, and do not mix them.

{
  "session_id": "abc123",
  "cwd": "/Users/sarah/myproject",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}
What each exit code does, per the hooks reference
Exit codeWhat Claude Code doesWhere your message goes
0Success, and the action goes aheadPlain-text stdout reaches Claude only on UserPromptSubmit, UserPromptExpansion, SessionStart and PostModelSwitch
2Blocking error, so on events that can block, the action stopsYour stderr becomes the reason, and Claude gets it as feedback on most events
Anything elseNon-blocking error, so the action goes aheadA hook error notice shows the first line of stderr after "Failed with non-blocking status code:"

How Claude Code's creator uses a format hook and notifications

Boris Cherny created Claude Code at Anthropic. His January 2026 setup thread on X says his team formats Claude's code with a PostToolUse hook. Claude usually writes well-formatted code, and the hook fixes the rest before a CI check fails on it. The auto-format hook in "Try it yourself" below is the same idea: PostToolUse, the matcher Edit|Write and Prettier.

The first tip in that thread is about waiting. He runs five Claude sessions at once in numbered terminal tabs and relies on system notifications to learn when one needs input. Your first hook in this lesson gives you that signal in any terminal, and the permission_prompt matcher narrows it to approval requests.

Claude usually generates well-formatted code out of the box, and the hook handles the last 10% to avoid formatting errors in CI later.

Boris Cherny, Creator of Claude Code, Anthropic · X (read November 5, 2026)
Boris Cherny (@bcherny) on X, 2 January 2026, tip 9 of his setup thread. We use a PostToolUse hook to format Claude's code. Claude usually generates well-formatted code out of the box, and the hook handles the last 10% to avoid formatting errors in CI later.
Boris Cherny on X, 2 January 2026
Boris Cherny (@bcherny) on X, 2 January 2026, tip 1 of his setup thread. I run 5 Claudes in parallel in my terminal. I number my tabs 1-5, and use system notifications to know when a Claude needs input.
Boris Cherny on X, 2 January 2026

Screenshots of X's own embeds of the posts, taken on 5 November 2026, with the time in UTC. Select a screenshot to open the post on X.

Common mistakes

  • A matcher that does not match. Matchers are case-sensitive and compare against the tool name, not the words in your prompt. Check the spelling, then check /hooks to see the hook under the event you meant.
  • Replacing the hooks object instead of adding to it. Each event name is a sibling key inside the one hooks object.
  • Invalid JSON in the settings file. Trailing commas and comments are not allowed, and a broken file leaves /hooks with nothing configured. Edits are normally picked up on their own. If a few seconds pass and nothing appears, restart the session.
  • A script that is not executable, or one that cannot be found. Run chmod +x on it and point to it with $CLAUDE_PROJECT_DIR. If the transcript says jq: command not found, install jq.
  • Expecting a successful hook to say something. It shows nothing, so check its effect, like a reformatted file, or press Ctrl+O to open the transcript view.
  • Expecting PostToolUse to undo anything. The tool has already run. A formatter hook with the matcher Edit|Write also misses files that a Bash command rewrites.

Try it yourself

  • Change the notification hook's matcher to permission_prompt and repeat the test. Then set it back to "" and compare when each one fires.
  • Test the protect script by hand before you trust it: echo '{"tool_input":{"file_path":".env"}}' | .claude/hooks/protect-files.sh, then echo $?. You should see the Blocked line and a 2.
  • Open /hooks and select All events at the end of the list, to browse every event, including the ones with no hook.
  • Add the guide's auto-format hook to .claude/settings.json. It is a PostToolUse hook with the matcher Edit|Write that runs Prettier on the edited file, and it needs jq and Prettier. Ask Claude to add single-quoted strings to a JavaScript file, then open the file.

Next: Agent Skills

That is the Claude Code course: install, plan mode, slash commands and hooks. The next course, Agent Skills, goes deeper on the SKILL.md format behind the commands you wrote in lesson 3.

Hooks also travel inside plugins, which our plugins guide covers. A project's standing instructions still belong in a CLAUDE.md file, because a hook enforces and a CLAUDE.md file explains.

Check yourself

  1. Your PreToolUse hook script must block an edit. Which exit code does it use?

    Show answer

    C. Exit 2 Exit 2 blocks the tool call and sends your stderr message to Claude, while exit 1 is a non-blocking error and the action goes ahead.

  2. Where do you save a hook so it applies to one project and your team gets it when you commit?

    Show answer

    A. .claude/settings.json That file belongs to one project and can be committed, while ~/.claude/settings.json covers all your projects and is not shared.

  3. Which matcher makes a PreToolUse hook fire for either the Edit tool or the Write tool?

    Show answer

    D. Edit|Write Edit|Write matches either tool, and matchers are case-sensitive and must match the tool name exactly.

  4. Your settings file already has a hooks key and you want to add a second event. What do you do?

    Show answer

    B. Add the new event name beside the others inside the one hooks object Each event is a key inside the one hooks object, so you add to it and never replace it.

Frequently asked questions

What are Claude Code hooks used for?

They run shell commands at points in a session, for example to format files after edits, block commands before they run, send a notification when Claude needs input, or add context at session start. Anthropic describes the gain as deterministic control: certain actions always happen, rather than relying on the model to choose to run them.

How do I see which hooks are active?

Type /hooks. It opens a read-only browser of your configured hooks, each labeled with where it comes from, such as user settings, project settings or a plugin. Select a hook to see the full command and the file that defines it.

Can a hook stop Claude from doing something?

Yes. A PreToolUse hook that exits 2, or returns a deny decision, cancels the tool call. That block holds in every permission mode, including bypassPermissions, because PreToolUse hooks fire before the permission-mode check.

How do I turn hooks off?

Set "disableAllHooks": true in your settings file, or pass --settings '{"disableAllHooks": true}' to turn them off for one run. You cannot disable one hook and keep it in the configuration. Hooks set in managed settings keep running unless that setting is also on at the managed level.

Are hooks from the internet safe to copy?

Only after you have read them. Whatever the command does, it does as you, on any file your account can reach. In an interactive session, Claude Code also holds back hooks from every settings file until you accept the workspace trust dialog for the folder.

Do I need to restart Claude Code after editing a hook?

Usually not. When you edit a settings file while Claude Code runs, the file watcher normally picks up hook changes on its own. If /hooks still does not show your change after a few seconds, restart the session to force a reload.

Can a skill or a plugin carry its own hooks?

Yes. A plugin ships hooks in hooks/hooks.json, and a skill can declare them in its frontmatter, where they run for the rest of the session once the skill is invoked. A subagent's frontmatter can too, and those hooks run while that subagent is running.

Sources

  1. Claude Code docs: Automate actions with hooks (read November 4, 2026)https://code.claude.com/docs/en/hooks-guide
  2. Claude Code docs: Hooks reference (read November 5, 2026)https://code.claude.com/docs/en/hooks
  3. Boris Cherny on X: tip 9, a PostToolUse format hook, 2 January 2026 (read November 5, 2026)https://x.com/bcherny/status/2007179852047335529
  4. Boris Cherny on X: tip 1, five sessions and notifications, 2 January 2026 (read November 5, 2026)https://x.com/bcherny/status/2007179833990885678

Share this lesson

Next course: Agent Skills

Go deeper on SKILL.md, the format behind your custom commands.

Open the Agent Skills course

This lesson as Markdown · Claude Code course for beginners · All courses