# 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: 2026-11-01

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:

```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:

```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`:

```bash
#!/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:

```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:

```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:

```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](https://5abeer.ai/en/docs/start/payment.md). 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](https://5abeer.ai/en/docs/start/keys.md#create). 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`:

`~/.claude/settings.json`

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

### 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:

| Model | Id | Input (ours) | Output (ours) | Context |
| --- | --- | --- | --- | --- |
| Claude Sonnet 5.5 | `claude-sonnet-5.5` | $0.60 | $3 | 1M |
| Claude Opus 5.5 | `claude-opus-5.5` | $1.20 | $6 | 1M |
| Claude Fable 5.1 | `claude-fable-5.1` | $3 | $15 | — |

With a 5abeer plan that includes Claude Code, the 5abeer AI app sets Claude Code up with no key, as in [Claude Code setup](https://5abeer.ai/en/docs/apps/claude-code.md#easy), 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: 600,000 input tokens and 20,000 output tokens on Claude Sonnet 5.5 (`claude-sonnet-5.5`) cost $0.42 at our price against $1.40 at the official price (70% lower). Official price checked October 19, 2026.

Give Claude Code its own key with a spend limit, as in [Control every key](https://5abeer.ai/en/docs/start/keys.md#controls), and check it in [Track your usage](https://5abeer.ai/en/docs/start/pricing.md#usage). Our price next to the official one is on the [Claude API page](https://5abeer.ai/en/api/claude), and [The cheapest way to use Claude Code](https://5abeer.ai/en/guides/claude-code-cheapest-way) 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`](https://5abeer.ai/en/docs/help/errors.md#INVALID_API_KEY): the key in `~/.claude/settings.json` is wrong or was deleted.
- [`MODEL_UNAVAILABLE`](https://5abeer.ai/en/docs/help/errors.md#MODEL_UNAVAILABLE): the model in a hook's `model` field isn't available right now. Retry, or pick another served model.
- [`INSUFFICIENT_BALANCE`](https://5abeer.ai/en/docs/help/errors.md#INSUFFICIENT_BALANCE): your balance doesn't cover the request. Top up, then try again.
- [`API_KEY_QUOTA_EXHAUSTED`](https://5abeer.ai/en/docs/help/errors.md#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](https://5abeer.ai/en/docs/help/errors.md).

## 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](https://5abeer.ai/en/guides/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](https://5abeer.ai/en/docs/subscriptions/plan-apps.md#claude-code) and the [pricing page](https://5abeer.ai/en/pricing).

### Is 5abeer part of Anthropic?

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

## Sources

Checked on 2026-11-01:

- Claude Code docs: [Automate actions with hooks](https://code.claude.com/docs/en/hooks-guide)
- Claude Code docs: [Hooks reference](https://code.claude.com/docs/en/hooks)
- Claude Code docs: [Model configuration](https://code.claude.com/docs/en/model-config)

## 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.
