Claude Code Hooks (2026): Complete Setup Guide, Examples & Troubleshooting

Claude Code Hooks (2026): Complete Setup Guide, Examples & Troubleshooting

Quick Answer

Claude Code can read files, modify code, run terminal commands, and interact with development tools. But what if you need certain actions to happen automatically whenever Claude edits a file, requests permission, or reaches a particular stage of its workflow?

That's where Claude Code hooks come in.

Hooks allow developers to automate repeatable actions such as formatting source files, checking protected paths, displaying notifications, and running validation scripts. Unlike ordinary instructions, hooks are tied to specific Claude Code lifecycle events.

This guide explains how to configure Claude Code hooks in 2026, where to save their settings, how different events work, and how to create practical automations with JSON and Python examples.

What Are Claude Code Hooks and How Do They Work?

Claude Code hooks are configurable handlers that run when specified lifecycle events occur, such as before a tool call, after a file edit, or when a session starts. Developers define them in Claude Code settings to trigger commands, HTTP requests, MCP tools, or model-assisted checks for automation, validation, notifications, and workflow control.

Table Of Content

Why Developers Use Claude Code Hooks

AI coding assistants can follow project instructions, but some tasks should not depend on whether the assistant remembers to perform them.

For example, a developer may want code formatting to run after every supported file edit, a warning to appear when Claude attempts to change a protected configuration file, or a desktop notification when permission is required.

Claude Code hooks make these workflows event-driven.

A hook runs because a defined event occurs—not simply because an instruction asks Claude to remember something.

Common Claude Code hook use cases

Use case

Relevant event

Example action

Format code after editing

PostToolUse

Run Prettier after an Edit or Write operation

Check a protected file before modifying it

PreToolUse

Inspect the target path and reject certain edits

Notify the developer

Notification

Display a desktop notification

Load project context

SessionStart

Add development conventions to the session

Check a submitted prompt

UserPromptSubmit

Validate or augment the submitted request

Record failed tool operations

PostToolUseFailure

Send a failure record to a logging system

Respond when Claude finishes

Stop

Trigger a task-end notification or validation

Save information before compaction

PreCompact

Preserve relevant context through a supported process

Important: A PostToolUse hook executes after the relevant tool has already run. It can report or correct some results, but it cannot retroactively prevent the original action.

To block a supported tool call before execution, use PreToolUse.

Claude Code Hooks vs. CLAUDE.md vs. Skills vs. MCP

Developers often confuse Claude Code hooks with other extension features.

These tools solve different problems and can work together.

Feature

What it does

When to use it

Hooks

Respond to lifecycle events through configured handlers

Enforce checks, automate formatting, trigger notifications

CLAUDE.md

Provides persistent instructions and project context

Coding conventions, architecture notes, repository guidance

Skills

Supplies reusable, task-specific instructions and workflows

Code reviews, documentation, testing playbooks

MCP servers

Connects Claude to external tools and data sources

Repositories, databases, APIs, developer services

Subagents

Perform delegated work with separate contexts

Focused research, code analysis, specialized tasks

Example: Using all four together

Consider a SaaS team building a Next.js marketing website.

Its CLAUDE.md file explains the coding conventions.

A development skill describes how to review a pull request.

An MCP server gives Claude access to an approved external service.

A PostToolUse hook checks or formats a file after a supported edit.

These components are complementary. A hook does not replace a skill, and adding an MCP server does not automatically create a hook.

Practical rule: Use a hook when the same action should be triggered by a specific event. Use instructions or skills when the task requires flexible reasoning.

How Claude Code Hooks Work: Event, Matcher, Handler

A standard Claude Code hook configuration has three main parts:

  1. Event: When the automation should run.

  2. Matcher: Which events or tools should activate it.

  3. Handler: What command, endpoint, tool, or model-assisted check should execute.

Here is a basic configuration shape:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'A file operation completed'"
          }
        ]

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'A file operation completed'"
          }
        ]

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'A file operation completed'"
          }
        ]

In this example:

  • PostToolUse identifies the lifecycle event.

  • Edit|Write matches either the Edit or Write tool.

  • type: "command" selects a command handler.

  • command defines the command that runs after a matching successful tool call.

The event occurs first. Claude Code evaluates the matcher, then executes the matching handler.

The command output from this simple example is primarily useful for debugging. It is not intended to change Claude's response or modify a file.

Step 1: Choose the Correct Claude Code Hooks Settings File

Claude Code reads hook configurations from several settings locations.

Choosing the right file determines whether a hook applies to every project or only one repository.

Claude Code hook configuration locations

Settings location

Scope

Recommended use

~/.claude/settings.json

Personal, across local projects

Personal notifications and preferences

.claude/settings.json

Shared project configuration

Team formatting and repository checks

.claude/settings.local.json

Personal configuration for one project

Testing a hook before sharing it

Managed settings

Organization-controlled

Administrative policies

Plugin hook configuration

When the plugin is active

Reusable packaged automation

For a developer working on multiple projects, personal notifications usually belong in the home-directory settings file.

For repository-specific validation, the shared project settings file is a more appropriate choice when the team agrees to the behavior.

Example project structure




The settings.json file contains hook registrations.

The hooks directory contains any supporting scripts.

What if settings.json does not exist?

Create the .claude directory and the appropriate settings file.

For macOS or Linux, you can create the project directory from the project root:

Then create .claude/settings.json with a text editor.

If the file already exists, merge the new event configuration into its existing hooks object. Do not replace unrelated settings, permissions, or hooks.

Use valid JSON with no trailing commas or comments.

Step 2: Create Your First Claude Code Hook

A notification is a useful beginner example because it does not need to modify your source files.

The example below uses the Notification lifecycle event.

Example A: Desktop notification on macOS

Add this configuration to ~/.claude/settings.json:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]

The empty matcher allows the handler to run for all notification types.

What happens next?

When Claude Code emits a notification event, the configured command asks macOS to display a desktop notification.

This does not mean a notification appears after every file edit. It runs for notification events, which can include permission requests or idle prompts.

How to verify the hook

  1. Save the settings file.

  2. Open Claude Code in your terminal.

  3. Type /hooks.

  4. Locate the Notification event and confirm the handler appears.

  5. Trigger an operation that requires your attention.

  6. Check whether the desktop notification appears.

macOS may require notification permissions for the application responsible for displaying the message.

Linux and Windows alternatives

On supported Linux desktop environments, a notification command can use:

This requires an appropriate notification daemon and notify-send to be available.

On Windows, a hook can use PowerShell. Claude Code supports selecting PowerShell explicitly through the handler's shell field.

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "shell": "powershell",
            "command": "Write-Host 'Claude Code notification event'"
          }
        ]

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "shell": "powershell",
            "command": "Write-Host 'Claude Code notification event'"
          }
        ]

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "shell": "powershell",
            "command": "Write-Host 'Claude Code notification event'"
          }
        ]

This Windows example writes a message through PowerShell rather than displaying a desktop toast. A native popup requires an appropriate notification implementation.

Testing note: Notification behavior depends on operating-system permissions and whether the development session has access to a graphical desktop.

Step 3: Understand the Most Useful Claude Code Hook Events

Claude Code supports numerous lifecycle events, and the official event list can expand as the product changes.

Rather than memorizing a fixed event count, understand the events relevant to your workflow.

Common lifecycle event reference

Event

When it runs

Can it block the original operation?

SessionStart

When a session starts or resumes

No

UserPromptSubmit

When the user submits a prompt

Yes

PreToolUse

Before a supported tool call runs

Yes

PermissionRequest

When Claude Code needs a permission decision

Can allow or deny through its decision schema

PostToolUse

After a tool succeeds

Cannot undo the completed action

PostToolUseFailure

After a tool fails

No

Notification

When Claude Code emits a notification

No

SubagentStart

When a subagent starts

No

SubagentStop

When a subagent finishes

Can prevent the subagent from stopping

Stop

When Claude finishes responding

Can request continued work

PreCompact

Before conversation compaction

Yes

SessionEnd

When the session ends

No

This is a selected reference, not a complete list of every supported event.

PreToolUse vs. PostToolUse

The difference matters when implementing safeguards.

PreToolUse example: Claude is about to edit a protected configuration file. A hook inspects the requested operation and rejects it before the Edit or Write tool executes.

PostToolUse example: Claude finishes editing a JavaScript file. A hook runs a local formatter on the resulting file.

Use a pre-execution check for preventive controls and a post-execution hook for follow-up work.

What about Stop hooks?

A Stop hook runs when Claude finishes a response, not necessarily when an entire multi-step software project is fully complete.

Be careful when blocking a stop event. An incorrect condition can cause unnecessary continuation attempts.

For beginners, Notification, PostToolUse, and narrowly scoped PreToolUse hooks are easier starting points.

Step 4: Configure Matchers Correctly

The matcher controls which events trigger a handler.

For tool-related events, common matcher values include:

Matcher

Intended behavior


Bash

Match the Bash tool


Edit

Match the Edit tool


Write

Match the Write tool


`Edit

Write`

Match either Edit or Write

*

Match every supported value for the event


mcp__.*

Regular-expression match for MCP tool names


Matchers are case-sensitive.

For example, Edit|Write is not the same as edit|write.

How MCP tool names work

MCP tools generally have names following a pattern similar to:

mcp__server_name__tool_name

A matcher using mcp__.* can target MCP tools broadly.

However, broad matchers can trigger more often than intended, and some plugin-provided MCP tools use additional name prefixes.

For sensitive automation, match the specific tool or server you intend to handle.

Avoid expensive hooks on every tool call

A command that performs network requests or runs a large test suite after every tool operation can noticeably slow development.

Use matchers to restrict unnecessary execution.

Where available and appropriate, additional if conditions can filter commands or file operations more narrowly.

Step 5: Understand the JSON Data Passed to Hooks

Command hooks receive event information as JSON through standard input (stdin).

The fields available depend on the event.

For a file-editing tool, a simplified input might resemble:




This is a simplified illustrative payload, not a captured live session. Real tool inputs may contain additional fields.

Your hook script can read the JSON, inspect the tool name or file path, and return an appropriate result.

Command hook exit codes

Exit code

General meaning

Practical implication

0

Command completed successfully

Continue normally; valid JSON output may provide additional decisions

2

Special blocking or feedback signal

A PreToolUse hook can block a tool call

Other nonzero code

Failure

Usually reports a hook error; behavior depends on event and failure configuration

Do not assume exit code 2 blocks every event.

For example, PostToolUse runs after the action, so the original edit cannot be prevented.

PermissionRequest has its own structured decision format and does not use exit code 2 as a generic permission denial.

Structured JSON decisions

A PreToolUse hook can explicitly deny a tool call through structured output:




When returning structured JSON from a command hook, exit successfully and print a valid JSON object without unrelated text on stdout.

An invalid JSON response may be ignored or treated as an error depending on the event and failure settings.

Step 6: Example — Protect Sensitive Project Files with PreToolUse

A useful safety workflow is preventing Claude's Edit and Write tools from modifying selected configuration files.

For example, you may want to restrict edits to:

  • .env and .env.* files.

  • package-lock.json.

  • Files inside .git/.

  • Paths outside the intended project directory.

Important limitation: The example below checks the target path of the Edit and Write tools. It is not a complete security sandbox and does not intercept every possible file modification through other tools or shell commands.

A. Create the protection script

Save this file at:

.claude/hooks/protect_files.py




This script reads the attempted target path, resolves it against the project directory, and blocks paths outside the project or within the selected protected patterns.

It uses Python's path handling rather than simply searching for a dangerous substring anywhere in a filename.

B. Register the PreToolUse hook

For macOS, Linux, or a compatible Unix-like shell environment, add this to .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect_files.py\"",
            "onFailure": "block"
          }
        ]

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect_files.py\"",
            "onFailure": "block"
          }
        ]

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect_files.py\"",
            "onFailure": "block"
          }
        ]

Ensure that Python 3 is available.

The onFailure setting requests blocking behavior if the handler fails unexpectedly, rather than allowing an edit simply because the guard script could not execute.

For native Windows configurations, adapt the command using the PowerShell or executable invocation supported by your Claude Code installation.

C. Test the protection logic

From the project root, you can send an illustrative event to the script:




Expected result:




Repeat using a normal source path such as src/app.ts. The script should exit successfully without printing a blocking message.

D. Important test results

The Python path-checking logic in this example was checked locally with simulated inputs for a normal source file, .env.local, .git/config, and a path outside the project.

Those checks produced the intended allow/block exit codes.

This is not an end-to-end Claude Code integration test. You should still confirm the handler's behavior in your installed Claude Code version, especially before applying it to a shared repository.

Step 7: Example — Automatically Format Code After Claude Edits a File

One popular Claude Code hook workflow is running a formatter after supported code edits.

For a JavaScript or TypeScript project, Prettier is a common choice.

The official Claude Code Hooks Guide provides a compact example using jq to extract the edited file path and run Prettier.

For projects that need stricter file-path handling, a separate script is preferable.

Prerequisites

Your project needs Node.js and a locally installed Prettier package.

Install Prettier using your project's package manager and standard dependency-management policy.

For an npm-based project, the development dependency command is:

A. Create the formatting script

Save as:

.claude/hooks/format_file.py

import json
import os
import subprocess
import sys
from pathlib import Path

payload = json.load(sys.stdin)

root = Path(os.environ["CLAUDE_PROJECT_DIR"]).resolve()
raw_path = payload.get("tool_input", {}).get("file_path")

if not isinstance(raw_path, str) or not raw_path:
    sys.exit(0)

file_path = Path(raw_path)

if not file_path.is_absolute():
    file_path = root / file_path

file_path = file_path.resolve(strict=False)

try:
    file_path.relative_to(root)
except ValueError:
    sys.exit(0)

allowed_extensions = {".js", ".jsx", ".ts", ".tsx", ".json", ".css"}

if not file_path.is_file() or file_path.suffix not in allowed_extensions:
    sys.exit(0)

prettier = root / "node_modules" / ".bin" / "prettier"

if not prettier.exists():
    print("Prettier is not installed locally.", file=sys.stderr)
    sys.exit(2)

result = subprocess.run(
    [str(prettier), "--write", str(file_path)]

import json
import os
import subprocess
import sys
from pathlib import Path

payload = json.load(sys.stdin)

root = Path(os.environ["CLAUDE_PROJECT_DIR"]).resolve()
raw_path = payload.get("tool_input", {}).get("file_path")

if not isinstance(raw_path, str) or not raw_path:
    sys.exit(0)

file_path = Path(raw_path)

if not file_path.is_absolute():
    file_path = root / file_path

file_path = file_path.resolve(strict=False)

try:
    file_path.relative_to(root)
except ValueError:
    sys.exit(0)

allowed_extensions = {".js", ".jsx", ".ts", ".tsx", ".json", ".css"}

if not file_path.is_file() or file_path.suffix not in allowed_extensions:
    sys.exit(0)

prettier = root / "node_modules" / ".bin" / "prettier"

if not prettier.exists():
    print("Prettier is not installed locally.", file=sys.stderr)
    sys.exit(2)

result = subprocess.run(
    [str(prettier), "--write", str(file_path)]

import json
import os
import subprocess
import sys
from pathlib import Path

payload = json.load(sys.stdin)

root = Path(os.environ["CLAUDE_PROJECT_DIR"]).resolve()
raw_path = payload.get("tool_input", {}).get("file_path")

if not isinstance(raw_path, str) or not raw_path:
    sys.exit(0)

file_path = Path(raw_path)

if not file_path.is_absolute():
    file_path = root / file_path

file_path = file_path.resolve(strict=False)

try:
    file_path.relative_to(root)
except ValueError:
    sys.exit(0)

allowed_extensions = {".js", ".jsx", ".ts", ".tsx", ".json", ".css"}

if not file_path.is_file() or file_path.suffix not in allowed_extensions:
    sys.exit(0)

prettier = root / "node_modules" / ".bin" / "prettier"

if not prettier.exists():
    print("Prettier is not installed locally.", file=sys.stderr)
    sys.exit(2)

result = subprocess.run(
    [str(prettier), "--write", str(file_path)]

B. Register the formatting hook

Add this event to the project's existing hooks object:

{
  "PostToolUse": [
    {
      "matcher": "Edit|Write",
      "hooks": [
        {
          "type": "command",
          "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/format_file.py\"",
          "timeout": 30
        }
      ]

{
  "PostToolUse": [
    {
      "matcher": "Edit|Write",
      "hooks": [
        {
          "type": "command",
          "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/format_file.py\"",
          "timeout": 30
        }
      ]

{
  "PostToolUse": [
    {
      "matcher": "Edit|Write",
      "hooks": [
        {
          "type": "command",
          "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/format_file.py\"",
          "timeout": 30
        }
      ]

Configuration reminder: If you already added the PreToolUse example, place PostToolUse alongside it inside the same top-level hooks object. Do not create a second JSON object or overwrite your other settings.

C. What happens during a file edit?

  1. Claude successfully edits or writes a file.

  2. Claude Code emits a matching PostToolUse event.

  3. The Python script reads the edited path.

  4. The script confirms that the file belongs to the project and has an allowed extension.

  5. The locally installed Prettier executable formats the file.

  6. The developer can inspect the formatted file and the debug log.

This script deliberately does not install Prettier automatically or execute an arbitrary command supplied through hook input.

D. Limits of this approach

A PostToolUse hook only responds to matching tool events.

If another process changes a file outside those tool calls, this hook might not execute.

Claude Code also supports FileChanged hooks for certain file-system-triggered workflows.

The Python formatter example is provided as a configuration pattern. It has not been verified against a live Claude Code session in this article; validate it in a disposable test repository before treating it as production automation.

Step 8: Example — Add Context Automatically at Session Start

Not all hooks need to execute a formatter or block a tool.

Some can add useful project context when a session begins.

For example, a team may want Claude to remember a temporary migration freeze or development rule.

Add this to your project settings:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Project reminder: use the existing design system and verify changes before deployment.'"
          }
        ]

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Project reminder: use the existing design system and verify changes before deployment.'"
          }
        ]

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Project reminder: use the existing design system and verify changes before deployment.'"
          }
        ]

For SessionStart, suitable plain-text stdout can be added to Claude's context.

This differs from a typical PostToolUse command, where ordinary stdout is not automatically displayed as new user-facing content.

When should you use this?

Useful examples include:

  • Reminding the agent about a temporary deployment freeze.

  • Supplying environment-specific development context.

  • Referencing an approved workflow for the current repository.

  • Restoring selected context after compaction using the appropriate session-start matcher.

For permanent project-wide instructions, CLAUDE.md is generally a simpler primary location.

Use a hook when the instruction must be supplied at a particular lifecycle event or generated dynamically.

Step 9: Command, HTTP, MCP Tool, Prompt and Agent Hooks

Modern Claude Code supports multiple handler types.

They are not interchangeable.

Handler type

How it works

Typical use

command

Executes a local command or script

Formatting, validation, notifications

http

Sends event information to an HTTP endpoint

Internal monitoring and external automation

mcp_tool

Invokes a configured MCP tool

Integrations with approved services

prompt

Uses a Claude model to evaluate a condition

Decisions requiring contextual judgment

agent

Uses a subagent for more involved checks

Complex verification using tools

Command hooks

Best for local actions that can be expressed as deterministic scripts.

They are appropriate for simple file checks, formatters, and environment inspection.

HTTP hooks

An HTTP handler sends hook information to an endpoint.

This can be useful for internal systems, but sending full event payloads to outside services may expose sensitive information.

Review what data is transmitted and use the documented authentication and allowlist options.

MCP tool hooks

MCP integrations can expose tools that run when particular lifecycle events occur.

Use the correct configured server and tool name. Avoid broad permissions simply to make an integration work.

Prompt-based hooks

These use a model to make decisions based on relevant context.

They are useful when a rule cannot be fully expressed with a simple deterministic check.

However, prompt-based decisions have model-related latency and are not equivalent to a guaranteed security control.

Agent-based hooks

An agent handler can investigate more complex conditions.

For example, it might inspect a group of files to assess whether a change satisfies a project-specific requirement.

Anthropic currently describes agent hooks as experimental, so production-critical implementations should be reviewed carefully.

Recommended starting point: Learn command hooks first. Add other handler types only when they solve a specific requirement that local scripts cannot handle effectively.

Step 10: How to Test and Debug Claude Code Hooks

Hook debugging should follow a repeatable process.

Start by confirming that the configuration loads, then test the script independently, and finally verify its behavior during an actual Claude Code event.

Test 1: Check the hooks browser

Inside Claude Code, enter:

Confirm that the intended event and handler are visible.

If the event does not appear, check the settings file location, JSON syntax, and applicable workspace trust or managed-policy restrictions.

Test 2: Validate settings JSON

If you have Python installed, use:

A successful parse confirms the JSON syntax is valid.

It does not prove that the hook event names, handler fields, or shell commands are correct.

Test 3: Run the script with sample input

For a file-validation hook:




Check the exit code.

Try both allowed and blocked file paths.

Test 4: Enable debug logging

Launch a diagnostic Claude Code session using:

The debug log is written to Claude Code's session debug directory.

You can also specify a log file directly:

Inspect the log for hook execution, error messages, timeouts, and matcher behavior.

Common Claude Code hooks errors and fixes

Problem

Likely cause

What to check

Hook never runs

Wrong event or matcher

Verify case-sensitive event and tool names

/hooks shows no configuration

Wrong file location, invalid JSON or policy

Check settings scope and syntax

Script reports command not found

Missing executable or incorrect PATH

Use an installed tool and valid path

Hook works in terminal but not Claude Code

Different environment or input

Inspect debug log and event JSON

Permission hook has no effect

Wrong decision schema

Check event-specific output requirements

JSON response is ignored

Extra stdout or invalid structure

Emit a single valid JSON object

File is modified before warning

Used PostToolUse instead of PreToolUse

Move preventive validation before the tool call

Windows shell errors

Bash-only syntax or missing Git Bash

Use supported PowerShell configuration

Hook runs repeatedly

Broad matcher or recursive side effect

Narrow the matcher and examine process behavior

Session becomes slow

Expensive hook runs too frequently

Reduce event scope and command workload

Why JSON responses sometimes fail

Structured hook responses must follow the correct schema for the event.

For example, a PreToolUse permission decision belongs in hookSpecificOutput, while other events may use a different response shape.

Avoid mixing diagnostic messages with structured JSON on stdout.

Put diagnostics in a suitable log or stderr, and use a JSON encoder rather than manually concatenating unescaped values.

Step 11: Claude Code Hooks Security Best Practices

Hooks can execute commands with the permissions of the user running Claude Code.

That makes them powerful, but also potentially dangerous.

Use these safeguards

Validate untrusted input. Never assume a file path or command string received from a hook event is safe.

Restrict sensitive paths. Avoid unnecessary access to credentials, environment files, private keys and repository internals.

Avoid unsafe shell interpolation. Use correctly quoted variables or argument arrays instead of inserting raw tool input into shell commands.

Use explicit project paths. Reference your intended project directory and reject paths that escape it.

Limit external data transmission. Review HTTP endpoints and any event payloads that may contain source code or project information.

Avoid broad auto-approval rules. A hook that approves every permission prompt can create much more risk than a narrowly scoped validation handler.

Review repository hooks before running unfamiliar projects. A shared .claude/settings.json file can contain executable hook registrations.

Test before sharing. Use a temporary or local project configuration until the behavior has been validated.

Do hooks replace a security sandbox?

No.

A file-protection hook that matches only Edit and Write calls does not automatically protect against all edits made through Bash commands, external processes, or integrations.

Critical access control belongs at the operating-system, repository, CI, or organizational policy level as appropriate.

A hook should complement these controls, not be presented as a complete security boundary.

Step 12: A Practical Hook Setup for a Web Development Team

Consider a small team building a Next.js SaaS website.

The team uses Claude Code to update React components, maintain content sections, and work on integrations.

A useful initial hook setup could include:

Workflow

Hook event

Intended result

Protect selected configuration files

PreToolUse

Reject targeted Edit and Write operations

Format JavaScript and TypeScript

PostToolUse

Apply Prettier after supported edits

Show permission notifications

Notification

Alert a developer when attention is needed

Add temporary project context

SessionStart

Remind Claude about current release requirements

Record diagnostic failures

PostToolUseFailure

Capture relevant tool errors for review

Recommended implementation order

Start with notifications because they are low-risk.

Then introduce a post-edit formatter in a test project.

After the formatter works, implement file-protection checks using a narrowly defined policy.

Only add model-assisted or external-service hooks when the business requirement justifies their complexity.

How to measure whether hooks help

Do not measure success by the number of hooks installed.

Instead, evaluate practical outcomes:

  • Fewer repetitive formatting corrections.

  • More consistent source-code conventions.

  • Reduced accidental edits to restricted files.

  • Faster awareness of permission prompts.

  • Clearer debugging records.

  • Less manual effort for repeatable tasks.

A team with three well-designed hooks may have a better workflow than a team with twenty overlapping automations.

For broader tools that support developer productivity, see our Best AI Coding Tools.

Claude Code Hooks Best Practices Checklist

Before moving a hook from a test environment into your regular workflow, confirm:

  • The hook solves a repeatable, clearly defined problem.

  • The event is appropriate for the action.

  • Matchers are as narrow as practical.

  • The settings file is at the correct scope.

  • Existing settings have been preserved.

  • The command or script is available in the intended environment.

  • Hook input is parsed safely.

  • File paths are validated when relevant.

  • Sensitive data isn't transmitted unnecessarily.

  • Return codes and structured responses use the correct event contract.

  • The hook has been tested with successful and failing examples.

  • The result has been verified during an actual Claude Code session.

  • Other developers understand the automation.

  • There is a documented way to disable or correct the hook.

  • A repository-level automation has undergone an appropriate security review.

Common Mistakes When Configuring Claude Code Hooks

Using the wrong hook event

A PostToolUse hook is suitable for formatting after an edit. It cannot prevent the edit that already occurred.

Use PreToolUse when a supported tool call must be checked before execution.

Replacing an existing settings file

Developers sometimes paste a complete example over their existing settings.json and accidentally remove other permissions or hook definitions.

Merge the relevant keys into the current configuration.

Using a wildcard for every event

Broad matchers can run commands unnecessarily, increase latency, and expose unrelated operations to an automation.

Start narrowly and expand only after testing.

Treating plain text as a structured JSON decision

Claude Code accepts event-specific decision objects. Printing an arbitrary message does not mean the action will be blocked.

Review the relevant output schema before implementing enforcement.

Ignoring operating-system differences

A Bash command written for macOS or Linux may not work in native Windows PowerShell.

Use a compatible interpreter and shell configuration.

Copying hooks from an unknown repository

Hooks may contain commands that can access or modify files.

Review the configuration and supporting scripts before trusting or executing unfamiliar projects.

Assuming hooks guarantee better AI-generated code

Hooks can improve workflow consistency and enforce certain checks, but they do not automatically establish code correctness, security, or maintainability.

Code review, testing, development standards, and appropriate permissions remain essential.

Are Claude Code Hooks Worth Using?

Claude Code hooks are most valuable when a developer has a repeated workflow that should run in response to a predictable event.

You may not need hooks when you are only experimenting with Claude Code or occasionally editing a file.

But as a project grows, carefully selected automations can help establish a more consistent development process.

Begin with a simple notification or formatting hook. Understand the event, matcher, command and output contract before implementing more restrictive controls.

For advanced workflows, evaluate HTTP, MCP-tool, prompt and agent handlers based on your actual requirements.

The goal is not to automate everything. It is to automate the repeatable work while retaining clear control over what executes, what changes, and how problems are detected.

Official Claude Code Documentation and References

These are the primary sources used for the technical guidance in this article:

The hook events, settings paths, matcher behavior, input/output rules, blocking behavior, and security guidance should be checked against the latest official reference whenever Claude Code changes.

Related reading: Before configuring automation, follow the Claude Code installation guide if the CLI is not yet ready.

For more automation options, compare the best AI automation tools and their use cases.

FAQs

What are hooks in Claude Code?

Claude Code hooks are event-driven handlers that execute when specific lifecycle events occur, such as before a tool call, after a successful edit, or when a session starts. Developers use hooks to automate formatting, validation, notifications, and other repeated workflows. Hooks can run commands, HTTP requests, MCP tools, or supported model-assisted checks.

How do I create a Claude Code hook?

Create or edit a Claude Code settings file, such as .claude/settings.json for a shared project or ~/.claude/settings.json for personal settings. Add a hooks object containing the event, optional matcher, and handler configuration. Save the file, use /hooks to verify that Claude Code recognizes the hook, and test the intended event.

What is the difference between PreToolUse and PostToolUse hooks?

PreToolUse runs before a supported tool call executes and can allow, deny, or request confirmation according to its decision schema. PostToolUse runs after a tool succeeds and is useful for formatting, logging, and follow-up checks. A PostToolUse hook cannot undo an action that has already occurred.

Can Claude Code hooks automatically format files?

Yes. A PostToolUse hook can match file-editing tools such as Edit and Write and run an installed formatter, including Prettier, against the affected file. The handler should validate file paths, limit the extensions it processes, and use a trusted local formatter. Test the configuration before enabling it for an important project.

Why are my Claude Code hooks not working?

Common causes include invalid JSON, a settings file in the wrong location, an incorrect or case-sensitive matcher, an unavailable executable, incompatible shell syntax, or restrictions from workspace trust and managed settings. Use /hooks to confirm configuration, test scripts independently with sample JSON, and inspect Claude Code debug logs for execution errors.

Author

Shailesh Thakor
Shailesh Thakor

Webflow & Framer Developer | Web Design & SEO Enthusiast

Shailesh Thakor is a Webflow and Framer developer specializing in modern, responsive websites and user-friendly digital experiences. His interests include website performance, technical SEO, AI-powered tools, and SaaS technologies. Through 8Spark, he shares practical insights into website builders, digital tools, and emerging technologies, with a focus on helping businesses and individuals make informed technology decisions.