back to docs
ai

Hooks

Lifecycle hooks for automating actions before and after Claude Code operations

Hooks let Claude Code run shell commands at specific moments: before a tool executes, after it completes, or when Claude finishes responding. They live in ~/.claude/settings.json.

1. Structure

{
  "hooks": {
    "PostToolUse": [...],
    "PreToolUse": [...]
  }
}

Each entry has a matcher (a regex against the tool name) and an array of hooks to run.

2. The one I use: auto-format after every edit

This is the most useful hook I have. Whenever Claude edits or writes a file, Prettier runs automatically:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path // empty' | tr -d '\\r' | xargs -r bunx prettier --write --ignore-unknown"
          }
        ]
      }
    ]
  }
}

How it works:

  • matcher: "Edit|Write" triggers after any Edit or Write tool call
  • jq -r '.tool_input.file_path // empty' extracts the file path from the JSON Claude Code pipes into stdin
  • tr -d '\r' is needed on Windows because jq under MINGW64 outputs CRLF, and the stray \r gets appended to the path, causing Prettier to fail with “file not found”
  • xargs -r skips if the path is empty, then passes it to Prettier
Tool Input JSON

Claude Code pipes a JSON object into each hook’s stdin containing tool_name, tool_input, and tool_response. Use jq to extract what you need.

3. Hook types

EventWhen it fires
PreToolUseBefore a tool executes, can block it
PostToolUseAfter a tool completes
StopWhen Claude finishes a response
SubagentStopWhen a subagent finishes

4. PreToolUse: blocking dangerous commands

PreToolUse can prevent a tool from running entirely. Here is an example that blocks rm -rf:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "jq -e '.tool_input.command | test(\"rm -rf\")' && echo '{\"action\":\"block\",\"reason\":\"rm -rf not allowed\"}' || true"
          }
        ]
      }
    ]
  }
}

Outputting JSON with action: "block" stops the tool before it runs.

5. A few things worth knowing

  • matcher is a regex, so "Edit|Write|MultiEdit" matches all three
  • Multiple hooks in an array run sequentially
  • PostToolUse hooks are fire-and-forget, their exit code does not affect Claude
  • Always use jq to parse stdin, never assume the format
Command Palette
Search for a command to run...