Claude Code hooks are the mechanism that connects Claude's actions to your organisation's quality gates, compliance checks, and notification systems โ€” automatically, without developer interruption. Every time Claude writes a file, runs a command, or completes a session, hooks can fire: running your linter, logging to your audit system, sending a Slack notification, or blocking an operation that violates a policy.

For individual developers, hooks remove the friction from best practices โ€” the linter runs without being asked, tests fire automatically after code changes, and the commit message gets validated before anything goes to version control. For enterprise teams, hooks are the primary mechanism for enforcing Claude Code governance policies without creating developer friction. You define the rules once; they apply everywhere, automatically.

Claude Code has been Anthropic's fastest-growing commercial product since its launch. As teams scale their usage, hooks become the infrastructure layer that makes Claude Code safe and auditable at enterprise scale โ€” not just productive.

Key Takeaways

  • Hooks fire on specific Claude Code lifecycle events: PreToolUse, PostToolUse, Notification, Stop, and SubagentStop
  • Hooks can block actions (exit code 2), add context to Claude's next step, or simply observe and log
  • The hooks configuration lives in .claude/settings.json and is version-controlled with the project
  • Enterprise hook patterns include: automatic linting, test execution, audit logging, secret scanning, and approval workflows
  • Hooks are the primary mechanism for enforcing Claude Code governance without developer friction

The Claude Code Hook Lifecycle

Claude Code exposes five hook events that cover the full lifecycle of an agentic session. Understanding when each fires โ€” and what data it receives โ€” is the foundation for designing effective hook workflows.

Event When It Fires Primary Use Cases
PreToolUse Before Claude executes any tool (file write, bash command, etc.) Block dangerous operations, validate against policies, require approval
PostToolUse After a tool completes execution Run linter/tests on changed files, log to audit trail, validate output
Notification When Claude wants to notify the user of something Route notifications to Slack, Teams, or PagerDuty; log decisions
Stop When Claude completes a task or session Run full test suite, generate summary report, create PR description
SubagentStop When a sub-agent completes in multi-agent workflows Validate sub-agent output before passing to next agent, audit trail per agent

Each hook receives a structured JSON payload containing the event context: which tool was called, what arguments it received, the current working directory, the session ID, and (for PostToolUse) the tool's output. Your hook script reads this payload from stdin and can write back to stdout to inject context that Claude will see in its next step.

The exit code your hook returns controls what happens next. Exit 0 means proceed normally. Exit 2 means block the operation โ€” Claude will stop and report that the action was blocked by a hook, along with whatever message your hook wrote to stdout. Any other exit code means the hook itself failed, which is treated as a non-blocking warning.

Configuring Hooks in settings.json

Hooks are configured in .claude/settings.json โ€” the same file that controls other Claude Code project settings. The hook configuration specifies which event to listen for, which tool (or all tools) to match on, and the command to run when the hook fires.

.claude/settings.json โ€” Hook Configuration
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/lint-on-write.sh"
          }
        ]
      },
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/audit-logger.py"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/secret-scanner.sh"
          }
        ]
      },
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/dangerous-command-check.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/run-tests-on-stop.sh"
          }
        ]
      }
    ]
  }
}

The matcher field supports exact tool names ("Write", "Bash", "Read") or a wildcard ("*") to match all tools. You can configure multiple hooks for the same event โ€” they run in order, and if any returns exit code 2, the chain stops and the operation is blocked.

Store your hook scripts in .claude/hooks/ within the repository and commit them alongside the configuration. This ensures every developer on the team has the same hooks active, and changes to hook behaviour go through your normal code review process โ€” which is important for any hooks that enforce security or compliance policies.

Essential Hooks for Enterprise Teams

Based on deployments across financial services, healthcare, and manufacturing, here are the hooks that consistently deliver the highest value for enterprise Claude Code teams:

Post-Write Linting

This is the most universally deployed hook. After Claude writes any file, the linter runs automatically on that file. If it finds errors, the output is fed back to Claude, which then fixes the issues before moving on. The developer never needs to manually run the linter โ€” it's part of the agentic loop.

.claude/hooks/lint-on-write.sh
#!/bin/bash
# PostToolUse / Write โ€” Run linter on changed file

# Parse the tool input from stdin
INPUT=$(cat)
FILE=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_input', {}).get('file_path', ''))")

# Only lint if it's a relevant file type
if [[ "$FILE" =~ \.(ts|tsx|js|jsx|py)$ ]]; then
  if [[ "$FILE" =~ \.(ts|tsx|js|jsx)$ ]]; then
    RESULT=$(npx eslint "$FILE" --format compact 2>&1)
    EXIT=$?
  elif [[ "$FILE" =~ \.py$ ]]; then
    RESULT=$(python3 -m ruff check "$FILE" 2>&1)
    EXIT=$?
  fi

  if [ $EXIT -ne 0 ]; then
    echo "Lint errors in $FILE:"
    echo "$RESULT"
    # Exit 0 so Claude sees the output but isn't blocked โ€” it can fix the errors
    exit 0
  fi
fi

exit 0

Secret Scanner (PreToolUse / Write)

Scans file content before it's written. If Claude is about to write a file containing what looks like a credential, API key, or private key, this hook blocks the write and explains why. This is a particularly high-value hook for regulated environments where accidental credential exposure has serious consequences.

.claude/hooks/secret-scanner.sh
#!/bin/bash
# PreToolUse / Write โ€” Block writes containing potential secrets

INPUT=$(cat)
CONTENT=$(echo "$INPUT" | python3 -c "
import sys, json
d = json.load(sys.stdin)
print(d.get('tool_input', {}).get('content', ''))
")

# Patterns that indicate secrets
PATTERNS=(
  'sk-[a-zA-Z0-9]{32,}'          # OpenAI/Anthropic keys
  'AKIA[0-9A-Z]{16}'              # AWS Access Key IDs
  'ghp_[a-zA-Z0-9]{36}'          # GitHub PATs
  'xoxb-[0-9]{11}-[0-9]{11}-'    # Slack Bot Tokens
  '-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----'  # Private keys
  'password\s*=\s*["\x27][^\s"\x27]{8,}'  # Hardcoded passwords
)

for PATTERN in "${PATTERNS[@]}"; do
  if echo "$CONTENT" | grep -qP "$PATTERN" 2>/dev/null; then
    echo "BLOCKED: Potential secret detected in file content."
    echo "Pattern matched: $PATTERN"
    echo "Do not hardcode credentials. Use environment variables or secrets management."
    exit 2  # Block the write
  fi
done

exit 0
โš ๏ธ Critical: Keep Hooks Fast Hooks run synchronously in the Claude Code event loop. A hook that takes 30 seconds to run will make every file write feel sluggish. Keep hooks lightweight: lint the specific file changed (not the whole codebase), use fast tools, and defer expensive operations to the Stop hook which runs once at session end rather than on every action.

Audit Logging Hooks for Enterprise Compliance

In regulated environments, an audit trail of AI-assisted code changes isn't optional โ€” it's a compliance requirement. Claude Code hooks make this achievable without developer friction. Every significant action Claude takes can be logged to a structured audit system, capturing what was done, when, by whom, and in what context.

.claude/hooks/audit-logger.py
#!/usr/bin/env python3
"""
PostToolUse hook โ€” Logs all Claude Code actions to the audit trail.
Writes to a structured JSON log file and optionally to a central audit service.
"""
import sys
import json
import os
import datetime
import hashlib

def main():
    try:
        event = json.load(sys.stdin)
    except json.JSONDecodeError:
        sys.exit(0)  # Don't block on parse failure

    log_entry = {
        "timestamp": datetime.datetime.utcnow().isoformat() + "Z",
        "session_id": event.get("session_id", "unknown"),
        "tool_name": event.get("tool_name", "unknown"),
        "tool_input": event.get("tool_input", {}),
        "tool_response_summary": summarise_response(event.get("tool_response", "")),
        "user": os.environ.get("USER", "unknown"),
        "project": os.path.basename(os.getcwd()),
        "git_branch": get_git_branch()
    }

    # Write to local audit log
    log_dir = ".claude/audit-logs"
    os.makedirs(log_dir, exist_ok=True)
    log_file = f"{log_dir}/{datetime.date.today().isoformat()}.jsonl"
    with open(log_file, "a") as f:
        f.write(json.dumps(log_entry) + "\n")

    # In enterprise deployments: also POST to central audit service
    # requests.post(os.environ.get("AUDIT_SERVICE_URL"), json=log_entry)

    sys.exit(0)  # Never block โ€” auditing is always non-blocking

def summarise_response(response):
    if not response:
        return None
    text = str(response)
    if len(text) > 200:
        return text[:200] + f"... [{len(text)} chars total]"
    return text

def get_git_branch():
    try:
        import subprocess
        result = subprocess.run(["git", "branch", "--show-current"],
                                capture_output=True, text=True, timeout=2)
        return result.stdout.strip()
    except Exception:
        return "unknown"

if __name__ == "__main__":
    main()

For enterprise audit requirements, we typically extend this pattern to send events to a central SIEM or audit service โ€” Splunk, Datadog, or a custom audit API. The hook writes to the local log as a fallback but also ships to the central system. This means AI-assisted code activity is captured in the same audit infrastructure as every other privileged operation in the environment.

Need Compliant Claude Code Hooks for Your Enterprise?

Our Claude security and governance service designs and deploys hook configurations that meet your organisation's compliance requirements โ€” SOC 2, HIPAA, financial services regulations, and custom security policies.

Approval Workflows with PreToolUse Hooks

For high-stakes operations โ€” deploying to production, modifying database schemas, changing infrastructure configuration โ€” you might want a human approval step before Claude proceeds. PreToolUse hooks can implement this pattern by blocking the operation until an approval is received.

The most common implementation sends a Slack message to a designated channel and then polls for a response. When the approver reacts with a โœ… or types "approved", the hook exits 0 and the operation proceeds. This turns Claude Code into a governed automation system where sensitive operations require explicit human sign-off โ€” without requiring the developer to leave their terminal.

.claude/hooks/require-approval.sh
#!/bin/bash
# PreToolUse / Bash โ€” Require approval for production commands

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | python3 -c "
import sys, json
d = json.load(sys.stdin)
print(d.get('tool_input', {}).get('command', ''))
")

# Check if this looks like a production deployment
if echo "$COMMAND" | grep -qE "(deploy|kubectl apply|terraform apply|helm upgrade).*prod"; then
  SESSION_ID=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('session_id',''))")

  # Notify the approval channel
  python3 .claude/hooks/slack-approval-request.py \
    --command "$COMMAND" \
    --session "$SESSION_ID" \
    --user "$USER"

  APPROVAL_STATUS=$?

  if [ $APPROVAL_STATUS -eq 0 ]; then
    echo "Production deployment approved. Proceeding."
    exit 0
  else
    echo "Production deployment not approved within timeout window."
    echo "Contact your DevOps lead to approve this operation."
    exit 2  # Block the command
  fi
fi

exit 0

For teams adopting Claude Code in regulated environments, this pattern is often the deciding factor that makes enterprise-wide deployment politically viable. Security and compliance teams accept Claude Code when they can see that their existing approval workflows are respected and enforced โ€” not bypassed. Our Claude governance framework includes pre-built hook patterns for the most common enterprise approval systems.

Automated Testing Hooks

The Stop hook โ€” which fires when Claude completes a task โ€” is the natural place to run your test suite. This is more reliable than running tests on every file write (too slow) but ensures that every completed Claude Code session ends with a verified build.

.claude/hooks/run-tests-on-stop.sh
#!/bin/bash
# Stop hook โ€” Run affected tests after Claude completes a task

# Get list of files changed in this session from git
CHANGED_FILES=$(git diff --name-only HEAD 2>/dev/null)

if [ -z "$CHANGED_FILES" ]; then
  echo "No files changed. Skipping test run."
  exit 0
fi

echo "Running tests for changed files..."

# For TypeScript/JavaScript projects
if echo "$CHANGED_FILES" | grep -q "\.(ts|tsx|js|jsx)$"; then
  npx vitest run --changed HEAD 2>&1
  TEST_EXIT=$?

  if [ $TEST_EXIT -ne 0 ]; then
    echo ""
    echo "โš ๏ธ  Tests failed. Review the failures above before committing."
    # Exit 0 โ€” inform Claude but don't block (it already ran the task)
    # Claude will see this output and can offer to fix the failures
    exit 0
  else
    echo "โœ… All tests passed."
  fi
fi

exit 0
๐Ÿ’ก Feed Test Results Back to Claude When tests fail in a Stop hook, your hook output is visible to Claude. Claude Code can read the test failures and offer to fix them โ€” without the developer needing to manually trigger a second session. This creates a self-correcting loop: Claude writes code, tests run, failures are reported, Claude fixes them. The developer reviews the final result, not every intermediate step.

Dangerous Command Protection

One of the most common concerns about AI-assisted coding tools is accidental execution of destructive commands. Claude Code's PreToolUse hook on Bash commands is the right place to implement a last-line-of-defence check against truly dangerous operations.

.claude/hooks/dangerous-command-check.sh
#!/bin/bash
# PreToolUse / Bash โ€” Block genuinely dangerous commands

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | python3 -c "
import sys, json
d = json.load(sys.stdin)
print(d.get('tool_input', {}).get('command', ''))
")

# Absolute blocks โ€” these should never run in a developer workflow
BLOCKED_PATTERNS=(
  "rm -rf /"
  "rm -rf ~"
  "rm -rf \$HOME"
  "DROP DATABASE"
  "DROP TABLE.*--"
  "FORMAT C:"
  "> /dev/sda"
  "dd if=/dev/zero of=/dev/sd"
)

for PATTERN in "${BLOCKED_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -qi "$PATTERN"; then
    echo "BLOCKED: Potentially destructive command detected."
    echo "Command: $COMMAND"
    echo "This command matches a blocked pattern: $PATTERN"
    echo "If you intended to run this, execute it manually with explicit confirmation."
    exit 2
  fi
done

# Soft warnings โ€” log but don't block
WARNING_PATTERNS=(
  "git push --force"
  "git reset --hard"
  "truncate.*table"
  "delete from.*where 1=1"
)

for PATTERN in "${WARNING_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -qi "$PATTERN"; then
    echo "WARNING: High-risk command detected: $COMMAND"
    echo "Proceeding as requested, but please verify this is intentional."
    # Exit 0 โ€” warn but don't block
    exit 0
  fi
done

exit 0

In enterprise environments, we layer this hook with the approval workflow hook: truly catastrophic commands are blocked outright, high-risk-but-legitimate operations require approval, and routine operations proceed without friction. This three-tier model gives teams confidence in deploying Claude Code to developers at all seniority levels โ€” including those who might not yet have the instinct to second-guess a destructive command.

Hooks as Part of Your CI/CD Strategy

Claude Code hooks don't replace your CI/CD pipeline โ€” they complement it. Think of hooks as the fast, local quality gate that catches issues before they reach CI, and CI as the comprehensive gate that runs the full test suite in a clean environment.

The most effective pattern we've seen in enterprise Claude Code deployments is a three-layer model: the PostToolUse lint hook catches syntax errors immediately, the Stop hook runs affected unit tests, and CI runs the full integration and end-to-end suite on PR. This means 80% of issues are caught before a PR is even opened โ€” without running a full CI build on every file change.

For teams with complex CI/CD requirements โ€” multiple environments, required approvals, deployment windows โ€” we also build hooks that enforce CI/CD process discipline: Claude can't create a PR unless all local checks pass, can't deploy to staging without a green build, and can't deploy to production without an approved PR. These guardrails make Claude Code deployment safe for even the most conservative engineering environments.

If you're ready to build a production-grade Claude Code deployment with a complete hooks configuration, book a free strategy call with our Claude Certified Architects. We've done this across financial services, healthcare, and manufacturing โ€” and the pattern is replicable for any enterprise that takes code quality and compliance seriously.

๐Ÿ”ง

ClaudeImplementation Team

Claude Certified Architects who design governance frameworks and hook configurations for enterprise Claude Code deployments. We've built audit-compliant Claude setups for finance, healthcare, and manufacturing. Learn about our team โ†’