AI & Development

Claude Code Hooks Explained: Automate Your AI Workflow

Claude Code hooks let you run shell commands automatically in response to AI actions - before a tool call, after a response, when a session starts.

Claude Code hooks are one of the less-documented but most powerful customization features available to developers using Claude Code in production. A hook is a shell command (or script) that Claude Code executes automatically in response to specific events in its lifecycle: before a tool call, after a tool call, before a response, after a response, when a session starts, when a session ends. Hooks let you build custom behavior into Claude Code without modifying the model or the tool itself.

What hooks are and how they work

A Claude Code hook is defined in the settings file (~/.claude/settings.json for user-level hooks, or .claude/settings.json in a project for project-level hooks). Each hook specifies: the event that triggers it, a matcher that determines which events of that type activate the hook, and the command to run.

Claude Code processes hooks synchronously in most configurations. A pre-tool-call hook that returns a non-zero exit code can block the tool from running - this is the mechanism for implementing custom guardrails. A post-response hook can log the model's output to a file. A session-start hook can run environment validation or send a notification.

Hooks run with the permissions of the user who started Claude Code. They have access to environment variables and the full shell environment. They can run any command available on the system.

The four most useful hook types

PreToolCall - before any tool executes. This hook fires before Claude Code uses a tool (Read, Write, Edit, Bash, etc.). It receives information about which tool is about to run and with what parameters. Use cases: blocking dangerous file operations (prevent writes to production config files), logging all tool calls to an audit file, validating that the tool's target file exists before reading it.

PostToolCall - after a tool executes. This hook fires after a tool completes. It receives the tool name, parameters, and result. Use cases: logging tool results for debugging, running a linter after every Edit operation, sending a notification after a Bash command completes.

PreResponse - before Claude sends a response. This hook fires before Claude Code's response text is returned to the user. Use cases: filtering sensitive information from responses before display, adding a logging timestamp, running a compliance check on generated content.

PostResponse (Session end) - after the session completes. This hook fires when a Claude Code session ends. Use cases: saving session logs to a file, running a test suite after a session where code was changed, sending a summary to a Slack channel.

Building a custom guardrail with PreToolCall

The most valuable production use of hooks is implementing guardrails - rules that prevent Claude Code from taking specific actions, regardless of what it has been asked to do. A guardrail hook runs before a tool call and can block it by returning a non-zero exit code with an explanation.

Example: block all writes to files in a config/production/ directory:

// In .claude/settings.json
{
  "hooks": {
    "PreToolCall": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'if echo "$CLAUDE_TOOL_INPUT" | grep -q "config/production"; then echo "Blocked: production config files are read-only in this environment" && exit 1; fi'"
          }
        ]
      }
    ]
  }
}

This hook intercepts every Write and Edit tool call, checks if the target path contains "config/production", and blocks the operation with an explanatory message if it does. Claude Code displays the hook's error message and does not execute the tool.

Guardrail hooks are more reliable than instructions in the system prompt because they are enforced by the system, not by model compliance. An instruction to "never modify production config files" relies on the model following it. A hook that blocks writes to that path enforces it mechanically.

Building an audit log with PostToolCall

For teams using Claude Code in shared environments, an audit log of all tool calls provides visibility into what the AI agent is doing. A PostToolCall hook can append each tool call and its result to a timestamped log file:

// Append each tool call to audit.log
{
  "PostToolCall": [
    {
      "matcher": "*",
      "hooks": [
        {
          "type": "command",
          "command": "echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) TOOL:$CLAUDE_TOOL_NAME RESULT:$CLAUDE_TOOL_RESULT_STATUS" >> ~/.claude/audit.log"
        }
      ]
    }
  ]
}

This produces a timestamped record of every tool Claude Code uses, which tool it was, and whether it succeeded. Combined with the session transcript, this log provides a complete record of what Claude Code did in any session.

Session-start hooks for environment validation

A session-start hook runs once when Claude Code starts a new session. This is the right place to validate that the environment is in the expected state before the AI starts working - checking that required environment variables are set, that the development server is running if needed, or that a database is accessible.

// Validate environment on session start
{
  "SessionStart": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "bash -c 'test -f .env || (echo "Missing .env file - Claude Code requires .env for this project" && exit 1)'"
        }
      ]
    }
  ]
}

If the validation fails, the hook exits with a non-zero code and Claude Code surfaces the error to the user before the session begins. This prevents Claude Code from starting work in a broken environment and attributing environment failures to code errors.

The hook feedback loop

Claude Code displays hook output to the user in the conversation. This means hooks can provide feedback - not just blocking or allowing actions, but explaining why, suggesting alternatives, or summarizing what happened.

A PreToolCall hook that blocks a dangerous operation can output a specific message explaining what was blocked and what alternative the developer should use. A PostToolCall hook that runs a linter can output the lint results, which Claude Code then reads and can act on in the next turn - running the hook creates a feedback loop where the AI's actions trigger validation that informs its next decision.