Hooks: Give Claude Code Guard Rails That Actually Run
How to turn "please remember this" into automation Claude cannot skip.
Every developer who uses Claude Code long enough hits the same moment: Claude does something you keep having to undo.
It edits a sensitive file. It forgets to run a formatter. It leaves debug code behind. It tries a risky shell command. You correct it, then correct it again a week later.
That pattern is a hook candidate.
What Hooks Are
Hooks are user-defined commands, HTTP endpoints, MCP tools, prompts, or agent checks that run automatically at specific points in Claude Code's lifecycle.
They are different from instructions. A line in CLAUDE.md says:
Please run prettier after editing TypeScript.
A hook actually runs the formatter after the edit.
That is the difference between guidance and a guard rail.
When Hooks Fire
Claude Code exposes lifecycle events. The most useful ones for everyday development are:
PreToolUse: before Claude runs a tool. This can block risky actions.PostToolUse: after a tool succeeds. Good for formatters, linters, and logging.UserPromptSubmit: before Claude processes a user prompt. Good for adding context or blocking unsafe prompts.Notification: when Claude asks for permission or waits for input.Stop: when Claude finishes responding. Good for reminders or final checks.SessionStart: when a session starts or resumes. Good for loading dynamic context.PreCompact: before compaction. Good for preserving important state.
Use the event that matches the moment you care about.
Where Hooks Live
Hooks are configured in JSON settings files:
~/.claude/settings.jsonfor user-level hooks..claude/settings.jsonfor project hooks you can share with a team..claude/settings.local.jsonfor local project hooks you do not commit.- Plugin hook files for plugin-distributed workflows.
- Skill or subagent frontmatter for hooks scoped to that component.
Use /hooks to browse what is configured. The menu is primarily for inspection; durable edits belong in the settings files or in the component that owns the hook.
The Basic Shape
A hook configuration has three layers:
1. The event. 2. A matcher group. 3. One or more hook handlers.
Example: run a project script after file edits.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-style.sh"
}
]
}
]
}
}
For tool events, the matcher filters by tool name. Edit|Write matches edits and writes. Bash matches shell commands. * matches everything.
Example 1: Block Dangerous Shell Commands
Use PreToolUse when you want to stop something before it runs.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous-bash.sh"
}
]
}
]
}
}
The script receives JSON on stdin. It can inspect the attempted command and exit with the appropriate decision. A blocking hook can feed feedback back to Claude so it can choose a safer next step.
Keep this script small, explicit, and heavily tested.
Example 2: Format After Edits
Use PostToolUse for automatic cleanup.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-touched-file.sh"
}
]
}
]
}
}
This is better than repeatedly telling Claude to format files. The hook runs every time the condition matches.
Example 3: Protect Sensitive Files
If a file should almost never be changed by Claude, block it.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/protect-sensitive-files.sh"
}
]
}
]
}
}
The script can reject edits to paths such as .env, production credentials, generated lockfiles, or migration folders.
This is especially useful on teams because it moves a convention out of someone's memory and into the tool loop.
Example 4: Final Check Before Stop
Use Stop when Claude is about to finish and you want a final reminder or validation.
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/final-check.sh"
}
]
}
]
}
}
A Stop hook can remind Claude that tests have not run, that a checklist is incomplete, or that a required artifact has not been updated.
Security Rules for Hooks
Hooks run automatically in your environment. Treat them like production automation.
Follow these rules:
- Review every hook before enabling it.
- Keep hook scripts short and readable.
- Quote shell variables.
- Use absolute paths or
$CLAUDE_PROJECT_DIR. - Validate and sanitize JSON input.
- Avoid broad destructive commands.
- Test in a safe repository first.
- Prefer project-local scripts over long inline shell one-liners.
Hooks can protect you, but a careless hook can also cause damage.
Hooks vs Permissions vs Instructions
Use all three, but for different jobs.
Use instructions when Claude should generally behave a certain way:
Prefer existing service helpers before adding new abstractions.
Use permissions when you want Claude Code to ask, allow, or deny tool access at the tool-policy level.
Use hooks when an action must run, validate, log, or block at a specific point in the workflow.
The clean setup is:
CLAUDE.mdfor preferences and project context.- Permissions for baseline tool access.
- Hooks for deterministic checks.
- Skills for reusable multi-step workflows.
The Hook Habit
Any time you correct Claude Code for the same behavior twice, ask:
Should this be a hook?
If the answer is yes, automate it.
Hooks are the part of Claude Code that turns "remember our process" into "the process runs."
Sources
Part 3 of a seven-part series on using Claude Code efficiently.