Launch offer: use code LAUNCH20 for 20% off any single pack. See the packs

Claude Code Hooks: 7 Copy-Paste Examples That Actually Work

Published 2026-10-17 · 6 min read · by AgentKit Co

CLAUDE.md asks Claude Code to behave. A hook makes something happen every time, whether the model remembers or not. Hooks are shell commands (or HTTP calls, prompts or MCP tools) that Claude Code runs at fixed points: before a tool runs, after a file edit, when it needs your input, when a session starts, and so on.

This page gives seven hooks you can paste into a settings file today. They were checked against Anthropic's hooks guide and hooks reference on 2026-10-17, and the shell script in example 4 was tested by piping sample tool input into it. Claude Code changes quickly, so re-check the docs for anything you rely on.

Where hooks go

Hooks live under a hooks key in a settings file. Where you put them decides who they affect:

File Scope
~/.claude/settings.json All your projects, only on your machine
.claude/settings.json One project, shared with the team when you commit it
.claude/settings.local.json One project, just you

Each event name (PreToolUse, PostToolUse, Notification...) is a key inside the single hooks object. If your file already has hooks, add the new event next to the existing ones instead of replacing the whole object. Type /hooks in Claude Code to see what it loaded.

Most examples use jq to read the JSON that Claude Code sends to the hook on stdin. Install it first (brew install jq or apt-get install jq).

The two rules that make hooks click

  1. Input arrives as JSON on stdin. For a tool event you get tool_name and tool_input (for Bash, tool_input.command; for Edit and Write, tool_input.file_path).
  2. The exit code decides what happens. Exit 0 means no objection. Exit 2 blocks the action, and whatever you print to stderr is shown to Claude as the reason, so it can change course. Other exit codes are reported as non-blocking errors and the action goes ahead.

1. Get a desktop notification when Claude needs you

Stop watching the terminal. Add to ~/.claude/settings.json (Linux shown; on macOS use osascript -e 'display notification "Claude Code needs your attention" with title "Claude Code"'):

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          { "type": "command", "command": "notify-send 'Claude Code' 'Claude Code needs your attention'" }
        ]
      }
    ]
  }
}

An empty matcher fires on every notification type. Use permission_prompt to be told only when Claude waits for an approval, or idle_prompt for when it finished and you have not typed for about a minute.

2. Auto-format every file Claude edits

A PostToolUse hook with an Edit|Write matcher runs right after those tools succeed. Put this in .claude/settings.json:

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

Swap Prettier for black, gofmt -w or rustfmt as needed. A successful hook prints nothing, so check the file to confirm it ran.

3. Block edits to protected files

Save as .claude/hooks/protect-files.sh and run chmod +x on it:

#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
FILE_PATH="${FILE_PATH//\\//}"   # normalize Windows separators

for pattern in ".env" "package-lock.json" ".git/"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done
exit 0

Register it as a PreToolUse hook so it runs before the edit happens:

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

Note that Claude can also change files through shell commands, which an Edit|Write matcher does not see. For files that must never be read or touched, add a deny rule too (see our settings.json permissions guide).

4. Block force pushes, pushes to main, reset --hard and rm -rf

This is our own script. Save as .claude/hooks/block-risky-bash.sh and chmod +x it:

#!/bin/bash
INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

block() { echo "Blocked by hook: $1. Ask the user to run it themselves if it is really needed." >&2; exit 2; }

[[ "$CMD" =~ git[[:space:]]+push.*(--force|[[:space:]]-f([[:space:]]|$)) ]] && block "force push"
[[ "$CMD" =~ git[[:space:]]+push.*[[:space:]](main|master)([[:space:]]|$) ]] && block "direct push to main/master"
[[ "$CMD" =~ git[[:space:]]+reset[[:space:]]+--hard ]] && block "git reset --hard"
[[ "$CMD" =~ rm[[:space:]]+-[a-zA-Z]*r[a-zA-Z]*f|rm[[:space:]]+-[a-zA-Z]*f[a-zA-Z]*r ]] && block "rm -rf"
exit 0

Register it on Bash:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-risky-bash.sh" }
        ]
      }
    ]
  }
}

We tested it by piping sample input, for example echo '{"tool_name":"Bash","tool_input":{"command":"git push -f"}}' | ./block-risky-bash.sh; echo $?. It returned 2 for git push --force origin feat, git push -f, git push origin main, git reset --hard HEAD~1, rm -rf build and rm -fr x, and 0 for git push origin feat, git push origin feature/main-fix, npm test and ls -la. Pattern matching on command text is a speed bump, not a sandbox: a determined command can be written in a way no regex catches. The docs note that a PreToolUse deny blocks the call even in bypassPermissions mode, which is why this pattern is popular, but pair it with permission rules for anything critical.

5. Re-inject key context after compaction

When the context fills up, Claude Code compacts the conversation and details can get lost. A SessionStart hook with the compact matcher runs after each compaction, and plain text it prints is added to Claude's context:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          { "type": "command", "command": "echo 'Reminder: use pnpm, not npm. Run pnpm test before saying done.'; git log --oneline -5" }
        ]
      }
    ]
  }
}

For context you want in every session, CLAUDE.md is the better home. Keep this hook for the few lines that must survive compaction.

6. Log every shell command Claude runs

Useful for reviewing a long session afterwards:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt" }
        ]
      }
    ]
  }
}

7. Run a hook only for some commands with if

The if field uses permission rule syntax, so the hook process starts only when the call matches. This runs a policy script for git commands only, not for every Bash call:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(git *)",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
          }
        ]
      }
    ]
  }
}

if works only on tool events (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied); on any other event it stops the hook from running. The docs call this filter best-effort, so do not use it as your only enforcement.

When a hook does not fire

  • Run /hooks and check it is listed under the right event. Matchers are case-sensitive: Bash, not bash.
  • Test the script by hand with sample JSON and check echo $?.
  • "command not found": use "$CLAUDE_PROJECT_DIR" paths, and chmod +x the script.
  • Invalid JSON in the settings file (a trailing comma, a comment) stops hooks from loading.
  • A Stop hook that blocks should check stop_hook_active in its input and exit early when it is true, or Claude Code overrides it after it blocks eight times in a row.

Going further

Hooks are one layer. CLAUDE.md or AGENTS.md explains the project, permission rules in settings.json enforce hard limits, and hooks automate the rest. Our free AGENTS.md generator builds a starting instruction file, and the free Agent Skills add reusable workflows such as prove-it-ran. If test output is eating your usage, the Claude Code Usage Saver Kit includes a hook that keeps only the failing tests.

Written with AI assistance and checked against the official documentation on the date above. Not affiliated with Anthropic.

AI Coding Agent Config Kit

Instruction-file templates for 12 stacks and 12 prompts that write and audit them. 34-page PDF, $14.

Not ready to buy? Start with the free PDF.

Get 10 of our most-used prompts as a free PDF from Gumroad. You will be asked for your email so Gumroad can deliver it.

Keep reading

Agent Skills (SKILL.md) Explained: Formats, Folders and SafetyWhat an Agent Skill is, how SKILL.md works, which folders Claude Code, Codex, Cursor and Copilot read, and how to write and vet one. Checked on 2026-10-17.4 min readAGENTS.md vs CLAUDE.md vs Cursor Rules: Which Do You Need?AGENTS.md, CLAUDE.md, Cursor rules and Copilot instructions explained: what each file does, which tools read it, and a simple setup that keeps them in sync.3 min readWhat Is an AI Agent? Plain-English Guide for BeginnersWhat an AI agent is, how it differs from a chatbot or automation, and the four building blocks every agent has. No jargon.4 min read