Advanced Tutorial

Automate Security Checks, Formatting, and Tests with Hooks

Event-driven automation for your AI coding workflow

Claude Code hooks are shell commands that run automatically before or after specific events — like pre-commit checks, post-edit formatting, or custom validation on every file change. They turn Claude Code from a smart assistant into a fully automated development pipeline.

Core Concept

What Are Hooks?

Hooks are the automation layer of Claude Code. They let you define shell commands that fire automatically when specific events occur during a Claude Code session — before a tool runs, after a file is edited, when a session starts, or when Claude sends a notification.

If you have used Git hooks (pre-commit, post-merge) or CI/CD pipeline triggers, the concept is identical. The difference is that Claude Code hooks operate at the AI interaction level: you can intercept and modify Claude's behavior in real time, not just at commit boundaries.

Hooks are defined in your settings.json file — either at the project level (.claude/settings.json in your repo) or at the user level (~/.claude/settings.json). Project-level hooks are shared with your team via version control. User-level hooks are personal preferences.

Event Reference

Built-in Hook Events

PreToolUse

Fires before Claude executes any tool (file write, bash command, etc.). Use this to validate, block, or modify operations before they happen.

Example: Block writes to production config files
PostToolUse

Fires after Claude completes a tool operation. Use this for post-processing — formatting code after edits, running tests after file changes, or logging what changed.

Example: Auto-format files after every edit
SessionStart

Fires once when a Claude Code session begins. Use this for environment setup — loading context files, checking prerequisites, or displaying project status.

Example: Load unreconciled Slack messages on startup
Notification

Fires when Claude sends a notification (e.g., when a background task completes). Use this to trigger alerts, sounds, or external integrations.

Example: Send a Slack message when a long task finishes

Step by Step

Creating Your First Hook

1

Create your settings file

If it does not already exist, create .claude/settings.json in your project root. This file configures Claude Code's behavior for this specific project.

2

Define the hook event and matcher

Choose which event to hook into (PreToolUse, PostToolUse, SessionStart, or Notification). If hooking tool events, add a matcher to filter by tool name — "Edit" for file edits, "Bash" for shell commands, "Write" for file creation.

3

Write the shell command

Your hook command is any valid shell expression. Environment variables like $CLAUDE_FILE_PATH and $CLAUDE_TOOL_INPUT give you context about what Claude is doing. Exit code 0 means success; non-zero exit codes block the operation (for PreToolUse hooks).

4

Test the hook

Start a new Claude Code session and trigger the event. Ask Claude to edit a file or run a command to verify your hook fires correctly. Check the Claude Code output for hook execution messages.

Copy-Paste Ready

Practical Examples

Auto-format on every file edit

Run Prettier automatically after Claude edits any file, ensuring consistent formatting without manual intervention.

settings.json
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "command": "npx prettier --write $CLAUDE_FILE_PATH"
    }]
  }
}

Security check before bash commands

Block dangerous commands like rm -rf or force pushes before they execute.

settings.json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "command": "echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'rm -rf|--force|--hard' && exit 1 || exit 0"
    }]
  }
}

Run tests after code changes

Automatically run relevant tests when Claude modifies source files, catching regressions immediately.

settings.json
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit",
      "command": "npm test -- --findRelatedTests $CLAUDE_FILE_PATH"
    }]
  }
}

Load project context on session start

Automatically surface unread messages, pending PRs, or project status when you start a Claude Code session.

settings.json
{
  "hooks": {
    "SessionStart": [{
      "command": "cat ~/project/STATUS.md && gh pr list --state open"
    }]
  }
}

Common Questions

Frequently Asked Questions

What is the difference between a hook and a slash command?

Slash commands are user-triggered — you type /command to run them manually. Hooks are event-triggered — they run automatically when a specific event occurs (like a file edit or tool call). Think of slash commands as buttons you press, and hooks as automated reactions.

How do I debug a hook that is not firing?

Check three things: (1) the hook is in the correct settings.json file (project-level .claude/settings.json or user-level ~/.claude/settings.json), (2) the event matcher matches the actual event name (case-sensitive), and (3) the shell command is valid and executable. Run the command manually in your terminal first to verify it works.

Do hooks impact Claude Code performance?

Hooks run synchronously — Claude waits for them to complete before proceeding. Fast hooks (linting a single file, running a formatter) add negligible delay. Slow hooks (full test suites, large builds) will block Claude's workflow. Keep hooks under 5 seconds for the best experience, or use async patterns for heavier operations.

Can I share hooks across my team?

Yes. Project-level hooks live in .claude/settings.json within your repository, so they are version-controlled and shared with everyone who clones the repo. User-level hooks in ~/.claude/settings.json are personal and stay on your machine. Use project-level for team standards (linting, security checks) and user-level for personal preferences.

Master Claude Code automation

Stop reading about it. Build something.