๐Ÿช

Hooks (Automated Actions)

Deterministic shell commands that fire on specific events, every single time. The difference between "please remember to format" and "formatting happens automatically, always."

Conceptยท5 sectionsยท1 min read

01Why Hooks Matter

Suggestion vs guarantee

Without hooks, you depend on Claude remembering to format code, run linters, or check tests. That works most of the time, but "most of the time" is not good enough for production workflows.

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. They do not depend on the model's judgment, memory, or prompt.
โšกShell commands. Any command your terminal can run, hooks can run.
๐ŸŽฏEvent driven. They fire on specific events, every single time.

02Hook Events

EventWhen It FiresTypical Use
PreToolUsePreToolUseBefore a tool executesBlock dangerous actions, validate inputs
PostToolUsePostToolUseAfter a tool executesFormat code, run linters, run tests
SessionStartSessionStartWhen a session beginsEnvironment checks, dependency validation
UserPromptSubmitUserPromptSubmitWhen user sends a messageInput validation, context loading
StopStopWhen Claude finishes respondingSummary generation, notifications
PreCompactPreCompactBefore conversation compressionSave important context to files
SessionEndSessionEndWhen session endsCleanup, final notifications
The two most commonly used events are PreToolUse (to block actions) and PostToolUse (to react to actions). Start with these two and add others as needed.

03How to Create a Hook

Hooks live in ~/.claude/settings.json (for global hooks) or in your project's .claude/settings.json (for project specific hooks).

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "prettier --write $CLAUDE_FILE_PATH"
      }
    ]
  }
}
๐ŸŽฏmatcher filters which tool triggers the hook. Use pipe (|) to match multiple tools.
โšกcommand is any shell command. Environment variables like $CLAUDE_FILE_PATH provide context.
๐ŸŒOmit the matcher to run the hook for every tool invocation on that event.

04Common Patterns

โœจ
Auto Format on Write

PostToolUse on Write|Edit: <code>prettier --write $CLAUDE_FILE_PATH</code>. Every file Claude writes gets formatted automatically.

๐Ÿ”
ESLint After Changes

PostToolUse on Write|Edit: <code>eslint --fix $CLAUDE_FILE_PATH 2>/dev/null || true</code>. Catch issues immediately.

๐Ÿ›ก๏ธ
Block Accidental Git Push

PreToolUse on Bash: check if command contains <code>git push</code> and exit 1 to block it. Safety first.

๐Ÿฅ
Environment Health Check

SessionStart: <code>node --version && npm --version && echo 'OK'</code>. Verify dependencies before starting work.

๐Ÿ””
Notification on Complete

Stop: send a macOS notification when Claude finishes responding. Never miss a completed task.

๐Ÿ“œ
Hook Scripts

For complex logic, point your hook at a script in <code>~/.claude/hooks/</code>. Dispatch on file extension, run different formatters per language.

05Best Practices

Step 1: Keep hooks fast

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.

Step 2: Use matchers to target specific tools

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.

Step 3: Test hooks in isolation

Run the command manually before adding it to your config. Make sure it handles edge cases: missing files, wrong file types, no internet.

Step 4: Do not over hook

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.

Step 5: Use || true for non critical hooks

If a linter warning should not block Claude's workflow, append <code>|| true</code> so the hook always exits successfully.

Step 6: Store complex logic in scripts

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.