Why This Matters#

Imagine you hired an assistant to work with code. Hooks are like instructions you leave for this assistant: “Before deleting a file — double-check,” “After every change — make sure nothing broke.”

A hook (from the English word “hook”) is a script or instruction that automatically runs at a specific moment during Claude Code’s work. Hooks let you:

  • 🛡️ Block dangerous commands (e.g., deleting important files)
  • ✅ Check code before saving
  • 📋 Add context at the start of a session
  • 🔍 Control work quality

When Hooks Trigger#

Claude Code supports several events (moments) to which you can “attach” a hook:

Event When It Triggers What to Use It For
SessionStart When a session starts Load project settings
UserPromptSubmit When you submit a request Add context or validate the request
PreToolUse Before performing an action Block dangerous operations
PostToolUse After performing an action Check the result
Stop When Claude finishes work Verify everything is done
SessionEnd When a session ends Cleanup, logging

Two Types of Hooks#

1. Command Hooks#

Run a script (program). Good for quick, precise checks:

{
  "type": "command",
  "command": "bash ./scripts/check.sh",
  "timeout": 10
}

Use AI to analyze the situation. Good for complex checks:

{
  "type": "prompt",
  "prompt": "Check if this file operation is safe. Return 'approve' or 'deny'.",
  "timeout": 30
}

Step-by-Step: Setting Up Hooks#

Step 1: Where Hooks Are Stored#

Hooks can be configured in two places:

  • In user settings — file .claude/settings.json (applies to all projects)
  • In a plugin — file hooks/hooks.json inside the plugin folder

Step 2: Creating a Simple Hook — Blocking Dangerous Commands#

Create the file .claude/settings.json in your project:

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

Now create the script .claude/hooks/block-rm.sh:

#!/bin/bash
# Read data about the command Claude wants to execute
COMMAND=$(cat | jq -r '.tool_input.command')

# If the command contains rm -rf — block it!
if echo "$COMMAND" | grep -q 'rm -rf'; then
  echo '{"hookSpecificOutput": {"permissionDecision": "deny", "permissionDecisionReason": "Dangerous rm -rf command blocked by hook"}}'
else
  exit 0  # All good, allow it
fi

Step 3: Security Hook from the security-guidance Plugin#

The security-guidance plugin contains a hook that reminds about security when editing files:

{
  "description": "Security reminder hook",
  "hooks": {
    "PreToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/security_reminder_hook.py"
          }
        ],
        "matcher": "Edit|Write|MultiEdit"
      }
    ]
  }
}

Note:

  • matcher — a filter: the hook triggers only for Edit, Write, or MultiEdit operations
  • ${CLAUDE_PLUGIN_ROOT} — a variable pointing to the plugin folder (always use it for portability)

Step 4: Hooks from the Hookify Plugin#

The Hookify plugin is a powerful tool that lets you create hooks through simple .local.md files. Here’s an example blocking the dangerous rm command:

---
name: block-dangerous-rm
enabled: true
event: bash
pattern: rm\s+-rf
action: block
---

⚠️ **Dangerous rm command detected!**

This command may delete important files. Please:
- Verify the path is correct
- Consider a safer alternative
- Make sure you have a backup

And here’s a hook that requires running tests before finishing:

---
name: require-tests-run
enabled: false
event: stop
action: block
conditions:
  - field: transcript
    operator: not_contains
    pattern: npm test|pytest|cargo test
---

**No tests detected!**

Run tests before finishing to make sure the changes work.

Hookify intercepts all major events: PreToolUse, PostToolUse, Stop, UserPromptSubmit.

Matchers — Filters for Hooks#

A matcher is a filter that determines exactly when a hook triggers:

"matcher": "Write"           // Only for file writes
"matcher": "Read|Write|Edit" // For reading, writing, and editing
"matcher": "Bash"            // Only for bash commands
"matcher": "*"               // For all operations
"matcher": "mcp__.*"         // For all MCP tools

Important to Remember#

⚠️ Hooks are loaded at session start. If you change a hook, you need to restart Claude Code for changes to take effect.

For debugging, use:

claude --debug

You can check loaded hooks with the /hooks command inside Claude Code.

Lesson Summary#

  • Hooks are automatic scripts that trigger at specific moments during Claude Code’s work
  • There are command hooks (scripts) and prompt hooks (AI-based)
  • Hooks let you block dangerous operations, check code quality, and add context
  • Matchers filter which tools a hook applies to
  • The Hookify plugin lets you create hooks through simple Markdown files
  • Always use ${CLAUDE_PLUGIN_ROOT} for paths in plugins