Hooks — Automated Guardrails for Claude Code¶
Hooks are shell commands that run automatically at key points in Claude Code's lifecycle. They can block dangerous operations, enforce standards, validate code, or log activity.
How Hooks Work¶
User Request → Claude picks tool → [PreToolUse Hook] → Tool executes → [PostToolUse Hook]
↓
Exit code 0 = allow
Exit code 2 = block (with message)
All Hook Events (13 events)¶
Lifecycle Hooks¶
| Event | When | Use For |
|---|---|---|
Setup |
Repository initialization or periodic maintenance | Inject environment, set up dependencies |
SessionStart |
Session begins (startup, resume, clear) | Load context, log session start |
SessionEnd |
Session ends | Cleanup temp files, log session duration |
Conversation Hooks¶
| Event | When | Use For |
|---|---|---|
UserPromptSubmit |
User submits a prompt | Validate input, log prompts, store session data |
Tool Execution Hooks¶
| Event | When | Use For |
|---|---|---|
PreToolUse |
Before tool executes | Block dangerous edits, validate input, security checks |
PostToolUse |
After tool succeeds | Auto-format, lint validation, verify output |
PostToolUseFailure |
After tool fails | Log failures, error analysis |
Permission Hooks¶
| Event | When | Use For |
|---|---|---|
PermissionRequest |
Claude requests permission | Auto-allow safe operations, log permission decisions |
Notification Hooks¶
| Event | When | Use For |
|---|---|---|
Notification |
Claude sends a notification | Alerts, TTS announcements, logging |
Completion Hooks¶
| Event | When | Use For |
|---|---|---|
Stop |
Claude finishes a response | Generate summaries, cleanup, notify user |
SubagentStart |
A sub-agent spawns | Log sub-agent activity, debug |
SubagentStop |
A sub-agent finishes | Track sub-agent completion |
Maintenance Hooks¶
| Event | When | Use For |
|---|---|---|
PreCompact |
Before context compaction | Backup transcripts, save state |
Configuration¶
Add hooks to .claude/settings.local.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/security-check.py"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/lint-on-edit.py"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/stop-summary.py"
}
]
}
]
}
}
Matcher Patterns¶
Write— matches Write tool onlyWrite|Edit— matches Write or EditBash— matches Bash tool*— matches all tools (use sparingly)""(empty) — for events without tool matchers (Stop, SessionStart, etc.)
Hook Input¶
Hooks receive JSON on stdin with context about the event:
PreToolUse / PostToolUse:
{
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.py",
"content": "file contents..."
}
}
Stop:
Hook Output¶
- Exit 0: Allow the operation (stdout ignored for most hooks)
- Exit 2: Block the operation (stdout shown as reason)
- Any other exit: Treated as allow (fail-open for safety)
Advanced — JSON output (for hooks that need granular control):
Available Hook Scripts¶
PreToolUse Hooks¶
security-check.py¶
Blocks edits that introduce secrets, API keys, or credentials.
Detects: API keys, secret keys, bearer tokens, GitHub tokens, private keys, hardcoded passwords.
Skips: .env.example, test files, .claude/ directory.
protect-files.py¶
Blocks edits to protected files (lock files, generated files, .env).
permission-auto-allow.py¶
Auto-allows safe read-only operations without prompting.
PostToolUse Hooks¶
format-on-edit.py¶
Auto-formats files after editing (runs formatters by file extension).
lint-on-edit.py¶
Runs linter after file edits and blocks if errors found (validation gate).
Completion Hooks¶
stop-summary.py¶
Logs a summary when Claude finishes a task.
Audit Hooks¶
log-commands.sh¶
Logs all Bash commands to an audit file for review.
cp hooks/log-commands.sh /your-project/.claude/hooks/
chmod +x /your-project/.claude/hooks/log-commands.sh
Installation¶
- Copy desired hook scripts to
.claude/hooks/in your project - Add hook configuration to
.claude/settings.local.json - Test by triggering the hook (e.g., try to write a file with a fake API key)
Tips¶
- Hooks should be fast (<1 second) — they run on every matching tool call
- Always fail-open (exit 0 on errors) to avoid blocking legitimate work
- Use
matcherto limit which tools trigger the hook - Test hooks before relying on them
- For PostToolUse validation (lint, type check), use exit 2 to block and show errors
- Keep audit logs in
.claude/audit/(gitignored)