Claude Code hooks: deterministic control over AI workflows
4m read time

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.

A hook in Claude Code is a local shell command Claude Code runs automatically at a fixed point in its own lifecycle, configured in .claude/settings.json instead of asked for in a prompt. That is the whole difference: a hook is guaranteed to run, where an instruction in a prompt is followed probabilistically.

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, just a loop rather than 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?

That guarantee is the whole point, and it is exactly what a prompt instruction cannot give you. Hooks are deterministic.

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).

Claude Code has added events since this guide went up, and one of the newer ones changes what you see on screen rather than what Claude reads. A plugin that timestamps every message uses both kinds at once: one hook that draws on your terminal and never reaches Claude, and one that tells Claude the time and never draws anything.

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. How that block fits alongside modes, allow rules and the sandbox is covered in the Claude Code permissions guide.

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. Now that Claude Code's defaults are shifting toward autonomy, those guarantees are no longer a luxury.

If you're not sure whether something should be a hook or something else, check the decision tree for skills, subagents, and hooks. Configure them once, check them into your repo, and let your team inherit a safer, more reliable AI coding environment.

(3 of 36)
01My Claude Code setup: status line, plugins and terminal02Superpowers: teaching Claude Code to think before it types03Claude Code hooks: deterministic control over AI workflows04The CLAUDE.md file: give your AI permanent memory05Stop asking your agent nicely06What's new in Claude Code: notes from the London talk07The best number in Opus 4.8 isn't a benchmark08Stale memory is worse than no memory09The agent is just a loop10Build an MCP server, then ask whether it should exist11Skill, subagent, hook, or slash command? Pick the right one12Log in to MCP servers from your shell13How to give Claude safe access to your SQL database14The day 'default' became 'Manual'15Claude Code skills: how to write one that works16How to write a proper Claude Code subagent17Claude Code permissions: the guide I wish the docs were18Sandboxing Claude Code: put your agent in a box that holds19Prompt injection defense for developers who ship agents20Best Claude model for coding: which one for which task21Refactoring legacy code with a coding agent: start with characterization tests22MCP server authentication: OAuth, scopes and rate limits23Opus 5 is here and your effort settings just expired24AI agent incident response: what to do when your coding agent goes wrong25Claude Code /doctor: what it checks and what changed26Claude Code context management: when to /clear and when to /compact27Git worktrees for parallel coding agents: what they isolate and what they share28Claude Code plan mode: decide before the agent writes29Debugging with a coding agent: give it the search, keep the hypothesis30Claude Code checkpoints and /rewind: how to undo an agent's changes31Claude Code cross-session messaging: how to make your sessions talk to each other32Audit logging for AI agents: what Claude Code records and what deserves a human33Sharing Claude Code config across a team: what a repo can and cannot enforce34Your Claude Code session has no clock35Claude Code in a large codebase: scoping an agent to the part that matters36Recovering a Claude Code session your picker will not show you