Suggestion vs guarantee
Hooks make it mechanical. They always run. They never forget. This is the difference between "please remember to format" and "formatting happens automatically, always." One is a suggestion. The other is a guarantee.
Deterministic shell commands that fire on specific events, every single time. The difference between "please remember to format" and "formatting happens automatically, always."
| Event | When It Fires | Typical Use | |
|---|---|---|---|
| PreToolUse | PreToolUse | Before a tool executes | Block dangerous actions, validate inputs |
| PostToolUse | PostToolUse | After a tool executes | Format code, run linters, run tests |
| SessionStart | SessionStart | When a session begins | Environment checks, dependency validation |
| UserPromptSubmit | UserPromptSubmit | When user sends a message | Input validation, context loading |
| Stop | Stop | When Claude finishes responding | Summary generation, notifications |
| PreCompact | PreCompact | Before conversation compression | Save important context to files |
| SessionEnd | SessionEnd | When session ends | Cleanup, final notifications |
Hooks live in ~/.claude/settings.json (for global hooks) or in your project's .claude/settings.json (for project specific hooks).
|) to match multiple tools.$CLAUDE_FILE_PATH provide context.PostToolUse on Write|Edit: <code>prettier --write $CLAUDE_FILE_PATH</code>. Every file Claude writes gets formatted automatically.
PostToolUse on Write|Edit: <code>eslint --fix $CLAUDE_FILE_PATH 2>/dev/null || true</code>. Catch issues immediately.
PreToolUse on Bash: check if command contains <code>git push</code> and exit 1 to block it. Safety first.
SessionStart: <code>node --version && npm --version && echo 'OK'</code>. Verify dependencies before starting work.
Stop: send a macOS notification when Claude finishes responding. Never miss a completed task.
For complex logic, point your hook at a script in <code>~/.claude/hooks/</code>. Dispatch on file extension, run different formatters per language.
Hooks run synchronously, meaning Claude waits for them to finish. A hook that takes 10 seconds adds 10 seconds to every matched tool call. Aim for sub second execution.
A PostToolUse hook without a matcher runs after every single tool call. That adds up fast. Always scope your hooks to the tools that actually need them.
Run the command manually before adding it to your config. Make sure it handles edge cases: missing files, wrong file types, no internet.
Three or four well chosen hooks are better than fifteen that slow everything down. Start with auto formatting and one safety check, then add more as needed.
If a linter warning should not block Claude's workflow, append <code>|| true</code> so the hook always exits successfully.
If your hook command is longer than one line, move it to a script file in <code>~/.claude/hooks/</code>. Inline commands get hard to read and harder to debug.