Skip to content

Claude Code hooks: automate checks, formatting and alerts

What Claude Code hooks are, which events they run on, ready examples to copy, and how to run them safely on an API key.

Updated November 1, 2026Markdown version

Claude Code hooks are commands that Claude Code runs on its own at set points in a session: before a tool runs, after a file edit, when Claude stops or needs your input. You define them in a settings file, so formatting, blocked edits and alerts happen every time, without relying on Claude to remember.

What are Claude Code hooks?#

Anthropic's docs describe hooks as "user-defined shell commands" that give you "deterministic control: certain actions always happen rather than relying on the LLM to choose to run them." A line in CLAUDE.md asks Claude to do something. A hook runs whether Claude remembers or not.

A hook has one of five types:

  • `command`: runs a shell command or script, the most common type.
  • `http`: posts the event's JSON to a URL.
  • `mcp_tool`: calls a tool on a connected MCP server.
  • `prompt`: asks a Claude model one yes-or-no question.
  • `agent`: starts a subagent that can read files and run tools before it decides. Anthropic marks this type experimental.

The docs suggest prompt and agent hooks "for decisions that require judgment rather than deterministic rules."

Which events can a hook run on?#

Claude Code has more than 30 hook events. Most setups use these:

  • `SessionStart`: a session begins or resumes.
  • `UserPromptSubmit`: you send a prompt, before Claude processes it.
  • `PreToolUse`: before a tool call runs. It can block the call.
  • `PermissionRequest`: a tool call needs your permission.
  • `PostToolUse`: after a tool call succeeds.
  • `Notification`: Claude Code sends a notification.
  • `Stop` and `SubagentStop`: Claude or a subagent finishes responding.
  • `PreCompact` and `SessionEnd`: before compaction, and when the session ends.

A matcher narrows when a hook fires. On tool events it matches the tool name, such as Bash, Edit|Write or mcp__github__.*, and it is case-sensitive. On SessionStart it matches how the session started, such as startup, resume, clear or compact. UserPromptSubmit and Stop take no matcher. On tool events, the if field also filters by input: "Bash(git *)" runs the hook only for git commands.

How do you set up a hook?#

Hooks live under a hooks key in a settings file, and the file sets where they apply:

  • `~/.claude/settings.json`: every project on your machine.
  • `.claude/settings.json`: this project. Commit it to share the hooks with your team.
  • `.claude/settings.local.json`: this project, on your machine only.
  • A subagent's frontmatter: only while that subagent runs.
  • A skill's frontmatter: from when the skill runs until the session ends. Set once: true on a hook to remove it after its first successful run.

This hook shows a macOS notification when Claude Code needs your attention:

~/.claude/settings.json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

On Linux, use notify-send 'Claude Code' 'Claude Code needs your attention' as the command. If the file already has a hooks key, add the event inside it. Type /hooks to list every loaded hook and its source, and set "disableAllHooks": true to turn them all off.

Which hooks should you add first?#

Format every edited file#

This PostToolUse hook runs Prettier on each file Claude edits or writes:

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

It misses files that a Bash command changes. For those, use a FileChanged hook, or a Stop hook that runs git status --porcelain.

Block edits to sensitive files#

Save this script and run chmod +x .claude/hooks/protect-files.sh:

.claude/hooks/protect-files.sh
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
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

Then register it to run before every edit:

.claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh" }
        ]
      }
    ]
  }
}

Exit code 2 blocks the edit, and Claude receives the Blocked: message as feedback.

Restore rules after compaction#

Compaction summarizes a long conversation and can drop details. A SessionStart hook with the compact matcher restores them, since Claude Code adds what the command prints to Claude's context:

.claude/settings.json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          { "type": "command", "command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'" }
        ]
      }
    ]
  }
}

Check the work before Claude stops#

This Stop prompt hook asks a model whether every task is done:

.claude/settings.json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
          }
        ]
      }
    ]
  }
}

On "ok": false, Claude keeps working and takes the reason as its next instruction. Claude Code overrides a Stop hook after eight blocks in a row with no tool call between them, and a command hook should check stop_hook_active so it doesn't loop.

How does a hook allow or block an action?#

A command hook receives the event as JSON on stdin, with fields such as session_id, cwd, hook_event_name, tool_name and tool_input. Its exit code decides what happens:

  • Exit 0: the action goes ahead. On SessionStart and UserPromptSubmit, printed text joins Claude's context.
  • Exit 2: blocks the action and sends stderr back as feedback. Some events, such as SessionStart, can't be blocked.
  • Any other code: a non-blocking error. On most events the action continues.

For finer control, exit 0 and print JSON. On PreToolUse, hookSpecificOutput.permissionDecision can be "allow", "deny", "ask" or "defer", and the most restrictive answer wins. A "deny" blocks the tool even with --dangerously-skip-permissions, while an "allow" only skips the prompt: your deny and ask rules still apply.

How to run hooks on a 5abeer key#

command, http and mcp_tool hooks call no model, so they work the same on any key. prompt and agent hooks send requests over the session's connection, so on a 5abeer key they are billed to your balance:

  1. Top up your balance#

    Pay with one of the payment methods. Prompt and agent hook requests are deducted from this balance.

  2. Create a key for Claude models#

    Create a key for the Anthropic provider, as in Create a key. Name it claude-code so its usage is easy to find.

  3. Add the settings#

    Put these settings in ~/.claude/settings.json, and replace YOUR_5ABEER_API_KEY with your key. Your hooks key sits in the same file, next to env:

    {
      "env": {
        "ANTHROPIC_BASE_URL": "https://api.5abeer.ai",
        "ANTHROPIC_AUTH_TOKEN": "YOUR_5ABEER_API_KEY",
        "ANTHROPIC_MODEL": "claude-sonnet-5.5"
      }
    }
    

    ~/.claude/settings.json

  4. Check a hook#

    Start claude, type /hooks to confirm your hooks loaded, then ask Claude to edit a file and watch the hook run.

A prompt hook's model field is optional. Without it, the hook uses the model Claude Code runs for background work. If a prompt hook fails with a model error, set model to an id your key serves. These are the newest Claude models for Claude Code, at our price per million tokens:

per 1M tokens

Official price from the provider's page, checked October 19, 2026.

All models and prices

With a 5abeer plan that includes Claude Code, the 5abeer AI app sets Claude Code up with no key, as in Claude Code setup, and hooks run within the plan's limits.

What do hooks cost?#

A command hook costs no tokens to run, but what it prints into context does: text from a SessionStart or UserPromptSubmit hook is sent again with later requests, so keep it short.

Each prompt hook is a model request, and an agent hook can take up to 50 tool-use turns. For example, a Stop prompt hook that fires 200 times a day, reads about 3,000 tokens each time and answers briefly, with model set to our default model:

Example: what Claude Sonnet 5.5 costs

600K input tokens and 20K output tokens

Our price$0.4270% below the official price
Official price$1.40

Official price from the provider's page, checked October 19, 2026.

Give Claude Code its own key with a spend limit, as in Control every key, and check it in Track your usage. Our price next to the official one is on the Claude API page, and The cheapest way to use Claude Code compares a key with a plan.

How to keep hooks safe#

Anthropic warns that "command hooks execute shell commands with your full user permissions." Treat each hook like a script you run yourself:

  • Test it by hand: echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh; echo $?.
  • Read a repository's hooks before you trust it. -p and SDK runs skip the workspace trust dialog, so before scripting claude -p over code you didn't write, use --bare or --settings '{"disableAllHooks": true}'.
  • Quote variables and check paths: use "$VAR", reference scripts through $CLAUDE_PROJECT_DIR, and reject paths that contain ...
  • Keep keys out of hook commands. An http hook resolves only the variables listed in allowedEnvVars.
  • Use permission rules for hard limits. The docs call the if filter best-effort.

Common errors and fixes#

If a hook doesn't run, check /hooks, the matcher's case and that the script is executable. For "command not found", use an absolute path or $CLAUDE_PROJECT_DIR. To see exit codes and stderr, start with claude --debug-file /tmp/claude.log or run /debug.

Prompt and agent hooks use the session's key, so they can hit the same errors as the main conversation:

  • INVALID_API_KEY: the key in ~/.claude/settings.json is wrong or was deleted.
  • MODEL_UNAVAILABLE: the model in a hook's model field isn't available right now. Retry, or pick another served model.
  • INSUFFICIENT_BALANCE: your balance doesn't cover the request. Top up, then try again.
  • API_KEY_QUOTA_EXHAUSTED: the key reached its spend limit, often because a prompt hook fires every turn.

Every code is explained in Errors and fixes.

FAQ#

Can a hook approve permission prompts for me?#

Yes. A PermissionRequest hook that returns "behavior": "allow" answers for you. The docs' example matches only ExitPlanMode, so only plan approvals skip the prompt.

Do hooks work on Windows?#

Yes. Command hooks run in Git Bash, or in PowerShell when Git Bash isn't installed. Set "shell": "powershell" to always use PowerShell.

Can a subagent have its own hooks?#

Yes. Hooks in a subagent's frontmatter run only while it works, and SubagentStart and SubagentStop hooks fire for every subagent. See Claude Code subagents.

Can a hook undo an edit?#

No. PostToolUse runs after the tool finishes. To stop an edit, block it in PreToolUse.

Do hooks work on a 5abeer plan?#

Yes, if your plan includes Claude Code. Prompt and agent hook requests count toward the plan's limits. See What each plan runs and the pricing page.

Is 5abeer part of Anthropic?#

No. 5abeer is an independent service, not affiliated with Anthropic.

Sources#

Checked on 2026-11-01:

Get started#

Get the 5abeer AI app to run Claude Code and its hooks on your plan, or create a key for Claude models and follow the steps above.