·3m read time·552 words·

Claude Code Hooks: Deterministic Control Over AI Workflows

While claude.md instructions are treated as suggestions, Hooks provide deterministic guarantees. Learn how to use pre- and post-tool hooks to enforce formatting, block dangerous commands, and standardize your team's workflow.

If you've been using Claude Code for a while, you probably use a claude.md file to give the model project-specific instructions. You might tell it to "always run prettier after editing a file."

And most of the time, it will do exactly that. But sometimes... it won't. It's an AI, not a strict state machine, which means its compliance is probabilistic.

If something needs to happen every single time without fail, you don't put it in a prompt. You put it in a hook.

What are Hooks?

Hooks let you run local shell commands at specific points in Claude Code's lifecycle. The key difference between a hook and a prompt instruction is that hooks are deterministic. They are guaranteed to run.

Hooks are configured in your .claude/settings.json file. Because they are project-level configurations, you can commit them to your repository, ensuring your entire team shares the same automated workflow.

Lifecycle Events

When configuring a hook, you pick an event to trigger on. Claude Code supports several lifecycle hooks:

  • userPromptSubmit: Runs immediately when you submit a prompt, before Claude processes it.
  • preToolUse: Runs right before a tool is executed.
  • postToolUse: Runs right after a tool completes its task.
  • notification: Runs when Claude sends a notification to the user.
  • stop: Runs when Claude has finished responding and the interaction is complete.

You can optionally define a matcher to restrict the hook to specific tools (e.g., only running on edit or bash tools).

The Auto-Formatter: Post-Tool Hooks

The most common use case for hooks is enforcing code formatting. Instead of hoping Claude remembers to format the file after an edit, you can use a postToolUse hook.

By setting the matcher to edit or multi-edit, the hook fires whenever Claude modifies a file. You can configure the hook's command to check the file extension and run the appropriate formatter—Prettier for TypeScript, gofmt for Go, or Ruff for Python.

Enforcing Hard Rules: Pre-Tool Hooks

While post-tool hooks are great for cleanup, pre-tool hooks give you a mechanism for safety and compliance. Pre-tool hooks can actually block Claude from executing a tool.

When a pre-tool hook fires, it receives the tool name and its input as JSON on stdin. The hook script can then inspect the payload and make a decision:

  • Exit code 0: Proceed with the tool execution.
  • Exit code 2: Block the tool execution.

If you block the execution, whatever you print to stderr is fed back directly to Claude. This means Claude understands why it was blocked and can adjust its approach.

This is how you enforce non-negotiable rules:

  • Block writes to a production config directory.
  • Block bash commands that contain rm -rf.
  • Log all executed commands for compliance.

Environment and Execution

When writing hook scripts, rely on the CLAUDE_PROJECT_DIR environment variable. This ensures your scripts run correctly regardless of Claude's current working directory at the time the hook fires.

Store your complex hook scripts inside your repository (e.g., in a .claude/hooks/ directory) and reference them in your settings.json.

Stop Suggesting, Start Guaranteeing

Hooks give you the deterministic control that prompts simply cannot provide. Use postToolUse for formatting and logging, and use preToolUse to block dangerous operations.

Configure them once, check them into your repo, and let your team inherit a safer, more reliable AI coding environment.

// series: Claude Pro(3 of 3)