AI for Automation
Hooks — Auto-Execute Rules

Step 19 / 22

Hooks — Auto-Execute Rules

Auto-format, block dangerous commands, send notifications

Hooks = Rules That Run Automatically

Have you ever wished Claude Code could do things like “auto-format code every time a file is saved” or “block dangerous commands entirely”?

That’s exactly what Hooks do. They let you set up automatic rules that trigger whenever Claude Code performs certain actions.

Think of them like automatic alarms. Just like an alarm goes off at 7 AM every morning, Hooks auto-format code when a file is saved, or block dangerous commands when detected.

Everyday AutomationClaude Code Hooks
7 AM → alarm ringsFile saved → auto-formatting
Front door opens → camera recordsCommand runs → safety check
Fridge temp high → alertError detected → Telegram notification
Fire detected → sprinklers activaterm -rf detected → execution blocked
End of workday → computer locksSession ends → work log saved

One-line summary

Hooks = “When this happens, automatically do that” — rules you set up in advance. Configure once and they run on their own forever.

7 Hook Events — When Do They Trigger?

For Hooks to run automatically, you need to specify “when.” Claude Code offers 7 trigger points (events). In practice, you’ll only use 3-4 of them regularly.

PreToolUse

Runs right before Claude uses a tool. Use it to inspect and block dangerous commands.

★ Most commonly used hook

PostToolUse

Runs right after Claude uses a tool. Use it for auto-formatting after file saves.

★ Most commonly used hook

🔔

Notification

Runs when Claude is waiting for user input. Send completion notifications via Telegram or Slack.

🚀

SessionStart

Runs when a Claude Code session begins. Use for environment checks or initial setup.

🚫

SessionEnd

Runs when a Claude Code session ends. Use for saving work logs or cleanup tasks.

💬

PreSendMessage

Runs just before Claude sends a response. Use to inspect or modify response content.

📝

PostSendMessage

Runs right after Claude sends a response. Use for response logging or post-processing.

No need to memorize all 7

In practice, just PreToolUse (blocking dangers) and PostToolUse (auto-formatting) are enough to start. Add others one at a time as needed.

Hooks = Claude Code’s Automatic Alarms

Set them once, and Claude handles what you used to do manually every time

How to Configure — One Place: settings.json

Hooks are configured in settings.json. Don’t overthink it — just write your rules in JSON format.

Settings File Locations — 3 Options

ScopeFile LocationDescription
This project only.claude/settings.jsonSaved inside the project folder. Shareable with team
All my projects~/.claude/settings.jsonSaved in home folder. Applies everywhere
This project (me only).claude/settings.local.jsonPersonal settings not committed to git

Start with project-level settings (.claude/settings.json). Move hooks to the home folder later if you want them everywhere.

Basic Structure

.claude/settings.json
{
  "hooks": {
    "event-name": [
      {
        "matcher": "which tools to apply to",
        "hooks": [
          {
            "type": "command",
            "command": "command to run"
          }
        ]
      }
    ]
  }
}

The structure looks complex but follows a pattern. Three key elements:

1
Event name (when?)

One of 7 events: PreToolUse, PostToolUse, etc.

2
Matcher (which tool?)

Target specific tools like Bash, Write, Edit, or apply to all

3
Command (what to run?)

Shell command or script path to execute

Exit Code Meanings

The number (exit code) returned by a hook script determines Claude Code’s behavior.

Exit CodeMeaningClaude Code Behavior
0Success (no issues)Proceed as normal
1WarningProceed but show a warning message to the user
2BlockCompletely block the tool from being used

Easy to remember

0 = Green light (pass), 1 = Yellow light (caution, proceed), 2 = Red light (stop, blocked). Think of it like a traffic light.

Example 1: Auto-Format on File Save

One of the most popular hooks is auto-formatting. Every time Claude edits a file, Prettier or ESLint runs automatically to enforce code style.

Instead of manually running npx prettier --write each time, set it up once as a hook and it’s automatic forever.

.claude/settings.json — Auto-run Prettier
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write $CLAUDE_FILE_PATH"
          }
        ]
      }
    ]
  }
}

Let’s break down what this means:

Configuration Explained

PostToolUse

Runs right after a tool is used

Write|Edit

Only triggers for file Write or Edit tools

command

$CLAUDE_FILE_PATH is automatically replaced with the modified file path. It formats only the changed file.

Environment Variables Available in Hooks

Hook scripts can use environment variables provided by Claude Code to know which file was modified or what command was attempted.

VariableValueUsage
$CLAUDE_FILE_PATHModified file pathFormat only that file
$CLAUDE_TOOL_INPUTInput passed to the tool (JSON)Inspect command content
$CLAUDE_SESSION_IDCurrent session IDLog recording

ESLint + Prettier Combo

.claude/settings.json — ESLint + Prettier together
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npx eslint --fix $CLAUDE_FILE_PATH && npx prettier --write $CLAUDE_FILE_PATH"
          }
        ]
      }
    ]
  }
}

Prettier must be installed

This hook requires Prettier to be installed in your project. Run npm install -D prettier first. Same for ESLint: npm install -D eslint.

Example 2: Auto-Block Dangerous Commands

Claude Code is powerful, but it can sometimes try to run dangerous commands. For example, rm -rf / could wipe your entire system.

Let’s create a hook that automatically blocks such commands. This time we use PreToolUse — it checks before a tool is used and blocks if dangerous.

.claude/settings.json — Block dangerous commands
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/safety-check.sh"
          }
        ]
      }
    ]
  }
}

Since the command can get long, we separated it into a script file. Create .claude/hooks/safety-check.sh with the checking logic.

.claude/hooks/safety-check.sh — Safety check script
#!/bin/bash
# Claude Code safety check hook
# Exit codes: 0=pass, 1=warn, 2=block

# Read the input command from environment variable
INPUT="$CLAUDE_TOOL_INPUT"

# Dangerous pattern list
DANGEROUS_PATTERNS=(
  "rm -rf /"
  "rm -rf ~"
  "rm -rf *"
  "mkfs"
  "dd if="
  "> /dev/sda"
  "chmod -R 777 /"
  ":(){ :|:& };:"
)

# Check each pattern
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
  if echo "$INPUT" | grep -q "$pattern"; then
    echo "BLOCKED: Dangerous command detected."
    echo "Pattern: $pattern"
    exit 2  # Block
  fi
done

# Warning patterns (not blocked, but caution shown)
WARNING_PATTERNS=(
  "sudo"
  "npm publish"
  "git push --force"
  "git reset --hard"
)

for pattern in "${WARNING_PATTERNS[@]}"; do
  if echo "$INPUT" | grep -q "$pattern"; then
    echo "WARNING: This command requires caution."
    echo "Pattern: $pattern"
    exit 1  # Warn (proceed allowed)
  fi
done

# No issues found, pass
exit 0

Exit Code Behavior

🟢

exit 0

Pass

Safe command, proceed

🟠

exit 1

Warning

Show caution message, proceed

🔴

exit 2

Block

Prevent execution entirely

Simple Inline Block (No Script File)

If you don’t want a separate script file, you can use a simple inline command:

One-liner to block rm -rf
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo $CLAUDE_TOOL_INPUT | grep -q 'rm -rf' && exit 2 || exit 0"
          }
        ]
      }
    ]
  }
}

Script needs execute permission

If you created a separate script file, it needs execute permission. Run chmod +x .claude/hooks/safety-check.sh once. On Windows, this isn’t needed.

Example 3: Send Completion Notifications

When you give Claude Code a long task, you’d normally have to watch the screen. With a Notification hook, you get alerted automatically when work is done.

Desktop Notification (Simplest)

macOS desktop notification
{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code task complete!\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}
Windows desktop notification (PowerShell)
{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "powershell -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code task complete!')\""
          }
        ]
      }
    ]
  }
}

Telegram Notification (Get Alerts Anywhere)

Using the Telegram Bot API, you can receive alerts on your phone even when you’re away.

Telegram notification script (.claude/hooks/notify-telegram.sh)
#!/bin/bash
# Send Claude Code notification via Telegram

BOT_TOKEN="your-bot-token"
CHAT_ID="your-chat-id"
MESSAGE="Claude Code task complete."

curl -s -X POST \
  "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" \
  -d "chat_id=$CHAT_ID" \
  -d "text=$MESSAGE" \
  > /dev/null

exit 0
Register in settings.json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/notify-telegram.sh"
          }
        ]
      }
    ]
  }
}

What does empty matcher mean?

"matcher": "" means “run in all cases.” Notification events aren’t tied to specific tools, so leaving the matcher empty is standard.

Try It Yourself

Reading alone won’t cut it. Follow these exercises, starting with the easiest.

Exercise 1: Set Up Auto-Formatting Hook (Easiest)

Try this in a project with Prettier installed. After setup, ask Claude to edit a file and see it auto-format.

Step 1: Create .claude folder
mkdir -p .claude
Step 2: Create settings.json
# Create .claude/settings.json with this content
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write $CLAUDE_FILE_PATH"
          }
        ]
      }
    ]
  }
}
Step 3: Ask Claude to edit a file
Add console.log("hello world") to index.js.

After Claude edits the file, verify that Prettier ran automatically!

Exercise 2: Create a Danger-Blocking Hook

Set up a hook that detects and blocks rm -rf.

Add to settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo $CLAUDE_TOOL_INPUT | grep -q 'rm -rf' && exit 2 || exit 0"
          }
        ]
      }
    ]
  }
}
Test: Ask Claude to run a dangerous command
Run rm -rf / for me

If you see a “Blocked” message, success!

Exercise 3: Ask Claude Code Directly

The easiest approach is to just ask Claude Code to create it.

Say this
Set up Claude Code hooks for me.
1. Auto-run prettier after file edits
2. Block rm -rf commands
3. Desktop notification on task completion
Configure in .claude/settings.json.

Verification Checklist

If hooks aren’t working, check that the script file has execute permission. Run chmod +x .claude/hooks/script.sh. Still not working? Ask Claude “The hook isn’t working, help me debug” and it’ll find the cause.

Tips + Sources

Practical tips for effective Hook usage.

1. Start small

Don’t build complex hooks from the start. Set up auto-formatting first, get the feel, then add blocking hooks and notifications one at a time. Complex hooks are harder to debug.

2. You can combine multiple hooks

Register multiple hooks on the same event. For example, PostToolUse can chain formatting + linting + type checking in sequence. If an earlier hook fails, later ones won’t run.

3. Matcher supports regex

Use | to match multiple tools like Write|Edit. Use .* for all tools, or specify exact tool names.

4. Share with your team

.claude/settings.json can be committed to Git. When the entire team uses the same safety rules and auto-formatting, code quality is maintained automatically. Personal settings go in settings.local.json.

5. Ask Claude to create hooks for you

If writing JSON by hand is hard, just tell Claude Code “create this hook for me.” It generates the settings.json and any needed scripts.

Hooks = Claude Code’s Automatic Alarms

Auto-format on save, auto-block dangers, auto-notify on completion. Set it once and you never have to think about it.

Even one hook makes a difference

Just setting up the auto-formatting hook will give you that “wow, this is so convenient” moment. From there, add more one at a time. No need to make everything perfect from the start.

References