Code of the Day
AdvancedAdvanced Agent Patterns

Hooks and automation

Use Claude Code hooks to run linters, tests, and guards automatically on agent events — making the agent's environment opinionated and safe.

Using AIAdvanced12 min read
By the end of this lesson you will be able to:
  • Explain what Claude Code hooks are and the four event points where they can fire
  • Write a PostToolUse hook that runs pytest after any Python file is written
  • Describe the pattern of using hooks to enforce standards the agent must meet
  • Identify appropriate and inappropriate uses of hooks in an agentic workflow

The previous lesson established phased orchestration: you review the output of each phase before the next one starts. That is a strong pattern, but it relies entirely on you to be the checkpoint. Every review costs your time and attention.

Hooks give you a different lever: automate the checkpoints that are always the same.

If you always want the tests to run after the agent writes a Python file, you should not have to remember to run them. A hook can run them for you — and feed the result back to the agent automatically, so it can fix failures without waiting for you to copy-paste them.

What hooks are

Claude Code hooks are shell commands that run automatically when specific events occur during an agent session. You configure them in your Claude Code settings, and they execute in the background as the agent works.

The four event points:

Hook typeWhen it fires
PreToolUseBefore the agent calls a tool (e.g., before writing a file)
PostToolUseAfter the agent calls a tool (e.g., after writing a file)
NotificationWhen the agent sends a notification
StopWhen the agent session ends

The most useful for coding workflows is PostToolUse — it fires after the agent has written or modified a file, which is exactly when you want to run your checks.

Configuring hooks

Hooks are defined in the Claude Code settings file, which lives at:

~/.claude/settings.json

Here is a minimal configuration that runs pytest after any Python file is written:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "cd /path/to/your/project && pytest tests/ -v 2>&1 | tail -20"
          }
        ]
      }
    ]
  }
}

The matcher field specifies which tool call triggers this hook. Write matches any file-write tool call. You can also match on Edit, Bash, or use .* to match all tool calls.

The command's stdout is fed back into the agent's context. The agent sees the pytest output as if you had run it yourself and pasted the result — and it can act on failures without waiting for you.

You can be more selective with the matcher. To only trigger on Python files: use a matcher that checks the tool input, or filter in the shell command with a conditional: if echo "$CLAUDE_TOOL_INPUT_FILE_PATH" | grep -q "\.py$"; then pytest ...; fi. The exact syntax depends on the Claude Code version — check the docs for your version.

What you can build with hooks

Auto-run linter after edits. After every file write, run ruff check or eslint. The agent sees lint errors immediately and fixes them in the same session, rather than you discovering them during review.

{
  "type": "command",
  "command": "ruff check . --output-format=text 2>&1 | head -30"
}

Log all tool calls. Write a hook that appends every tool call to a log file. Useful for auditing what the agent did in a long session.

{
  "type": "command",
  "command": "echo \"$(date): $CLAUDE_TOOL_NAME $CLAUDE_TOOL_INPUT_FILE_PATH\" >> ~/.claude/session.log"
}

Block dangerous operations. A PreToolUse hook on Bash can inspect the command about to be run and exit with a non-zero status to block it:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if echo \"$CLAUDE_TOOL_INPUT_COMMAND\" | grep -qE 'rm -rf|DROP TABLE|git push'; then echo 'BLOCKED: dangerous operation'; exit 2; fi"
          }
        ]
      }
    ]
  }
}

A hook that exits with code 2 signals Claude Code to block the tool call entirely. An exit code of 0 allows it. Anything else is treated as a warning. Be careful with blocking hooks on Bash — if your command is too broad, it will block legitimate operations. Start with logging, then add blocking once you have validated the pattern.

Run tests after any Python file change. The most universally useful hook for Python projects:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "pytest tests/ --tb=short -q 2>&1 | tail -15"
          }
        ]
      }
    ]
  }
}

With this hook active, the agent writes a file, sees the test output, and can immediately fix failures — all within the same session, without you having to intervene.

The pattern: opinionated environments

The principle behind hooks is making the agent's environment opinionated.

Without hooks, the agent operates in a neutral environment — it does not know your standards are being enforced until you tell it they were violated. With hooks, the environment enforces standards automatically. Lint errors appear before the agent moves on. Test failures are visible immediately. Dangerous operations are blocked before they execute.

This shifts the work from "catch mistakes in review" to "prevent mistakes during execution." Review is still necessary — hooks do not catch all categories of problems. But a well-configured hook setup means the diff you review has already passed a basic automated check.

What hooks are not for

Not a substitute for diff review. Hooks catch mechanical errors — syntax, lint, test failures. They do not catch deleted error handling, hardcoded values, or scope creep. Human review still covers those.

Not a place for complex business logic. Hooks run shell commands. If your "hook" would need to be a Python script with complex logic to determine whether to block an operation, you are building a more complex safety system than hooks are designed for. Keep hooks simple.

Not always appropriate for every project. A hook that runs the full test suite after every file write makes sense for a project with fast tests (under a few seconds). For a project where the test suite takes five minutes, that hook will slow every agent session to a crawl. Tune hooks to the project.

Hooks and automation

  1. 1.
    A PostToolUse hook that runs pytest after every file write feeds the test output back to the agent. What is the primary benefit of this pattern?
  2. 2.
    Which of the following are appropriate uses of Claude Code hooks? Select all that apply.
  3. 3.
    A PreToolUse hook that exits with code 2 will block the corresponding tool call from executing.

Where to go next

Hooks automate the enforcement of your project's standards. The next lesson expands the agent's capabilities in a different direction: MCP servers give the agent access to external tools and data sources — databases, APIs, and more — without you having to build that access from scratch.

Finished reading? Mark it complete to track your progress.

On this page