Skip to content

Hooks

Automate actions with lifecycle hooks that run before or after Claude operations.

What You'll Learn

  • What hooks are and when to use them
  • Available hook types
  • Creating hook scripts
  • Real-world hook examples

What Are Hooks?

Hooks are shell commands that run automatically at specific points in Claude Code's lifecycle. They let you:

  • Run linting before commits
  • Notify when Claude modifies files
  • Validate changes before they're applied
  • Log operations for auditing

Available Hooks

Hook When It Runs
pre-tool-use Before any tool runs
post-tool-use After any tool runs
notification When Claude has a notification
user-prompt-submit When you submit a prompt
stop When Claude stops or crashes

Configuration

Hooks are configured in Claude settings. Access with:

> /config

Or edit the settings file directly: - Project: .claude/settings.json - User: ~/.claude/settings.json

Basic Structure

{
  "hooks": {
    "pre-tool-use": [
      {
        "matcher": "Edit",
        "command": "/path/to/script.sh"
      }
    ]
  }
}

Hook Matchers

Matchers filter which operations trigger the hook:

By Tool Name

{
  "matcher": "Edit",
  "command": "echo 'File being edited'"
}

Matches only Edit operations.

By Pattern

{
  "matcher": "Edit:*.py",
  "command": "echo 'Python file edited'"
}

Matches Python file edits.

All Operations

{
  "matcher": "*",
  "command": "echo 'Something happened'"
}

Environment Variables

Hooks receive context via environment variables:

Variable Content
CLAUDE_TOOL_NAME Name of the tool
CLAUDE_TOOL_ARGS JSON of arguments
CLAUDE_TOOL_RESULT Result (post-tool only)
CLAUDE_SESSION_ID Current session ID
CLAUDE_WORKING_DIR Working directory

Real-World Examples

Example 1: Lint on Edit

Run linting when Python files are modified:

{
  "hooks": {
    "post-tool-use": [
      {
        "matcher": "Edit:*.py",
        "command": "ruff check ${CLAUDE_TOOL_ARGS} --fix"
      }
    ]
  }
}

Example 2: Format on Save

Auto-format edited files:

{
  "hooks": {
    "post-tool-use": [
      {
        "matcher": "Edit:*.ts",
        "command": "prettier --write $(echo $CLAUDE_TOOL_ARGS | jq -r '.file_path')"
      },
      {
        "matcher": "Edit:*.tsx",
        "command": "prettier --write $(echo $CLAUDE_TOOL_ARGS | jq -r '.file_path')"
      }
    ]
  }
}

Example 3: Security Check

Block editing of sensitive files:

Create a script ~/.claude/scripts/security-check.sh:

#!/bin/bash

FILE_PATH=$(echo "$CLAUDE_TOOL_ARGS" | jq -r '.file_path')

# Block sensitive files
if [[ "$FILE_PATH" == *.env* ]] || [[ "$FILE_PATH" == *secret* ]]; then
  echo "BLOCKED: Cannot edit sensitive file: $FILE_PATH"
  exit 1
fi

exit 0

Configure:

{
  "hooks": {
    "pre-tool-use": [
      {
        "matcher": "Edit",
        "command": "~/.claude/scripts/security-check.sh"
      }
    ]
  }
}

Example 4: Logging

Log all Claude operations:

#!/bin/bash
# ~/.claude/scripts/log-operation.sh

LOG_FILE="$HOME/.claude/operations.log"
TIMESTAMP=$(date -u +"%Y-%m-%d %H:%M:%S")

echo "[$TIMESTAMP] $CLAUDE_TOOL_NAME: $CLAUDE_TOOL_ARGS" >> "$LOG_FILE"
{
  "hooks": {
    "pre-tool-use": [
      {
        "matcher": "*",
        "command": "~/.claude/scripts/log-operation.sh"
      }
    ]
  }
}

Example 5: Notification

Send desktop notification on completion:

{
  "hooks": {
    "notification": [
      {
        "command": "notify-send 'Claude Code' \"$CLAUDE_NOTIFICATION\""
      }
    ]
  }
}

Hook Script Best Practices

Make Them Fast

Hooks run synchronously. Slow hooks = slow Claude.

# Bad: Slow operation
npm test  # Could take minutes

# Good: Quick check
npm run lint:quick  # Just syntax check

Handle Errors Gracefully

#!/bin/bash

if ! command -v prettier &> /dev/null; then
  echo "prettier not found, skipping format"
  exit 0  # Don't fail the operation
fi

prettier --write "$FILE_PATH"

Return Appropriate Exit Codes

  • exit 0 - Success, continue
  • exit 1 - Failure, block operation (for pre-hooks)

Log for Debugging

#!/bin/bash

DEBUG_LOG="/tmp/claude-hook-debug.log"
echo "Hook called at $(date)" >> "$DEBUG_LOG"
echo "Tool: $CLAUDE_TOOL_NAME" >> "$DEBUG_LOG"
echo "Args: $CLAUDE_TOOL_ARGS" >> "$DEBUG_LOG"

Blocking Operations

Pre-hooks can block operations by returning non-zero:

#!/bin/bash
# Block edits after 6pm

HOUR=$(date +%H)
if [ "$HOUR" -ge 18 ]; then
  echo "BLOCKED: No edits after 6pm"
  exit 1
fi

exit 0

When blocked, Claude sees the message and can adjust.

Complex Hook Patterns

Chain Multiple Actions

{
  "hooks": {
    "post-tool-use": [
      {
        "matcher": "Edit:*.ts",
        "command": "npm run format && npm run lint && npm run typecheck"
      }
    ]
  }
}

Conditional Hooks

#!/bin/bash
# Only run on main branch

BRANCH=$(git branch --show-current)
if [ "$BRANCH" = "main" ]; then
  npm run strict-lint
else
  npm run lint
fi

Tool-Specific Logic

#!/bin/bash

case "$CLAUDE_TOOL_NAME" in
  "Edit")
    run_linter
    ;;
  "Write")
    validate_new_file
    ;;
  "Bash")
    log_command
    ;;
esac

Try It Yourself

Exercise: Create a Formatting Hook

  1. Create a hook script:

    mkdir -p ~/.claude/scripts
    cat > ~/.claude/scripts/format-check.sh << 'EOF'
    #!/bin/bash
    FILE=$(echo "$CLAUDE_TOOL_ARGS" | jq -r '.file_path')
    echo "Checking format of: $FILE"
    # Add your formatter here
    EOF
    chmod +x ~/.claude/scripts/format-check.sh
    

  2. Configure the hook:

    cat > .claude/settings.json << 'EOF'
    {
      "hooks": {
        "post-tool-use": [
          {
            "matcher": "Edit",
            "command": "~/.claude/scripts/format-check.sh"
          }
        ]
      }
    }
    EOF
    

  3. Test it:

    > Create a simple JavaScript file
    

Watch for the hook output.

Exercise: Operation Logger

Create a hook that logs all operations to a file, including: - Timestamp - Tool used - Files affected - Success/failure

What's Next?

Extend Claude with external services using MCP in 03-mcp-servers.


Summary: - Hooks run at lifecycle points: pre-tool, post-tool, notification, prompt submit - Matchers filter which operations trigger hooks - Environment variables provide context to hook scripts - Pre-hooks can block operations by returning non-zero - Keep hooks fast and handle errors gracefully


Learning Resources

Edmund Yong: 800+ Hours Learning Claude Code - Hooks (Popular tech channel)

Configure lifecycle hooks for automated linting, formatting, and security checks.

Additional Resources

Type Resource Description
🎬 Video Git Hooks Tutorial Git hook automation basics
📚 Official Docs Hooks Documentation Official hooks reference
📖 Tutorial Plugins & Hooks Anthropic plugin guide
🎓 Free Course Awesome Claude Code Community hooks collection
💼 Commercial Git Complete Advanced hook patterns