LESSONS
Introducing Hooks
PreToolUse, PostToolUse, and hook concepts
What are Hooks?
Hooks allow you to run commands before or after Claude Code does something. You can also optionally block Claude's action.
Runs before a tool executes. Can prevent the tool call from running.
Runs after a tool has executed. Cannot block, but can provide feedback.
Example Use Cases
- • Run a code formatter after Claude edits a file
- • Stop Claude from reading sensitive files like .env
- • Check for TODO comments and log them automatically
- • Run tests automatically after a file is changed
- • Block file edits that violate naming conventions
- • Prevent usage of deprecated functions
Available Tools to Monitor
Read # Read a file
Edit # Edit an existing file
MultiEdit # Multiple edits in one operation
Write # Create a file and write to it
Bash # Execute a shell command
Glob # Find files/folders by pattern
Grep # Search for content in files
Task # Create a sub-agent
Defining Hooks
Configuration, matchers, and exit codes
Building a Hook: 4 Steps
PreToolUse can prevent actions; PostToolUse runs after
Use matchers like "Read|Grep" or "*" for all tools
Your command receives JSON data via stdin
Exit code determines if action is allowed or blocked
Exit Codes
Allow the tool call to proceed
Block the tool call (PreToolUse only). Stderr is sent to Claude as feedback.
Configuration File
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Grep",
"hooks": [
{
"type": "command",
"command": "node /path/to/hook.js"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write"
}
]
}
]
}
}
Implementing Hooks
Practical examples: .env protection, TypeScript checking
Example 1: Protect .env Files
This hook prevents Claude from reading sensitive files like .env that contain API keys and secrets.
async function main() {
const chunks = [];
for await (const chunk of process.stdin) {
chunks.push(chunk);
}
const toolArgs = JSON.parse(Buffer.concat(chunks).toString());
// Get the file path Claude is trying to read
const readPath = toolArgs.tool_input?.file_path ||
toolArgs.tool_input?.path || "";
// Block .env files
if (readPath.includes(".env")) {
console.error("Access denied: .env files are protected");
process.exit(2); // Block the read
}
process.exit(0); // Allow other reads
}
main();
Example 2: TypeScript Type Checking
Run the TypeScript compiler after every edit to catch type errors immediately.
import * as ts from "typescript";
async function main() {
const input = await readInput();
const file = input.tool_response?.filePath ||
input.tool_input?.file_path;
// Only check TypeScript files
if (!file || !/\.(ts|tsx)$/.test(file)) {
process.exit(0);
}
const errors = runTypeCheck("./tsconfig.json");
if (errors) {
console.error(errors);
process.exit(2); // Report errors to Claude
}
}
main();
- • Always validate and sanitize inputs
- • Use absolute paths for scripts (prevents path interception)
- • Quote shell variables: "$VAR" not $VAR
- • Block path traversal: check for ".." in paths
Other Hook Types
Notification, Stop, UserPromptSubmit, and more
Beyond PreToolUse and PostToolUse
Claude Code provides several additional hook types for different lifecycle events:
Runs when Claude needs permission or after 60 seconds of idle time
Runs when Claude has finished responding
Runs when a subagent (Task) has finished
Runs before a compact operation (manual or automatic)
Runs when user submits a prompt, before Claude processes it
Runs when starting/resuming or ending a session
Create a helper hook that logs all inputs to a file with jq . > hook-log.json.
This helps you understand the exact structure of data your command receives.
Practical Example: Auto-Format on Save
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_response.filePath // .tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
Pro Tips & Plugins
Boris Cherny's hook tips and official plugins
Practical tips shared by Boris Cherny from Anthropic Developer Relations.
Tip 9: Code Formatting with PostToolUse
Automatically runs Prettier every time Claude modifies a file, keeping code style consistent.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_response.filePath // .tool_input.file_path' | xargs npx prettier --write 2>/dev/null || true"
}
]
}
]
}
}
Tip 10: Permission Management with /permissions
Use the /permissions command to check and manage the current session's permissions.
View currently allowed tools and paths
/permissions
Approve permissions for specific operations
/allowed-tools
Tip 12: ralph-loop Plugin (Long-running Tasks)
Implements a self-referencing loop that automatically restarts Claude after task completion. Can be used with background agents to automate long-running tasks.
# Re-invoke Claude from the Stop hook
{
"hooks": {
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "claude --continue"
}
]
}
]
}
}
Official Plugins
hookify
A plugin for easily creating and managing custom hooks.
# Install
claude plugins add github:anthropics/hookify
# Run hook creation wizard
/hookify:create
ralph-loop
A plugin that implements self-referencing loop cycles. Automates long-running tasks in the background.
# Install
claude plugins add github:anthropics/ralph-loop
# Start long-running task
/ralph-loop:start "Perform large-scale refactoring"
TERMINOLOGY
PreToolUse Hook
A hook triggered before a tool executes. Returning exit code 2 can block the tool from running. Used for blocking .env file reads, preventing dangerous command execution, etc.
PostToolUse Hook
A hook triggered after a tool executes. Cannot block actions, but can provide feedback to Claude. Used for code formatting, type checking, logging, etc.
Exit Code
The exit code returned by a hook script.
0 = Allow tool execution
2 = Block tool execution (PreToolUse only)
Messages printed to stderr are passed to Claude as feedback.
Background Agent
A mode where Claude runs in the background and sends a notification when the task is complete.
Start a long task and check back when it finishes.
Activated with run_in_background: true.
Agent Stop Hook
A hook triggered when Claude finishes responding. Used for automatic notifications, logging, or implementing self-referencing loops (ralph-loop).
KNOWLEDGE CHECK
Ready for the Challenge?
Build automated workflows with hooks in a real project.