---
title: "Claude Code Hooks: Automating Your Development Workflow"
description: "Deep dive into Claude Code hooks — pre and post tool execution hooks that let you enforce linting, run tests automatically, validate changes, and build custom CI-like workflows."
canonical: https://callsphere.ai/blog/claude-code-hooks-workflow-automation
category: "Agentic AI"
tags: ["Claude Code", "Hooks", "Workflow Automation", "Developer Tools", "CI/CD"]
author: "CallSphere Team"
published: 2026-01-10T00:00:00.000Z
updated: 2026-05-21T00:37:04.887Z
---

# Claude Code Hooks: Automating Your Development Workflow

> Deep dive into Claude Code hooks — pre and post tool execution hooks that let you enforce linting, run tests automatically, validate changes, and build custom CI-like workflows.

## What Are Claude Code Hooks?

Claude Code hooks are user-defined shell commands that execute automatically at specific points during Claude Code's agentic workflow. They let you inject custom logic before or after Claude Code performs actions — similar to git hooks, but for AI-assisted development.

Hooks solve a fundamental problem: you want Claude Code to follow specific procedures (run linting after every edit, validate JSON schemas, check for secrets) but you do not want to repeat these instructions in every conversation. Hooks make these procedures automatic and enforceable.

## Hook Types

Claude Code supports hooks at several execution points:

```mermaid
flowchart LR
    USER(["User message"])
    LOOP{"messages.create
agent loop"}
    THINK["Extended thinking
optional"]
    TOOL{"stop_reason
tool_use?"}
    EXEC["Execute tool
append tool_result"]
    DONE(["stop_reason
end_turn"])
    USER --> LOOP --> THINK --> TOOL
    TOOL -->|Yes| EXEC --> LOOP
    TOOL -->|No| DONE
    style LOOP fill:#4f46e5,stroke:#4338ca,color:#fff
    style THINK fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
    style DONE fill:#059669,stroke:#047857,color:#fff
```

### PreToolUse Hooks

PreToolUse hooks run **before** Claude Code executes a tool. They can inspect the planned action and either allow it, modify it, or block it.

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit",
        "hook": "python3 .claude/hooks/pre-edit-check.py"
      }
    ]
  }
}
```

Use cases:

- **Block edits to protected files** — Prevent Claude from modifying migration files, lock files, or generated code
- **Validate before write** — Check that new files follow naming conventions
- **Security scanning** — Scan Bash commands for dangerous operations before execution

### PostToolUse Hooks

PostToolUse hooks run **after** a tool completes. They can inspect the result and trigger follow-up actions.

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hook": "npx eslint --fix $CLAUDE_FILE_PATH"
      },
      {
        "matcher": "Write",
        "hook": "npx prettier --write $CLAUDE_FILE_PATH"
      }
    ]
  }
}
```

Use cases:

- **Auto-format after edits** — Run Prettier, Black, or gofmt on every modified file
- **Lint checking** — Run ESLint or Ruff after file changes
- **Test execution** — Automatically run relevant tests after code changes
- **Schema validation** — Validate JSON/YAML files after writing

### Notification Hooks

Notification hooks trigger when specific events occur, such as Claude Code requesting user input or completing a long task.

```json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hook": "terminal-notifier -message '$CLAUDE_NOTIFICATION' -title 'Claude Code'"
      }
    ]
  }
}
```

## Configuring Hooks

Hooks are defined in `.claude/settings.json` at the project level or `~/.claude/settings.json` globally.

### Full Configuration Example

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hook": "python3 .claude/hooks/validate-bash-command.py",
        "timeout": 5000
      },
      {
        "matcher": "Edit|Write",
        "hook": ".claude/hooks/check-protected-files.sh"
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hook": ".claude/hooks/post-edit.sh"
      },
      {
        "matcher": "Write",
        "hook": ".claude/hooks/post-write.sh"
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hook": "notify-send 'Claude Code' '$CLAUDE_NOTIFICATION'"
      }
    ]
  }
}
```

### Matcher Patterns

The `matcher` field determines which tool triggers the hook. It supports:

- Exact match: `"Edit"` — only Edit tool calls
- Pipe-separated alternatives: `"Edit|Write"` — both Edit and Write
- Empty string: `""` — matches all tools/events

### Environment Variables Available to Hooks

| Variable | Description |
| --- | --- |
| `$CLAUDE_TOOL_NAME` | The tool being called (Read, Edit, Write, Bash, etc.) |
| `$CLAUDE_FILE_PATH` | The file being operated on (for file tools) |
| `$CLAUDE_BASH_COMMAND` | The command being executed (for Bash tool) |
| `$CLAUDE_NOTIFICATION` | The notification message (for Notification hooks) |
| `$CLAUDE_PROJECT_DIR` | The project root directory |

## Practical Hook Recipes

### Recipe 1: Auto-Format on Every Edit

```bash
#!/bin/bash
# .claude/hooks/post-edit.sh
FILE="$CLAUDE_FILE_PATH"

case "$FILE" in
  *.ts|*.tsx|*.js|*.jsx)
    npx prettier --write "$FILE" 2>/dev/null
    npx eslint --fix "$FILE" 2>/dev/null
    ;;
  *.py)
    ruff format "$FILE" 2>/dev/null
    ruff check --fix "$FILE" 2>/dev/null
    ;;
  *.go)
    gofmt -w "$FILE" 2>/dev/null
    ;;
  *.rs)
    rustfmt "$FILE" 2>/dev/null
    ;;
esac
```

This hook auto-formats every file Claude Code edits, ensuring consistent style without Claude needing to worry about formatting.

### Recipe 2: Protect Critical Files

```bash
#!/bin/bash
# .claude/hooks/check-protected-files.sh

PROTECTED_PATTERNS=(
  "*.lock"
  "package-lock.json"
  "yarn.lock"
  "migrations/versions/*.py"
  ".env*"
  "*.pem"
  "*.key"
)

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$CLAUDE_FILE_PATH" == $pattern ]]; then
    echo "BLOCKED: Cannot modify protected file: $CLAUDE_FILE_PATH"
    exit 1
  fi
done

exit 0
```

When a PreToolUse hook exits with a non-zero code, Claude Code blocks the tool execution and shows the hook's output to the model, which then adjusts its approach.

### Recipe 3: Run Tests After Changes

```bash
#!/bin/bash
# .claude/hooks/post-edit-test.sh

FILE="$CLAUDE_FILE_PATH"

# Find and run related test files
if [[ "$FILE" == *.py ]]; then
  TEST_FILE="${FILE/app\//tests/test_}"
  if [[ -f "$TEST_FILE" ]]; then
    pytest "$TEST_FILE" -x --tb=short -q 2>&1 | tail -5
  fi
elif [[ "$FILE" == *.ts || "$FILE" == *.tsx ]]; then
  TEST_FILE="${FILE%.ts*}.test${FILE##*.ts}"
  if [[ -f "$TEST_FILE" ]]; then
    npx vitest run "$TEST_FILE" --reporter=verbose 2>&1 | tail -10
  fi
fi
```

### Recipe 4: Secret Detection

```bash
#!/bin/bash
# .claude/hooks/scan-secrets.sh

FILE="$CLAUDE_FILE_PATH"

# Skip binary files and known safe patterns
if file "$FILE" | grep -q "binary"; then
  exit 0
fi

# Check for common secret patterns
PATTERNS=(
  "sk-[a-zA-Z0-9]{20,}"
  "AKIA[0-9A-Z]{16}"
  "ghp_[a-zA-Z0-9]{36}"
  "-----BEGIN (RSA |EC )?PRIVATE KEY-----"
  "password\s*=\s*["'][^"']{8,}["']"
)

for pattern in "${PATTERNS[@]}"; do
  if grep -qP "$pattern" "$FILE" 2>/dev/null; then
    echo "BLOCKED: Potential secret detected in $FILE matching pattern: $pattern"
    exit 1
  fi
done

exit 0
```

## Hook Execution Order and Error Handling

### Execution Order

1. PreToolUse hooks run sequentially in the order they are defined
2. If any PreToolUse hook exits non-zero, the tool call is blocked
3. If all PreToolUse hooks pass, the tool executes
4. PostToolUse hooks run sequentially after the tool completes
5. PostToolUse hook failures are reported but do not undo the tool execution

### Timeout Handling

Hooks have a configurable timeout (default: 60 seconds). If a hook exceeds its timeout, it is killed and treated as a failure for PreToolUse (blocks the action) or a warning for PostToolUse.

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hook": "npm test -- --timeout 30000",
        "timeout": 45000
      }
    ]
  }
}
```

### Hook Output

Hooks can write to stdout and stderr. This output is captured and fed back to Claude Code's model, allowing it to react to hook results. For example, if a linting hook reports errors, Claude Code will see those errors and can fix them in its next tool call.

## Hooks vs CLAUDE.md Instructions

Both hooks and CLAUDE.md instructions influence Claude Code's behavior, but they work differently:

| Aspect | CLAUDE.md | Hooks |
| --- | --- | --- |
| Enforcement | Advisory (model follows them but can deviate) | Mandatory (PreToolUse hooks block execution) |
| Execution | Interpreted by the AI model | Executed as shell commands |
| Timing | Read at session start | Run at each tool call |
| Reliability | High but not guaranteed | Guaranteed (scripts run regardless) |

**Use CLAUDE.md for:** coding conventions, architecture guidelines, style preferences

**Use hooks for:** formatting enforcement, security scanning, file protection, automated testing

The combination is powerful: CLAUDE.md tells Claude Code how to write code, and hooks verify that the code meets your standards after every change.

## Team Workflow with Hooks

When your hooks are committed to the repository in `.claude/settings.json` and `.claude/hooks/`, every team member who uses Claude Code gets the same automated checks. This creates a consistent development experience:

1. A developer asks Claude Code to implement a feature
2. Claude Code writes the code
3. PostToolUse hooks automatically format it and run linting
4. If linting fails, Claude Code sees the errors and fixes them
5. The developer reviews clean, formatted, validated code

This is essentially a local CI pipeline that runs on every AI-generated edit.

## Conclusion

Claude Code hooks transform your AI coding assistant from a tool that follows suggestions into one that enforces standards. By combining PreToolUse hooks (for protection and validation) with PostToolUse hooks (for formatting and testing), you create guardrails that ensure Claude Code's output meets your team's quality bar automatically, every time.

---

Source: https://callsphere.ai/blog/claude-code-hooks-workflow-automation
