# Configuring Claude Code Hooks for Specific Tools: A Complete Implementation Guide

> Implement Claude Code hooks to automate scripts for specific tools like Write, Bash, or Read. Enforce security, format code, and validate commands automatically.

- Repository: [Luong NGUYEN/claude-howto](https://github.com/luongnv89/claude-howto)
- Tags: how-to-guide
- Published: 2026-03-30

---

**Claude Code hooks enable automatic execution of custom scripts when specific tools like `Write`, `Bash`, or `Read` are invoked, allowing you to enforce security policies, format code, or validate commands before they run.**

The `luongnv89/claude-howto` repository provides a comprehensive reference implementation for Claude Code's extensible hook architecture. By configuring **matchers** that target specific tool names in `~/.claude/settings.json`, you can intercept operations at `PreToolUse` or `PostToolUse` events to inject custom validation, automation, or logging logic directly into Claude Code's execution flow.

## Understanding the Hook Architecture

Claude Code exposes **25 distinct hook events**, including `PreToolUse`, `PostToolUse`, and `UserPromptSubmit`. Each event communicates via JSON payloads delivered through **stdin**, expecting a JSON response on **stdout**. According to the source documentation in [`06-hooks/README.md`](https://github.com/luongnv89/claude-howto/blob/main/06-hooks/README.md), the system supports four hook types: `command` (shell scripts), `http` (web endpoints), `prompt` (interactive confirmation), and `agent` (sub-agent invocation).

The configuration schema follows a nested structure where events contain matchers, and matchers contain hook definitions:

```json
{
  "hooks": {
    "<EventName>": [
      {
        "matcher": "<ToolPattern>",
        "hooks": [
          {
            "type": "command",
            "command": "<script-path>",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

```

Hooks execute with the same permissions as the Claude Code process, receiving a payload containing `session_id`, `tool_name`, `tool_input`, and `transcript_path`. This payload allows hooks to return `permissionDecision` values of `"allow"`, `"deny"`, or `"ask"`, and even modify tool inputs via the `updatedInput` field before execution.

## How Tool Matching Works

The **matcher** field determines which tools trigger your hook. The evaluation supports three patterns against the incoming JSON's `"tool_name"` field:

- **Exact match**: `"Write"` fires only for the `Write` tool
- **Regular expressions**: `"Write|Edit"` fires for either tool
- **Wildcard**: `"*"` or an empty string matches all tools

Matching is case-sensitive, so `"write"` does not match `"Write"` unless you implement a case-insensitive regex pattern. This specificity allows you to bind distinct validation logic to individual tools without affecting Claude Code's broader operation.

## Execution Flow: PreToolUse vs PostToolUse

The hook system implements a strict execution pipeline:

1. Claude prepares tool parameters and generates the JSON payload
2. `PreToolUse` hooks execute sequentially
3. If any hook returns exit code **2** or `"permissionDecision":"deny"`, the tool is blocked immediately
4. The tool executes (if allowed)
5. `PostToolUse` hooks run (these cannot block execution but can trigger side effects)

This architecture makes `PreToolUse` ideal for security validation and `PostToolUse` suitable for formatting, logging, or cleanup operations.

## Practical Hook Implementations by Tool

### Blocking Dangerous Bash Commands

For the `Bash` tool, implement a **PreToolUse** hook that validates commands against dangerous patterns. The reference implementation in [`06-hooks/validate-bash.py`](https://github.com/luongnv89/claude-howto/blob/main/06-hooks/validate-bash.py) demonstrates blocking `rm -rf /` and `sudo rm` operations:

```python
#!/usr/bin/env python3
import json, sys, re

BLOCKED = [
    (r"\brm\s+-rf\s+/", "Blocking rm -rf"),
    (r"\bsudo\s+rm", "Blocking sudo rm")
]

payload = json.load(sys.stdin)
if payload.get("tool_name") != "Bash":
    sys.exit(0)

cmd = payload.get("tool_input", {}).get("command", "")
for pat, msg in BLOCKED:
    if re.search(pat, cmd):
        print(msg, file=sys.stderr)
        sys.exit(2)   # Exit code 2 blocks execution

sys.exit(0)

```

Configure this in `~/.claude/settings.json`:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\"",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

```

### Auto-Formatting Code After Writes

Use **PostToolUse** hooks to automatically format files after `Write` or `Edit` operations. The [`06-hooks/format-code.sh`](https://github.com/luongnv89/claude-howto/blob/main/06-hooks/format-code.sh) script detects file extensions and applies appropriate formatters:

```bash
#!/bin/bash
INPUT=$(cat)
TOOL=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_name',''))")
FILE=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_input',{}).get('file_path',''))")

if [[ "$TOOL" != "Write" && "$TOOL" != "Edit" ]]; then
  exit 0
fi

case "$FILE" in
  *.js|*.jsx|*.ts|*.tsx|*.json) command -v prettier &>/dev/null && prettier --write "$FILE" ;;
  *.py)               command -v black &>/dev/null && black "$FILE" ;;
  *.go)               command -v gofmt &>/dev/null && gofmt -w "$FILE" ;;
esac
exit 0

```

The corresponding configuration uses a regex matcher:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh\"",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

```

### Scanning for Hard-Coded Secrets

Implement security scanning for the `Write` tool using [`06-hooks/security-scan.sh`](https://github.com/luongnv89/claude-howto/blob/main/06-hooks/security-scan.sh) to detect potential secret leakage:

```bash
#!/bin/bash
FILE=$1
if [[ -z "$FILE" ]]; then exit 0; fi

echo "🔒 Scanning $FILE for secrets..."
if grep -qE "(password|api[_-]?key|secret).*=" "$FILE"; then
  echo "⚠️  Potential secret detected in $FILE"
fi
exit 0

```

### Auto-Adapting Permissions Based on Usage

The advanced [`06-hooks/auto-adapt-mode.py`](https://github.com/luongnv89/claude-howto/blob/main/06-hooks/auto-adapt-mode.py) script demonstrates learning safe permission patterns from successful tool executions. It runs on `PostToolUse` with a wildcard matcher to automatically expand `~/.claude/settings.json` permissions for frequently used Bash commands:

```python
#!/usr/bin/env python3
import json, sys, os, re

SETTINGS = os.path.expanduser("~/.claude/settings.json")
BASELINE = ["Read(*)","Write(*)","Bash(ls:*)","Bash(cat:*)"]

def load():
    return json.load(open(SETTINGS)) if os.path.exists(SETTINGS) else {}

def save(cfg):
    with open(SETTINGS,"w") as f:
        json.dump(cfg,f,indent=2)
        f.write("\n")

payload = json.load(sys.stdin)
tool = payload.get("tool_name")
inp = payload.get("tool_input", {})

if tool == "Bash":
    cmd = inp.get("command","")
    parts = cmd.split()
    if parts:
        rule = f"Bash({parts[0]}:*)"
        cfg = load()
        allow = cfg.setdefault("permissions",{}).setdefault("allow",[])
        if rule not in allow and not any(re.search(p, cmd) for p in ["rm -rf","force"]):
            allow.append(rule)
            save(cfg)

sys.exit(0)

```

Configuration for global monitoring:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/auto-adapt-mode.py\"",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

```

## Configuration File Locations

Claude Code searches for hook configurations in three locations, in order of precedence:

1. `~/.claude/settings.json` (global user settings)
2. [`.claude/settings.json`](https://github.com/luongnv89/claude-howto/blob/main/.claude/settings.json) (project-local settings)
3. Plugin directories containing [`hooks/hooks.json`](https://github.com/luongnv89/claude-howto/blob/main/hooks/hooks.json)

The `CLAUDE_PROJECT_DIR` environment variable is automatically set by Claude Code, allowing scripts to reference project-relative paths reliably across different environments.

## Security Considerations and Best Practices

When configuring Claude Code hooks for specific tools, adhere to these security principles from the [`06-hooks/README.md`](https://github.com/luongnv89/claude-howto/blob/main/06-hooks/README.md) documentation:

- **Validate all inputs**: Never trust user-provided data in `tool_input` fields; sanitize before processing
- **Use explicit environment variables**: When configuring `http` type hooks, explicitly declare `allowedEnvVars` to prevent secret leakage
- **Blocking semantics**: Return exit code **2** or `"permissionDecision":"deny"` in `PreToolUse` hooks to prevent dangerous operations from executing
- **Timeout constraints**: Always set reasonable `timeout` values (in seconds) to prevent hooks from hanging indefinitely
- **Permission parity**: Remember hooks execute with Claude Code's permissions, so avoid elevated privileges in hook scripts

## Summary

- **Tool-specific hooks** use matchers targeting the `tool_name` field in JSON payloads delivered via stdin
- **PreToolUse** hooks can block operations by returning exit code 2 or `"permissionDecision":"deny"`, while **PostToolUse** hooks cannot block but can trigger side effects
- Configuration resides in `~/.claude/settings.json` or [`.claude/settings.json`](https://github.com/luongnv89/claude-howto/blob/main/.claude/settings.json) using the nested hooks schema documented in [`06-hooks/README.md`](https://github.com/luongnv89/claude-howto/blob/main/06-hooks/README.md)
- The `luongnv89/claude-howto` repository provides production-ready examples including [`validate-bash.py`](https://github.com/luongnv89/claude-howto/blob/main/validate-bash.py) for security, [`format-code.sh`](https://github.com/luongnv89/claude-howto/blob/main/format-code.sh) for automation, and [`auto-adapt-mode.py`](https://github.com/luongnv89/claude-howto/blob/main/auto-adapt-mode.py) for dynamic permission management
- Hooks must respond with valid JSON on stdout and respect the timeout parameter to ensure smooth Claude Code operation

## Frequently Asked Questions

### How do I target multiple specific tools with one hook configuration?

Use a regular expression in the **matcher** field. For example, `"Write|Edit"` applies the hook to both the `Write` and `Edit` tools, while `"*"` matches all tools. The matching is case-sensitive and evaluates against the `tool_name` field in the incoming JSON payload.

### Can I modify tool input parameters before Claude Code executes them?

Yes. In a `PreToolUse` hook, return a JSON object containing an `updatedInput` field with the modified parameters. Claude Code will use these updated values instead of the original inputs. This allows you to sanitize paths, add flags, or transform arguments before execution.

### What is the difference between PreToolUse and PostToolUse hooks?

**PreToolUse** hooks execute before the tool runs and can block execution by returning exit code 2 or `"permissionDecision":"deny"`. **PostToolUse** hooks execute after the tool completes and cannot block, but they can perform actions like logging, formatting, or updating configuration files based on the operation's results.

### Where should I store hook scripts for team-wide consistency?

Store hook scripts in your project's `.claude/hooks/` directory and reference them using the `$CLAUDE_PROJECT_DIR` environment variable in your configuration. This ensures the scripts are version-controlled and accessible to all team members, while individual developers can still maintain personal hooks in `~/.claude/settings.json`.