Skip to Content

Claude Code Hooks: The Complete Setup Guide for Beginners

September 22, 2026 by
aliakram

Claude Code can read files, edit code, run commands, execute tests, and work through multi-step development tasks. Claude Code Hooks let you add deterministic automation around those actions.

For example, you can configure Claude Code to:

  • format a file after Claude edits it
  • inspect a shell command before it runs
  • block changes to protected files
  • notify you when Claude needs your attention
  • load project context when a session starts
  • record configuration changes
  • validate work before Claude stops
  • run an external command or MCP tool after a specific event

The key idea is simple:

Claude decides what to do; hooks let you define what must happen when a specific event occurs.

That distinction makes hooks particularly useful for repetitive tasks, guardrails, validation, and project automation.

This guide explains Claude Code Hooks from the beginning, including events, matchers, configuration, command hooks, structured output, security, troubleshooting, Windows considerations, and practical examples.

What Are Claude Code Hooks?

Claude Code Hooks are user-defined automations that run at specific points in Claude Code's lifecycle.

The simplest mental model is:

Claude Code event
       ↓
    Matcher
       ↓
 Hook condition
       ↓
     Action
       ↓
Command / script / prompt / agent / MCP tool
       ↓
Result

Suppose Claude wants to run:

rm -rf ./build

A PreToolUse hook can inspect that operation before it happens.

The workflow becomes:

Claude requests Bash
       ↓
PreToolUse
       ↓
Matcher: Bash
       ↓
Hook checks command
       ↓
Safe → continue
Risky → deny

This is different from simply putting an instruction inside CLAUDE.md.

CLAUDE.md says:

"Don't delete the production directory."

A hook can enforce a deterministic rule around the relevant operation.

That difference is one of the main reasons hooks are useful.

Claude Code Hooks vs Normal Commands

A normal shell command only runs when something explicitly invokes it.

For example:

npm run lint

does nothing until you execute it.

A hook attaches that command to an event:

Claude edits code
      ↓
PostToolUse
      ↓
npm run lint

The command has become event-driven automation.

Claude Code's official documentation describes hooks as a way to create deterministic behavior around lifecycle events rather than relying on the model to remember to perform an action.

That makes hooks particularly useful for rules that should happen consistently.

Why Use Claude Code Hooks?

There are several practical reasons to use them.

Automatic formatting

Problem: Claude edits a TypeScript file and the formatting needs to be fixed afterward.

Hook solution: Use PostToolUse with an Edit|Write matcher and run your formatter.

Result: Formatting becomes part of the workflow rather than another instruction you have to remember.

Security validation

Problem: You don't want Claude to execute certain dangerous commands.

Hook solution: Use PreToolUse and inspect the command before execution.

Result: The hook can return a denial decision for matching operations.

Protected files

Problem: Certain files such as production configuration or secret-related files should not be modified automatically.

Hook solution: Match file-editing tools and run a validation script.

Result: The hook can stop a prohibited operation before it proceeds.

Notifications

Problem: Claude is working on a long task and you don't want to keep watching the terminal.

Hook solution: Use the Notification event to trigger an operating-system notification or external notification workflow.

Result: You can work on something else and return when Claude needs attention.

Session initialization

Problem: Every Claude Code session needs the same project information.

Hook solution: Use SessionStart to load relevant context.

Result: Claude can receive automatically generated context when the session begins.

Configuration auditing

Problem: You want to know when Claude Code configuration changes.

Hook solution: Use ConfigChange.

Result: The hook can record which configuration changed and when.

How Claude Code Hooks Work

A hook normally has several components.

1. Event

The event identifies when the hook fires.

Examples include:

SessionStart
UserPromptSubmit
PreToolUse
PostToolUse
Notification
Stop
SessionEnd
PreCompact
PostCompact
SubagentStart
SubagentStop
ConfigChange
FileChanged

The current Claude Code event system is much larger than the short event lists found in many older tutorials.

The official reference currently documents events covering tool execution, sessions, prompts, subagents, tasks, teams, configuration, files, worktrees, compaction, model switching, MCP elicitation, and other lifecycle operations.

2. Matcher

The matcher determines which event instances should activate a hook.

For tool events:

"matcher": "Bash"

means the hook targets Bash tool calls.

You can also match multiple tools:

"matcher": "Edit|Write"

A matcher can also use regular expressions when the value requires regex syntax.

3. if condition

Current Claude Code also supports an if condition for tool-related events.

This gives you another filtering layer.

For example:

"matcher": "Bash",
"if": "Bash(git *)"

Conceptually:

Event
  ↓
Matcher
  ↓
Bash?
  ↓
if condition
  ↓
git command?
  ↓
Run hook

This can be useful because the broader matcher can identify a tool while the if rule narrows the operation further.

For example, you might want a hook for Bash commands generally but only run an expensive security check when the command begins with git.

Claude Code Hook Lifecycle

A simplified workflow looks like this:

Session starts
      ↓
SessionStart
      ↓
User submits prompt
      ↓
UserPromptSubmit
      ↓
Claude calls a tool
      ↓
PreToolUse
      ↓
Tool executes
      ↓
PostToolUse
      ↓
Claude continues
      ↓
Stop
      ↓
SessionEnd

That is only a simplified view.

Modern Claude Code also has events such as:

PostToolUseFailure
PostToolBatch
PermissionRequest
PermissionDenied
SubagentStart
SubagentStop
TaskCreated
TaskCompleted
TeammateIdle
InstructionsLoaded
ConfigChange
CwdChanged
DirectoryAdded
FileChanged
WorktreeCreate
WorktreeRemove
PreCompact
PostCompact
PreModelSwitch
PostModelSwitch
Elicitation
ElicitationResult

The exact behavior differs by event.

For example, some events can block an operation while others exist mainly for logging or side effects.

Claude Code Hook Events

Here are some of the most useful current events for beginners:

Hook EventWhen It RunsPractical Use
SessionStartSession starts/resumesLoad context or environment information
UserPromptSubmitUser submits a promptValidate or add context
PreToolUseBefore a tool executesSecurity and validation
PermissionRequestPermission decision is requestedAutomate supported permission decisions
PostToolUseTool succeedsFormat, validate, log
PostToolUseFailureTool failsError logging or recovery
PostToolBatchParallel tool batch finishesBatch validation
NotificationClaude Code sends notificationDesktop/external alerts
SubagentStartSubagent startsTrack subagent activity
SubagentStopSubagent finishesValidate/report subagent work
StopClaude finishes respondingFinal checks
StopFailureTurn ends because of API errorFailure handling
SessionEndSession endsCleanup/logging
InstructionsLoadedCLAUDE.md/rules loadAudit instruction loading
ConfigChangeConfiguration changesConfiguration auditing
FileChangedWatched file changesFile automation
PreCompactBefore compactionPreserve information
PostCompactAfter compactionRestore/reinject context
PreModelSwitchBefore model switchControl model changes
PostModelSwitchAfter model switchAdd model-specific context

This is not merely a cosmetic expansion of the old hooks system. The newer events allow developers to build much more targeted automation.

Where Are Claude Code Hooks Configured?

Common settings locations include:

~/.claude/settings.json

for user settings,

.claude/settings.json

for project settings,

and:

.claude/settings.local.json

for local project settings.

Claude Code can also obtain hooks through plugins and session-level mechanisms.

The /hooks command provides a read-only browser showing configured hooks and their sources, including:

  • User Settings
  • Project Settings
  • Local Settings
  • Plugin Hooks
  • Session Hooks

This makes /hooks one of the easiest ways to confirm whether Claude Code actually loaded your configuration.

Your First Claude Code Hook

Start with something harmless.

Do not begin with a complicated security system.

A notification hook is a good example because it produces an obvious result.

Open:

~/.claude/settings.json

and add:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "YOUR_NOTIFICATION_COMMAND"
          }
        ]
      }
    ]
  }
}

Replace YOUR_NOTIFICATION_COMMAND with a command appropriate for your operating system.

The important structure is:

hooks
  ↓
Notification
  ↓
matcher
  ↓
command

If your settings file already contains a hooks object, do not replace it.

Add the new event alongside the existing events.

For example:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "YOUR_FORMATTER"
          }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "YOUR_NOTIFICATION_COMMAND"
          }
        ]
      }
    ]
  }
}

Verify Your Hook With /hooks

After saving the settings, start Claude Code and run:

/hooks

The /hooks interface is useful for checking configuration.

It can show:

  • event
  • matcher
  • handler type
  • source settings file
  • command
  • prompt
  • URL

The menu is read-only. Configuration changes are made in the settings files or through Claude Code.

This is an important troubleshooting step that many beginner tutorials miss.

Instead of immediately assuming the command is broken, first ask:

Did Claude Code actually load the hook?

Hook Input: The Important Part Beginners Often Miss

Command hooks receive event information through standard input as JSON.

For example, a PreToolUse hook for Bash can receive data similar to:

{
  "session_id": "abc123",
  "cwd": "/home/user/my-project",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

That means your script can read the JSON and make decisions based on the actual event.

A Bash script can start with:

#!/bin/bash

input=$(cat)

echo "$input"

The exact fields depend on the event.

This is more reliable than assuming a fixed set of environment variables contains every piece of information you need.

Hook Output and Exit Codes

This is another area where older tutorials can be misleading.

A command hook can communicate with Claude Code through its exit code and structured stdout/stderr behavior.

The basic concepts are:

Exit 0

Generally means the hook completed successfully.

For standard decision events, Claude Code can also parse structured JSON from stdout.

Exit 2

For events using the standard decision model, exit code 2 represents a blocking error.

The stderr output can be surfaced as the reason.

Other non-zero codes

Behavior depends on the event.

Some events treat errors as non-blocking warnings, while others have special behavior.

Therefore, don't use the simplistic rule:

"Any non-zero exit code always blocks."

That is not correct across every current Claude Code hook event.

Blocking a Dangerous Bash Command

Here is the basic idea of a security hook:

Claude wants to run Bash
        ↓
PreToolUse
        ↓
Matcher = Bash
        ↓
Security script reads stdin JSON
        ↓
Inspect tool_input.command
        ↓
Dangerous?
   ↙          ↘
 YES           NO
 ↓              ↓
DENY          Continue

A structured decision can look like:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Destructive command blocked by hook"
  }
}

The important detail is that the hook is not merely printing:

Blocked

It is returning a structured decision that Claude Code understands.

A Powerful Feature: additionalContext

Hooks can also send useful information back into Claude's context.

For example:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "This file is generated. Do not edit it directly."
  }
}

Claude Code can inject that information as context at the appropriate point in the conversation.

This creates an interesting workflow:

Tool action
    ↓
Hook checks result
    ↓
Hook generates context
    ↓
Claude receives context
    ↓
Claude adjusts next action

This is particularly useful when automation needs to inform Claude about something discovered during the workflow.

For example:

  • generated files
  • validation results
  • environment information
  • repository state
  • test failures
  • project-specific restrictions

Example 1: Auto-Format Edited Files

A common hook is:

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

The flow is:

Claude edits file
      ↓
PostToolUse
      ↓
Edit or Write?
      ↓
Extract file path
      ↓
Prettier
      ↓
Formatted file

This is much better than running:

npx prettier --write .

after every edit because the hook can target the affected file.

Important

The example assumes:

  • jq is installed
  • Prettier is available
  • the project actually uses Prettier
  • the incoming tool input contains the expected file path

If your project uses another formatter, use that instead.

Example 2: Run Tests Without Making Claude Slow

It is tempting to write:

After every edit → run the entire test suite

but that can become expensive.

Imagine Claude edits 15 files.

You could accidentally create:

Edit
 ↓
Full test suite

Edit
 ↓
Full test suite

Edit
 ↓
Full test suite

A better approach is to consider:

  • lightweight validation after edits
  • targeted tests
  • a final test stage
  • asynchronous hooks
  • project-specific scripts

Claude Code supports asynchronous command hooks, which can be useful when you want a longer-running operation to continue in the background rather than holding up the normal workflow.

For example:

File changed
    ↓
PostToolUse
    ↓
Async test
    ↓
Claude continues

This is a better design for some expensive checks than forcing every operation to wait.

Example 3: Protect Sensitive Files

Suppose your project contains:

.env
production.config.js
credentials.json

You could create a PreToolUse hook around file-editing operations.

Conceptually:

Edit/Write
    ↓
PreToolUse
    ↓
Check path
    ↓
Protected?
  ↙       ↘
Yes       No
 ↓         ↓
Deny    Continue

The exact implementation should inspect the actual tool_input received by the hook.

Do not rely on a simple filename check if your security requirement is more complicated.

Example 4: Security Check for Bash and PowerShell

Current Claude Code can target both Bash and PowerShell tool calls.

For example:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|PowerShell",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "YOUR_SECURITY_SCRIPT"
          },
          {
            "type": "command",
            "if": "PowerShell(Remove-Item *)",
            "command": "YOUR_SECURITY_SCRIPT"
          }
        ]
      }
    ]
  }
}

The important concept here is the combination of:

matcher
+
if

The matcher identifies relevant tools.

The if condition narrows the actual command pattern.

This can reduce unnecessary script execution.

Example 5: Notifications

The current official Claude Code guide provides platform-specific examples for:

  • macOS
  • Linux
  • Windows PowerShell

The Notification event can fire when Claude Code sends notifications such as when Claude needs attention or permission.

A notification hook receives information including:

{
  "hook_event_name": "Notification",
  "message": "Claude needs your permission",
  "title": "Permission needed",
  "notification_type": "permission_prompt"
}

This means your notification script can use the actual notification message rather than showing the same hard-coded text every time.

A notification hook is intended for side effects. It cannot block or modify the notification itself.

Example 6: SessionStart and Automatic Context

SessionStart is useful when a project needs dynamic information at the beginning of a session.

For example, a hook could collect:

Current branch
Uncommitted files
Current issue
Environment information
Project status

Then return additional context:

{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Current branch: feature/auth\nUncommitted files: src/auth.ts"
  }
}

This creates a useful pattern:

New Claude session
       ↓
SessionStart hook
       ↓
Collect current project state
       ↓
Return context
       ↓
Claude starts with fresh information

This can be particularly useful in repositories where Git state changes frequently.

Example 7: Re-Inject Context After Compaction

Claude Code also provides:

PreCompact
PostCompact

events.

These are useful when you build workflows around context compaction.

For example:

Long session
    ↓
Context gets compacted
    ↓
PostCompact
    ↓
Restore useful project information
    ↓
Claude continues

This is a more advanced use case.

Don't build a complicated compaction workflow until you understand how Claude Code's normal context management behaves.

Example 8: Audit Configuration Changes

The ConfigChange event can monitor configuration changes.

A simple architecture is:

Claude Code configuration changes
            ↓
       ConfigChange
            ↓
     Audit script
            ↓
      Log the change

The event can provide information about the configuration source and, where available, the affected file.

This can be useful for teams that want better visibility into automated-development environments.

Example 9: InstructionsLoaded

A particularly useful newer event is:

InstructionsLoaded

It fires when Claude Code loads instruction files such as:

CLAUDE.md
.claude/rules/*.md

This is useful for:

  • audit logging
  • compliance tracking
  • observability
  • understanding which instructions were loaded

An important limitation is that InstructionsLoaded does not provide decision control.

In other words, it is useful for observing instruction loading, not for blocking it.

Example 10: SessionEnd

SessionEnd runs when a Claude Code session ends.

Possible uses include:

  • logging
  • cleanup
  • saving session statistics
  • recording the end reason

The hook receives a reason such as:

clear
resume
logout
prompt_input_exit
other

Unlike PreToolUse, SessionEnd cannot stop the session from ending.

That makes it a good lifecycle-cleanup event rather than a security gate.

Matchers Explained

Matchers deserve special attention because they determine how often your automation fires.

A basic matcher:

"matcher": "Bash"

matches Bash.

Multiple exact tools:

"matcher": "Edit|Write"

can target either tool.

A matcher can also use regular expressions.

For example:

"matcher": "^Notebook"

can match tool names beginning with Notebook.

Current Claude Code matcher behavior also has version-specific details around exact matching, commas, hyphens, and regex interpretation.

For a production configuration, check the current Claude Code version and documentation instead of copying an old matcher example blindly.

Matcher vs if

This distinction is worth understanding.

You can think of it as:

matcher
= Which event/tool family?

if
= Which specific operation inside it?

For example:

"matcher": "Bash",
"if": "Bash(git *)"

means:

Only Bash
   ↓
Only git-related Bash commands
   ↓
Run hook

This can be more efficient than running your script for every Bash command.

Hooks vs Commands vs MCP

FeatureHooksSlash CommandsMCP
Main purposeEvent-driven automationUser-invoked workflowExternal tools/data
TriggerLifecycle eventUserClaude/tool interaction
Deterministic automationStrongLimitedDepends on tool
Security/guardrailsStrong for supported eventsLimitedDepends on implementation
External servicesPossiblePossibleCore use case
Best use caseValidation/automationRepeatable manual workflowExternal capability
Beginner difficultyMediumLowMedium

The three technologies are complementary.

For example:

Slash command
     ↓
Claude starts workflow
     ↓
MCP provides external data
     ↓
Hook validates tool operation

There is no reason to force every workflow into one mechanism.

Hooks vs CLAUDE.md

This is one of the most important distinctions for beginners.

Use CLAUDE.md for instructions

For example:

Use TypeScript strict mode.

Follow the existing naming conventions.

Run the test suite before considering the task complete.

These are instructions and project context.

Use Hooks for automatic reactions

For example:

Before Bash command → inspect command

After Edit → run formatter

At session start → load Git state

After configuration change → record audit entry

A simple mental model:

CLAUDE.md
= "These are the rules."

Hooks
= "When this happens, automatically do this."

You can combine them.

For example, CLAUDE.md can explain the project's testing policy while a hook performs a deterministic validation step.

Prompt-Based and Agent-Based Hooks

Current Claude Code Hooks are not limited to shell commands.

The official system supports several handler types, including:

command
http
mcp_tool
prompt
agent

This matters because not every condition is deterministic.

Command hook

Best for:

Run this exact script.

Examples:

  • formatter
  • linter
  • security check
  • logging

Prompt hook

Useful when a Claude model should evaluate a condition rather than a fixed shell rule.

Agent hook

Can use a subagent to investigate a condition using tools such as reading or searching files.

The official documentation currently describes agent hooks as experimental, so they should be treated differently from mature command-based automation.

HTTP hook

Useful when the event needs to call an HTTP endpoint.

MCP tool hook

Allows a hook to invoke an MCP tool directly.

This creates another useful distinction:

Deterministic rule
      ↓
Command hook

Model-based judgment
      ↓
Prompt / Agent hook

External service
      ↓
HTTP hook

External tool
      ↓
MCP tool hook

Choose the simplest mechanism that solves the problem.

Claude Code Hooks Security

Hooks are powerful because they execute automatically.

That is also the main security concern.

A hook can potentially:

  • execute shell commands
  • read files
  • modify files
  • make network requests
  • interact with external services
  • block operations
  • receive sensitive tool input

Therefore, treat hook configuration as executable automation.

Do not blindly copy hooks

A repository may contain:

.claude/settings.json
.claude/hooks/

Inspect both.

A small JSON configuration might call a large shell script.

The risk is in what the script actually does.

Avoid secrets

Do not hard-code:

API keys
passwords
tokens
private credentials

into hook files.

Remember that hook input can contain tool arguments, paths, prompts, and other project information.

Be careful with third-party hooks

Before installing a hook from GitHub or a blog:

  1. Read the configuration.
  2. Find every referenced script.
  3. Read those scripts.
  4. Check network requests.
  5. Check file writes.
  6. Check destructive commands.
  7. Check environment-variable usage.
  8. Test in a safe environment.

Hooks and Workspace Trust

Workspace trust matters because hooks can execute code automatically.

Treat a project containing hook configuration similarly to a project containing executable scripts.

This is particularly important when cloning unfamiliar repositories.

A repository can contain automation that executes as soon as Claude Code reaches the relevant event.

Do not assume:

"It's only a settings file, so it's safe."

The settings file can launch commands.

Don't Make Hooks Slow

Hooks can dramatically affect the developer experience if they execute expensive operations too often.

Bad design:

Every file edit
      ↓
Full build
      ↓
Full test suite
      ↓
Dependency scan

Better:

File edit
   ↓
Fast formatter

Stop
   ↓
Final validation

Explicit command
   ↓
Full test suite

Or use asynchronous automation where appropriate.

The goal should be:

Automate repetitive work without turning every Claude action into a waiting period.

Claude Code Hooks on Windows

Windows deserves special attention because many Claude Code hook examples online use Bash.

Claude Code also supports PowerShell-oriented hook commands.

For example:

{
  "type": "command",
  "command": "powershell.exe",
  "args": [
    "-NoProfile",
    "-ExecutionPolicy",
    "Bypass",
    "-File",
    "${CLAUDE_PROJECT_DIR}/.claude/hooks/check.ps1"
  ]
}

A PowerShell script can read hook input from standard input:

$inputData = [Console]::In.ReadToEnd() | ConvertFrom-Json

$command = $inputData.tool_input.command

This is often cleaner on Windows than trying to force a Bash script into a PowerShell environment.

For cross-platform projects, consider placing the logic in a language such as Node.js or Python and keeping the hook configuration thin.

Common Claude Code Hook Problems

Hook does not run

Symptom

Nothing happens.

Possible causes

  • Wrong settings location
  • Invalid JSON
  • Incorrect event
  • Matcher does not match
  • Hook not loaded
  • Command unavailable
  • Script error

Fix

First run:

/hooks

If the hook does not appear there, investigate configuration before investigating the script.

Invalid JSON

Symptom

The configuration appears to have no effect.

Fix

Check:

  • commas
  • quotes
  • brackets
  • braces
  • escaping

Then verify the hook appears in /hooks.

Matcher is wrong

Symptom

The hook exists but does not trigger.

Fix

Start broad:

"matcher": ""

or an appropriate broad matcher for the event.

Once you confirm the hook fires, narrow it.

if condition is wrong

Symptom

The event matches but the actual handler never executes.

Fix

Temporarily remove the if condition.

If the hook now runs, the filtering rule needs correction.

Command not found

Test the command outside Claude Code first.

For example:

prettier --version

or:

prettier --version

If the command fails manually, fix that environment problem first.

Hook blocks legitimate work

A badly written security hook can be more annoying than the problem it was designed to solve.

Start with:

Log
 ↓
Observe
 ↓
Test
 ↓
Then block

Do not begin with an aggressive blocking policy.

Debugging Claude Code Hooks

One practical recommendation from the community sources you provided is to use Claude Code's debugging facilities while developing a hook.

For example:

claude --debug

can help expose hook-related diagnostics.

The official documentation also provides hook debugging guidance.

A useful debugging process is:

1. Check /hooks
        ↓
2. Confirm event
        ↓
3. Confirm matcher
        ↓
4. Test command manually
        ↓
5. Run with debug logging
        ↓
6. Inspect stdin JSON
        ↓
7. Inspect exit code
        ↓
8. Inspect structured output

This is much faster than randomly changing JSON until something works.

Testing a Hook Without Starting a Full Interactive Workflow

A useful advanced practice is to separate the hook's logic from Claude Code itself.

Suppose your hook script expects:

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

You can save a representative fixture and pipe it into your script.

Conceptually:

cat fixture.json | ./check-command.sh

This lets you test:

Input
 ↓
Script
 ↓
Exit code
 ↓
JSON output

without repeatedly performing the real Claude Code action.

This is especially valuable for security hooks.

When NOT to Use a Hook

Hooks are not automatically the right solution.

Don't use a hook when:

The task is genuinely manual

If you only need something once, a normal command may be simpler.

The action requires nuanced judgment

A deterministic shell rule may be the wrong tool when the decision requires understanding the entire codebase.

A prompt or agent-based workflow may be more appropriate where supported.

The command is extremely expensive

Running a ten-minute process after every small edit will quickly become frustrating.

The rule belongs in documentation

If you simply need to explain a coding convention, use CLAUDE.md.

The workflow is explicitly user-triggered

A slash command may be a better fit when the developer should decide exactly when it runs.

Best Claude Code Hooks for Beginners

Rather than calling one hook "the best," categorize them by complexity.

Good first hooks

Start with:

  • simple notifications
  • lightweight formatting
  • basic logging
  • session context
  • simple validation

Useful project hooks

Next consider:

  • protected file checks
  • security validation
  • targeted testing
  • configuration auditing
  • context injection

Advanced hooks

After understanding the basics:

  • asynchronous hooks
  • prompt-based hooks
  • agent-based hooks
  • HTTP hooks
  • MCP tool hooks
  • model-switch hooks
  • subagent automation
  • worktree automation

Avoid initially

Don't start with:

  • dozens of hooks
  • broad matchers everywhere
  • expensive commands after every tool call
  • destructive automation
  • third-party hooks you have not inspected

Claude Code Hooks Best Practices

Keep hooks focused

One hook should ideally have one clear responsibility.

Use narrow matchers

The narrower the trigger, the fewer unnecessary executions you get.

Use if for additional filtering

When a tool event is broad but only certain commands matter, an if condition can reduce unnecessary handler execution.

Put complex logic in scripts

Instead of:

settings.json
= huge shell command

prefer:

settings.json
     ↓
small hook declaration
     ↓
project script

Return structured output when you need decisions

If Claude Code needs to understand a hook decision, use the documented JSON output structure rather than relying only on human-readable text.

Test before blocking

First observe.

Then validate.

Then enforce.

Think about operating systems

A Bash hook may work perfectly on Linux and fail on Windows.

Keep performance in mind

A hook that executes every few seconds needs to be lightweight.

Document project hooks

If your project commits:

.claude/settings.json

tell contributors what the hooks do and why they exist.

Review third-party automation

Treat hooks as executable code, not harmless configuration.

Claude Code Hooks vs Git Hooks

These are not the same thing.

Git hook

Runs because of a Git lifecycle event.

Examples:

pre-commit
pre-push
post-merge

Claude Code hook

Runs because of a Claude Code lifecycle event.

Examples:

PreToolUse
PostToolUse
SessionStart
Stop
Notification

You can use both.

For example:

Claude edits code
      ↓
Claude Code hook
      ↓
Run formatter
      ↓
Git commit
      ↓
Git pre-commit hook
      ↓
Run final checks

They operate at different layers.

Claude Code Hooks FAQ

What are Claude Code Hooks?

Claude Code Hooks are event-driven automations that execute at defined points in Claude Code's lifecycle.

Where are Claude Code Hooks configured?

Common locations include user settings, project settings, and local project settings such as:

~/.claude/settings.json
.claude/settings.json
.claude/settings.local.json

Plugins and session-level hooks can also contribute hooks.

What is PreToolUse?

PreToolUse fires before a tool executes. It is useful for validation, security checks, and supported decision control.

What is PostToolUse?

PostToolUse fires after a tool succeeds. It is useful for formatting, logging, and post-operation validation.

Can Claude Code Hooks block commands?

Some events support blocking. PreToolUse is one of the main examples.

The exact behavior depends on the event.

Can Hooks modify tool input?

Supported PreToolUse workflows can return structured output that modifies supported tool input through the documented updatedInput mechanism.

Can Hooks run shell commands?

Yes. Command hooks can execute shell commands or project scripts.

Can Hooks use MCP?

Yes. Current Claude Code supports MCP tool hooks.

One important lifecycle detail: MCP tool hooks require the session's MCP servers to be available. For example, MCP hooks on the initial SessionStart can be skipped because the MCP client context is not yet available.

Can Hooks run on Windows?

Yes. Windows-specific PowerShell commands and scripts are supported.

What does /hooks do?

/hooks opens a read-only browser showing configured hooks, matchers, handler types, and their sources.

Why isn't my Hook running?

Check:

  1. settings location
  2. JSON validity
  3. /hooks
  4. event name
  5. matcher
  6. if condition
  7. command availability
  8. script permissions
  9. debug output

Are Claude Code Hooks safe?

They can be useful security controls, but they are executable automation and should be treated accordingly.

Review third-party hooks, avoid secrets, test blocking logic carefully, and don't run scripts you do not understand.

Final Takeaway

If you are new to Claude Code Hooks, start small.

A good first workflow is:

Claude edits a file
       ↓
PostToolUse
       ↓
Run formatter

After that, try a notification hook or a lightweight validation hook.

Once you understand matchers, stdin JSON, exit codes, and structured output, you can move into more advanced automation such as:

PreToolUse
   ↓
Security validation
   ↓
Allow / deny

SessionStart
   ↓
Project state
   ↓
Additional context

PostCompact
   ↓
Restore useful context

ConfigChange
   ↓
Audit

SubagentStop
   ↓
Validate subagent work

The goal is not to make every Claude Code action automated.

The goal is to identify repetitive, deterministic, or safety-sensitive tasks and move them from "Claude should remember to do this" to "the workflow automatically does this."

That is where Claude Code Hooks become genuinely useful.