Claude Code commands

How to Configure Hooks in Claude Code

4 min read

Hooks run a shell command automatically on Claude Code events — format after every edit, block edits to a protected path, log what ran. They live in your settings files and run as ordinary code, so they do what a prompt can only ask for.

Read this first if you’re porting an old hook: $CLAUDE_FILE_PATHS no longer exists. Hooks receive a JSON object on stdin, and the file path lives at .tool_input.file_path. The environment variable that does exist is ${CLAUDE_PROJECT_DIR}.

1. Pick an event

PreToolUse (before a tool runs, and able to block it), PostToolUse (after — format, lint, test), UserPromptSubmit, SessionStart, SessionEnd and Stop cover almost everything. The matcher filters by tool name.

2. Add it to settings.json

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh" }
        ]
      }
    ]
  }
}

What it does: runs one script after every Edit or Write. Put it in .claude/settings.json for the project or ~/.claude/settings.json for yourself — see which settings file wins.

3. Read the path from stdin

#!/bin/bash
# .claude/hooks/format.sh
file=$(jq -r '.tool_input.file_path')
[ -f "$file" ] && npx prettier --write "$file"

What it does: parses the hook’s stdin JSON with jq and formats the one file that changed. This is the modern replacement for the old $CLAUDE_FILE_PATHS one-liner.

4. Confirm it loaded

/hooks

What it does: opens a read-only browser of every configured hook, its matcher, its handler type and which settings file it came from. No restart is needed — direct edits to hooks in settings files are picked up by the file watcher.

5. Block something

A PreToolUse hook that exits with code 2 blocks the action and shows stderr as the reason. Any other non-zero code is a non-blocking error and the action proceeds.

If it fails

If /hooks doesn’t list it, the JSON is malformed — claude doctor names what it dropped. If it lists but never fires, the matcher doesn’t match the tool name. Hooks run without a controlling terminal, so anything interactive hangs until it times out.

Start with one harmless formatting hook before you write a blocking one: a bad matcher gets in your way all day. Hooks are real guardrails rather than instructions, which is what separates them from permissions prompts. They run unattended in headless CI too, so read one carefully.

Source: Claude Code hooks reference.

Open the full interactive version (with copy buttons) ↗

← All guides