Claude Code

Claude Code Hooks: Automate Your Dev Workflow

9 min read

Most of what you do with Claude Code is a conversation: you ask, it edits, you review. But some things should never depend on the model remembering to do them. You always want your files formatted after an edit. You never want a stray rm -rf to reach your shell. You want a desktop ping the moment a long task finishes so you can stop staring at the terminal. Hooks are how you turn those wishes into guarantees.

A hook is a shell command that Claude Code runs automatically at a defined point in its lifecycle. Because hooks are executed by the harness and not generated by the model, they are deterministic: they fire every single time, regardless of how the conversation is going. This guide covers what hooks are, the events you can attach them to, how the configuration works, and the practical automations that make them worth setting up. When you want ready-made examples, the Claude Code hooks catalog on ToolZip has curated, installable configurations.

What Are Claude Code Hooks?

Instructions in a CLAUDE.md file are suggestions. The model reads them and usually follows them, but "usually" is not a policy you can build a workflow on. Hooks are different. They are configuration, not prompting, and the harness enforces them.

Concretely, a hook is a command line that Claude Code runs when a specific event occurs. The command receives structured JSON on standard input describing what just happened, and it communicates back through its exit code:

  • Exit code 0 means success. The workflow continues as normal.
  • Exit code 2 is a blocking error. Claude Code reads whatever the hook printed to stderr and treats it as a reason to stop or change course. This is the mechanism that lets a hook veto a tool call.
  • Any other non-zero code is a non-blocking error: it is surfaced to you but does not halt the action.

Because the hook is an ordinary shell command, it can be anything your machine can run: a one-line jq filter, a Prettier invocation, a Python script, or a call to a notification tool. That flexibility is the whole point. Hooks sit alongside settings, slash commands, and subagents as one of the core ways to shape how Claude Code behaves in your project.

The Hook Lifecycle Events

Hooks attach to named events. Knowing which event fires when is the key to designing an automation that does what you expect. The events you will use most often are:

  • PreToolUse — runs before Claude Code executes a tool such as Bash, Edit, or Write. This is your gate: block the action with exit code 2, or let it proceed.
  • PostToolUse — runs after a tool completes successfully. Ideal for reacting to a change, such as formatting a file that was just written.
  • UserPromptSubmit — runs when you submit a message, before the model processes it. Useful for injecting context or validating input.
  • Notification — runs when Claude Code sends a notification, for example when it needs permission or has gone idle waiting on you.
  • Stop — runs when the main agent finishes its response. A natural place to trigger a build, a test run, or an alert.
  • SubagentStop — runs when a subagent spawned by the Task tool finishes.
  • PreCompact — runs before Claude Code compacts the conversation to save context.
  • SessionStart and SessionEnd — run when a session begins or resumes, and when it ends.

For tool-related events, a matcher narrows which tools trigger the hook, so a formatter can watch only Edit and Write while a safety check watches only Bash.

How Hook Configuration Works

Hooks live in your settings JSON — .claude/settings.json for project-wide rules committed to the repo, or ~/.claude/settings.json for personal defaults across every project. The structure groups hooks by event, then by matcher. Here is a hook that runs Prettier on any file Claude edits or writes:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

Read it from the inside out. The event is PostToolUse. The matcher is a regular expression that matches the Edit and Write tool names. When either runs, the command executes: it reads the event JSON on stdin, uses jq to pull out the path of the file that changed, and pipes that path into Prettier. Nothing about this depends on the model choosing to format — it happens by construction.

A few details that save debugging time later:

  • The matcher is a regex, so "Edit|Write" matches either tool and "*" (or an empty matcher) matches all of them.
  • The JSON on stdin includes fields like tool_name and tool_input. For a Bash call, tool_input.command holds the exact command string, which is what safety hooks inspect.
  • Claude Code exposes the project root as the $CLAUDE_PROJECT_DIR environment variable, which is handy when your hook calls a script checked into the repo.

Practical Use Cases

The events and the exit-code contract combine into a small set of automations that most teams reach for first.

Lint and format on save

Attach a formatter or linter to PostToolUse matching Edit and Write. Every file Claude touches comes back formatted to your standard, so diffs stay clean and review noise disappears. A ready-made version lives at formatting/prettier-on-save in the catalog.

Block dangerous commands

Attach a validator to PreToolUse matching Bash. The hook reads the proposed command, checks it against a denylist — rm -rf on protected paths, force pushes to main, destructive database calls — and exits with code 2 to veto anything that matches, printing an explanation to stderr so the model understands why. This is a guardrail the model cannot talk its way past. See security/block-dangerous-commands for a starting point.

Desktop and Slack notifications

Attach a notifier to the Stop or Notification event so you get pinged when Claude finishes a long run or needs your input. Instead of babysitting the terminal, you find out the moment your attention is actually required. The notifications/desktop-notify hook wires this up in one line.

Run tests automatically

Attach your test command to PostToolUse or Stop so the suite runs after Claude changes source files. Fast feedback catches a broken edit while the context is still fresh, and the failing output can flow straight back into the conversation. Browse testing/run-tests-on-save for an example.

Installing a Hook with npx

You can write hook JSON by hand, but the fastest path is to install a curated one. ToolZip catalogs each hook with a copy-paste command that merges the configuration into your settings for you. The install format uses the --hook flag with a category/name path:

# Install a curated hook into your project
npx claude-code-templates@latest --hook="security/block-dangerous-commands" --yes

Swap the path for any hook you find in the hooks hub — for example --hook="formatting/prettier-on-save" or --hook="notifications/desktop-notify". The --yes flag skips the confirmation prompt for a fully non-interactive install, which is convenient inside a setup script. After installing, open your .claude/settings.json to confirm the hook landed under the event you expected, then trigger the matching action to watch it fire.

Best Practices

Hooks run real commands on your machine with your permissions, so a little discipline keeps them safe and predictable:

  • Read a hook before you install it. Because hooks execute shell commands automatically, treat third-party ones the way you would any script — review what the command does first.
  • Keep hooks fast. They run inline in the workflow. A slow hook on PostToolUse adds latency to every edit, so push heavy work to Stop where it runs once.
  • Scope with matchers. A precise matcher avoids running a formatter on a Bash call or a command validator on a file read.
  • Fail loudly, block deliberately. Reserve exit code 2 for genuine guardrails. Overusing it turns helpful automation into a wall Claude keeps hitting.
  • Commit project hooks. Putting shared rules in .claude/settings.json and checking them in gives your whole team the same guardrails without extra setup.

Hooks are the quiet layer that makes Claude Code feel like it fits your project rather than the other way around. Start with one — a formatter on save is the easiest win — then layer in a safety gate and a notification as you learn where the friction is. When you are ready to expand, the hooks catalog and the broader Claude Code component library on ToolZip give you tested building blocks to install in a single command.

Browse Claude Code Hooks

Shell commands that fire on Claude Code lifecycle events for automation.

Open catalog →

Frequently Asked Questions

What is a Claude Code hook?
A hook is a shell command that Claude Code runs automatically at a specific lifecycle event, such as before a tool runs or after an edit. Because the harness executes it rather than the model generating it, a hook fires deterministically every time, making it ideal for guardrails and automation that must not be skipped.
What lifecycle events can I attach hooks to?
The main events are PreToolUse and PostToolUse (before and after a tool runs), UserPromptSubmit, Notification, Stop and SubagentStop (when the main agent or a subagent finishes), PreCompact (before context compaction), and SessionStart and SessionEnd. Tool events also accept a matcher so a hook only fires for specific tools like Bash, Edit, or Write.
How does a hook block a dangerous command?
Attach a validator to the PreToolUse event with a matcher for Bash. The hook reads the proposed command from the JSON on stdin, checks it against your rules, and exits with code 2 to veto it. Claude Code reads the hook's stderr as the reason and does not run the command, so it is a guardrail the model cannot bypass.
Where do I put hook configuration?
Hooks live in your settings JSON under a hooks key, grouped by event and then by matcher. Use .claude/settings.json in the repo for rules shared with your team, or ~/.claude/settings.json for personal defaults across all projects. Each hook has a type of command and a command string that Claude Code runs.
How do I install a hook without writing JSON by hand?
Use the ToolZip catalog install command: npx claude-code-templates@latest --hook="category/name" --yes. It merges the curated configuration into your settings.json for you. Replace category/name with any hook from the /claude-code/hooks hub, such as security/block-dangerous-commands or formatting/prettier-on-save.
Are hooks safe to use?
Hooks run real shell commands with your permissions, so review any hook before installing it, just as you would any script. Keep hooks fast since they run inline, scope them with precise matchers, and reserve blocking exit code 2 for genuine guardrails. Committing project hooks to the repo gives your whole team consistent, reviewed automation.