Claudeintermediate

How to Use Claude Code Hooks for Automation

Master Claude Code hooks to automate your development workflow. Learn to configure PreToolUse, PostToolUse, and other hook events for automated testing, formatting, CI/CD integration, and custom workflows.

15 min readUpdated January 2025

Want us to handle this for you?

Get expert help →

Claude Code hooks are powerful automation triggers that execute shell commands at key points during your coding sessions. They provide deterministic control over Claude's behavior, ensuring specific actions always happen rather than relying on the AI to choose them.

Understanding Hook Events

Claude Code supports multiple hook events that fire at different points in the session lifecycle:

Hook EventWhen It FiresCommon Use Cases
SessionStartWhen session begins or resumesSet environment variables, load configs
UserPromptSubmitWhen you submit a prompt, before processingInput validation, context injection
PreToolUseBefore Claude executes a toolBlock dangerous commands, validate inputs
PermissionRequestWhen permission dialog appearsAuto-approve/deny based on rules
PostToolUseAfter a tool completes successfullyRun linters, formatters, tests
PostToolUseFailureAfter a tool failsError logging, notifications
StopWhen Claude finishes respondingNotifications, cleanup tasks
SubagentStopWhen a subagent finishesSubagent-specific cleanup
NotificationWhen Claude sends notificationsCustom notification routing
SessionEndWhen session terminatesFinal cleanup, logging

Configuration Basics

Configuration File Locations

Hooks can be configured at three levels:

  • User settings: ~/.claude/settings.json - applies globally to all projects
  • Project settings: .claude/settings.json - version-controlled, shared with team
  • Local project: .claude/settings.local.json - local overrides, not committed

Basic Configuration Structure

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npm run lint --fix",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

Using the Interactive Configuration

The easiest way to configure hooks is through Claude Code's built-in interface:

  1. Start Claude Code in your project directory
  2. Type /hooks to open the hook configuration menu
  3. Select an event type (e.g., PostToolUse)
  4. Add a matcher pattern (e.g., Write|Edit)
  5. Enter your command

This interactive method is recommended for beginners as it validates your configuration automatically.

Matcher Syntax

Matchers determine which tools trigger your hooks. They apply to PreToolUse, PermissionRequest, and PostToolUse events.

Matching Patterns

// Exact tool match
"matcher": "Write"

// Multiple tools with pipe syntax
"matcher": "Edit|Write|MultiEdit"

// Regex pattern
"matcher": "Notebook.*"

// Match all tools
"matcher": "*"

Common Tool Names

ToolDescription
BashShell commands
ReadReading files
WriteCreating new files
EditModifying existing files
MultiEditMultiple file edits
GlobFile pattern matching
GrepContent searching
WebFetchFetching web content
TaskSubagent tasks
mcp__*MCP server tools

Practical Hook Examples

Automatic Code Formatting

Run Prettier on JavaScript/TypeScript files after every edit:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write \"$(echo $CLAUDE_FILE_PATHS | tr ',' ' ')\" 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}

Run Tests on File Changes

Automatically run related tests when test files are modified:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": "if [[ \"$CLAUDE_FILE_PATHS\" == *\".test.\"* ]]; then npm test -- --related; fi"
          }
        ]
      }
    ]
  }
}

TypeScript Type Checking

Run type checking after editing TypeScript files:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "if [[ \"$CLAUDE_FILE_PATHS\" == *\".ts\"* ]]; then npx tsc --noEmit; fi"
          }
        ]
      }
    ]
  }
}

Block Dangerous Commands

Prevent accidental destructive operations:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'rm -rf /|DROP DATABASE|format c:'; then echo 'Dangerous command blocked!' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}

Protect Sensitive Files

Block modifications to critical files:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 -c \"import json, sys; data=json.load(sys.stdin); path=data.get('tool_input',{}).get('file_path',''); sys.exit(2 if any(p in path for p in ['.env', 'package-lock.json', '.git/', 'secrets']) else 0)\""
          }
        ]
      }
    ]
  }
}

macOS Desktop Notifications

Get notified when Claude finishes a task (macOS):

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude has finished the task\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Windows Desktop Notifications

PowerShell notification for Windows:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "powershell -Command \"[System.Windows.Forms.MessageBox]::Show('Claude has finished the task', 'Claude Code')\""
          }
        ]
      }
    ]
  }
}

Linux Desktop Notifications

Using notify-send on Linux:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "notify-send 'Claude Code' 'Claude has finished the task'"
          }
        ]
      }
    ]
  }
}

Command Logging

Log all bash commands for compliance or debugging:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '\"[\\(now | strftime(\"%Y-%m-%d %H:%M:%S\"))] \\(.tool_input.command)\"' >> ~/.claude/command-log.txt"
          }
        ]
      }
    ]
  }
}

Environment Variables

Hooks have access to several environment variables:

VariableDescription
CLAUDE_PROJECT_DIRAbsolute path to the project root
CLAUDE_ENV_FILEFile path for persisting environment variables (SessionStart only)
CLAUDE_CODE_REMOTE"true" if running in remote/web environment
CLAUDE_PLUGIN_ROOTPath to plugin directory (for plugin hooks)

Persisting Environment Variables

Use SessionStart hooks to set environment variables for the entire session:

#!/bin/bash
# ~/.claude/hooks/session-init.sh
if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
  echo 'export DEBUG=true' >> "$CLAUDE_ENV_FILE"
fi
exit 0
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-init.sh"
          }
        ]
      }
    ]
  }
}

Hook Input and Output

Input Format

Hooks receive JSON data via stdin containing context about the operation:

{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/current/working/directory",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm install",
    "description": "Install dependencies"
  }
}

Exit Codes

Exit CodeBehavior
0Success - continue execution
2Blocking error - stop the action (PreToolUse/PermissionRequest only)
OtherNon-blocking error - show warning, continue

JSON Output for Advanced Control

Hooks can return JSON to control behavior:

{
  "continue": true,
  "suppressOutput": false,
  "systemMessage": "Warning: Consider using pnpm instead of npm",
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "additionalContext": "Approved for development environment"
  }
}

CI/CD Integration

Headless Mode

For CI/CD pipelines, use Claude Code's headless mode with hooks:

# Run Claude Code non-interactively
claude -p "Fix all linting errors in src/" --output-format stream-json

GitHub Actions Example

name: Claude Code Review
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Claude Code
        run: curl -fsSL https://claude.ai/install.sh | bash

      - name: Run Claude Review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          claude -p "Review the changes in this PR and check for:
          1. Security vulnerabilities
          2. Performance issues
          3. Code style violations
          Report findings in markdown format."

Pre-commit Hook Integration

Create a pre-commit hook that uses Claude Code:

#!/bin/bash
# .git/hooks/pre-commit

# Get staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|tsx)$')

if [ -n "$STAGED_FILES" ]; then
  # Run Claude Code to check for issues
  echo "$STAGED_FILES" | claude -p "Review these staged files for obvious bugs or security issues. Exit with code 1 if critical issues found."

  if [ $? -ne 0 ]; then
    echo "Claude found issues. Please fix before committing."
    exit 1
  fi
fi

Detecting Remote Environments

Use the CLAUDE_CODE_REMOTE variable to run different logic in CI:

#!/bin/bash
if [ "$CLAUDE_CODE_REMOTE" = "true" ]; then
  # CI/CD environment - use stricter checks
  npm run lint -- --max-warnings 0
  npm run test -- --coverage --ci
else
  # Local development - more relaxed
  npm run lint
fi

Platform-Specific Notes

macOS

  • Use osascript for native notifications
  • Homebrew-installed tools are typically in /opt/homebrew/bin (Apple Silicon) or /usr/local/bin (Intel)
  • File paths are case-insensitive by default

Windows

  • Native Windows support available since Claude Code 2.x
  • Use PowerShell for complex scripts
  • WSL 2 provides sandboxing support if needed
  • File paths use backslashes but Claude Code handles both

Linux

  • Use notify-send for desktop notifications (requires libnotify)
  • Ensure hook scripts have execute permissions: chmod +x script.sh
  • SELinux/AppArmor may affect hook execution

Security Best Practices

Hooks execute arbitrary shell commands with your user permissions. Follow these guidelines:

  1. Validate inputs: Never trust data from stdin without validation
  2. Quote variables: Always use "$VAR" not $VAR to prevent word splitting
  3. Block path traversal: Check for .. in file paths
  4. Use absolute paths: Reference $CLAUDE_PROJECT_DIR for project files
  5. Skip sensitive files: Don't process .env, .git/, or key files
  6. Review changes: Hooks require manual review in /hooks menu to take effect

Debugging Hooks

Enable Debug Output

claude --debug

This shows detailed hook execution information.

Verbose Mode

Press Ctrl+O in Claude Code to toggle verbose mode, which displays hook stdout.

Test Hooks Manually

Test your hook commands outside Claude Code first:

# Simulate hook input
echo '{"tool_name":"Bash","tool_input":{"command":"npm test"}}' | your-hook-script.sh
echo $?  # Check exit code

Troubleshooting

Hook Not Executing

  1. Verify the hook is registered with /hooks command
  2. Check the matcher pattern matches the tool name exactly
  3. Ensure the hook command is executable
  4. Review debug output with claude --debug

Hook Times Out

Default timeout is 60 seconds. Increase it in your configuration:

{
  "type": "command",
  "command": "npm run build",
  "timeout": 300
}

Permission Denied

Ensure hook scripts have execute permissions:

chmod +x ~/.claude/hooks/my-hook.sh

Additional Resources


Need help automating your development workflow? Inventive HQ specializes in AI-powered development tooling and DevOps automation. Contact us to discuss how we can streamline your engineering processes.

Frequently Asked Questions

Find answers to common questions

Hooks are user-defined shell commands that execute automatically at specific points during Claude Code's operation. They act as 'if this, then that' rules - for example, automatically running a linter whenever Claude edits a file, or blocking dangerous commands before they execute.

Need Professional IT & Security Help?

Our team of experts is ready to help protect and optimize your technology infrastructure.