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.jsonand 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.
{
"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
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
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.