DaleSchool

Automate Workflows with Hooks

Intermediate20min

Learning Objectives

  • Understand the main Hook event types
  • Block dangerous operations with a PreToolUse Hook
  • Set up automatic post-processing with a PostToolUse Hook

Working Code

Let's add a Hook to .claude/settings.json. This Hook blocks file modifications on the main branch:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "[ \"$(git branch --show-current)\" != \"main\" ] || { echo '{\"block\":true,\"message\":\"Cannot modify files on the main branch. Create a feature branch first.\"}' >&2; exit 2; }",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

Now ask Claude to modify a file on the main branch:

> Add a line to README.md

The Hook triggers and blocks the modification. Switch to a different branch and it works normally:

> Create a branch called feature/test with git checkout -b, then modify README.md

Try It Yourself

Now let's use a PostToolUse Hook to auto-lint after file saves:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "npx eslint --fix $CLAUDE_FILE_PATHS 2>/dev/null || true",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

Every time Claude modifies a file, ESLint automatically runs and fixes the style.

"Why?" — Enforcing Rules with Code

Even if you write "don't work on the main branch" in CLAUDE.md, Claude might ignore it. Hooks enforce rules with code. It's not a request — it's a block.

Key Hook Events

| Event | When | Purpose | | ------------------ | -------------------------- | ------------------------------------------------ | | PreToolUse | Before tool execution | Block dangerous operations, conditional approval | | PostToolUse | After tool execution | Auto-formatting, linting, logging | | Notification | When a notification fires | Custom notifications (Slack, desktop, etc.) | | Stop | When response completes | Result validation, cleanup | | UserPromptSubmit | When user submits input | Input preprocessing | | SubagentStop | When sub-agent completes | Sub-agent result post-processing | | PreCompact | Before context compression | Preserve data before compression | | SessionStart | When session starts | Environment validation, dependency checks | | SessionEnd | When session ends | Cleanup, logging |

The most commonly used are PreToolUse, PostToolUse, and Notification.

Hook Configuration Structure

{
  "hooks": {
    "EventName": [
      {
        "matcher": "tool name pattern (regex)",
        "hooks": [
          {
            "type": "command",
            "command": "shell command to execute",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

Matcher Patterns

The matcher filters which tools the Hook applies to:

| Pattern | Matches | | ------------- | ---------------------------- | | Edit\|Write | File edit/create tools | | Bash | Shell command execution | | Read | File reading | | .* | All tools (use with caution) |

Exit Codes Determine Behavior

The Hook's exit code determines what happens:

| Exit Code | Behavior | | --------- | ---------------------------------- | | 0 | Success — continue the operation | | 2 | Block — prevent tool execution | | Other | Treated as error |

Returning exit 2 from PreToolUse blocks the operation:

exit 2

Send a message via stderr to tell Claude why it was blocked:

echo "Cannot modify files on the main branch." >&2
exit 2

Deep Dive

What environment variables are available in Hooks?

You can use these environment variables inside Hook commands:

  • $CLAUDE_FILE_PATHS — file paths related to the current tool call (space-separated)
  • $CLAUDE_PROJECT_DIR — absolute path to the project root directory

For more detailed information, parse the JSON passed via stdin with jq:

jq -r '.tool_input.command' >> ~/.claude/bash-log.txt
  1. Add a main branch protection Hook to .claude/settings.json and test it.
  2. Create a PostToolUse Hook that auto-runs prettier --write after file modifications.
  3. Create a SessionStart Hook that prints "Current branch: {branch name}" when a session starts.

Q1. Which Hook event runs before Claude Code modifies a file?

  • A) PostToolUse
  • B) SessionStart
  • C) PreToolUse
  • D) Stop

Further Reading