Claude Code hooks

Hooks run commands, handlers, or prompts around Claude Code lifecycle events. They help convert team rules into repeatable checks instead of relying on memory.

Last verified
Applies to
Claude Code 2.1.x. CLI spellings were checked on local version 2.1.198; current official docs may describe newer 2.1.x behavior.
Verification method
Official documentation, CLI reference, and changelog reviewed on 2026-07-19. Hook schema and exit semantics were checked against official references. The standalone blocker script can be tested locally; this audit did not install a hook into user settings.
Official sources
Hooks guideHooks referenceChangelog

Reproducible practice

Block a dangerous database command with a project hook

Build and test a deterministic PreToolUse guard before relying on it in a Claude Code session.

Prerequisites

  • A trusted test repository, jq, and a POSIX shell.
  • A reviewed .claude/hooks directory committed only if the team accepts the policy.
  • No production database credentials in the test environment.

Steps

  1. Create the blocker script

    Save this exact script as .claude/hooks/block-drop-table.sh. It reads the Bash command from hook JSON, blocks a conservative pattern, and explains the denial on stderr.

    Step 1
    #!/bin/sh
    COMMAND=$(jq -r '.tool_input.command // ""')
    if echo "$COMMAND" | grep -Eiq 'drop[[:space:]]+table'; then
      echo 'Blocked: DROP TABLE is not allowed' >&2
      exit 2
    fi
    exit 0
  2. Test the script outside Claude Code

    A dangerous fixture must exit 2; a harmless fixture must exit 0.

    Step 2
    chmod +x .claude/hooks/block-drop-table.sh
    printf '%s' '{"tool_input":{"command":"drop table demo"}}' | .claude/hooks/block-drop-table.sh; test $? -eq 2
    printf '%s' '{"tool_input":{"command":"git status"}}' | .claude/hooks/block-drop-table.sh
  3. Register a PreToolUse Bash matcher

    Add a command hook in project settings only after JSON review. The hook command should reference $CLAUDE_PROJECT_DIR/.claude/hooks/block-drop-table.sh.

  4. Exercise both paths in a disposable session

    Request a harmless git status and a non-executing command string containing DROP TABLE; confirm the latter is denied before tool execution.

Expected result

The standalone fixtures return 0 and 2 as designed, and the session shows the denial reason without executing the matched Bash call.

Validation

  • Parse the settings file with jq -e . .claude/settings.json.
  • Test allow and deny fixtures directly after every script change.
  • Use debug category hooks only when the hook does not fire as expected.

Failure handling

Hook never fires

Check event name, Bash matcher, executable bit, settings scope, and JSON parsing.

Everything is blocked

Print a redacted fixture to the script and narrow the pattern; keep fail-safe behavior explicit.

Cleanup or rollback

  • Remove the hook entry first, then delete the script after confirming settings still parse.
  • Keep a normal permission deny rule for high-risk actions; hooks should not be the only security boundary.

Boundaries and when not to use it

  • Hook commands execute with the user's privileges. Treat reviewed hook code like production automation.
  • Exit 2 blocks PreToolUse, but some other hook events cannot be blocked; event semantics are not interchangeable.

Lifecycle automation

Hooks can run at points such as session start, tool use, file changes, and stop events. This is useful for formatting after edits, checking command safety, or recording progress.

Quality gates

A hook can run lint, formatting, static checks, or custom validations after relevant actions. Keep hooks fast enough to support iteration and reserve heavier checks for deliberate verification steps.

Team conventions

Hooks can enforce repository-specific rules such as generated file restrictions, required review notes, or notification conventions. They should explain failures clearly so the agent and developer know what to fix.

Failure handling

A useful hook fails with a focused message and a recovery path. Avoid broad scripts that produce noisy output and make it hard to identify the actual blocked action.

Example hooks.json entry
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [{ "type": "command", "command": "pnpm lint --fix" }]
      }
    ]
  }
}

Common hook events

Hooks can run around session lifecycle, tool use, and stop events. Match the event to the check you want to enforce.

SessionStart

Load environment checks, notify the team channel, or print repository-specific reminders.

Read guide

Stop

Run final lint, test, or summary steps before a session ends.

Read guide

Hook checklist

Related topics