Claude Code Hooks Mastery: Automating PreToolUse, Guardrails, and Lifecycle Events
Anthropic’s introduction of deterministic execution hooks within Claude Code has redefined how software engineering teams enforce security governance over autonomous coding agents.
While prompt instructions inside CLAUDE.md shape model behavior probabilistically, enterprise safety requires guaranteed mechanics. Lifecycle hooks operate synchronously outside the language model’s context window, allowing engineers to execute local shell scripts at specific lifecycle events (SessionStart, PreToolUse, PostToolUse, and Stop).
The Lifecycle Pipeline: Deterministic Hooks vs Context Guidance
CLAUDE CODE TOOL EXECUTION PIPELINE
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ Agent Proposes Tool Call │ │ PreToolUse Synchronous Hook │
│ (e.g. `Bash(rm -rf /)`) │ ────────► │ (Inspects Stdin Payload JSON)│
└─────────────────────────────┘ └──────────────┬──────────────┘
│
┌─────────────────────────┴─────────────────────────┐
▼ ▼
[Exit Code 0: APPROVED] [Exit Code 2: BLOCKED]
│ │
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ Executes Tool Action │ │ Halts Operation │
│ & Runs `PostToolUse` │ │ & Notifies Agent │
└───────────────────────┘ └───────────────────────┘
1. Configuring PreToolUse Hooks in settings.json
Define project-level or global lifecycle hooks inside .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "node .claude/hooks/guardrail-bash.js"
}
],
"PostToolUse": [
{
"matcher": "FileWrite",
"command": "npx prettier --write \"$CLAUDE_FILE_PATH\""
}
]
}
}
2. Enforcing Exit Code 2 Guardrails (guardrail-bash.js)
Here is a production-grade Node.js guardrail script (.claude/hooks/guardrail-bash.js) that reads incoming tool call JSON via stdin and exits with code 2 to block destructive operations:
#!/usr/bin/env node
const fs = require('fs');
// Read Tool Operation Payload from stdin
const inputJson = fs.readFileSync(0, 'utf-8');
try {
const payload = JSON.parse(inputJson);
const command = payload.tool_input?.command || '';
// Dangerous Commands Blacklist
const dangerousPatterns = [
/rm\s+-rf/,
/git\s+push\s+--force/,
/DROP\s+TABLE/i,
/env|process\.env/
];
for (const pattern of dangerousPatterns) {
if (pattern.test(command)) {
console.error(`❌ BLOCKED BY GUARDRAIL: Destructive command detected -> "${command}"`);
// Exit Code 2 halts Claude Code tool execution deterministically
process.exit(2);
}
}
// Approved
process.exit(0);
} catch (err) {
process.exit(0);
}
Summary & Enterprise Recommendations
- Use Exit Code 2: Always exit with status code
2inPreToolUsehook scripts to signal a hard security block to Claude Code. - Auto-Format on
PostToolUse: Trigger linters (eslint,prettier) inPostToolUseafter file writes to keep codebase formatting perfectly synchronized.

