Skip to content

Claude Code Hooks: Automate Formatting, Guardrails, and Logging

Use Claude Code hooks to run shell commands on agent events — auto-format after edits, block risky commands, log activity. The events, config, and working examples.

MGMCSA Guru Team February 22, 2026 7 min read
A Claude Code settings file showing a PostToolUse hook that runs a formatter after edits

There’s a category of things you want to happen every time, no matter what the agent decides — code gets formatted after an edit, a destructive command gets blocked, an action gets logged. Relying on the model to remember those is a losing game. Hooks are how you make them deterministic: shell commands that Claude Code runs automatically when a specific event fires.

This guide covers the events you can hook, where the config lives, and a set of working examples — auto-formatting, a guardrail that blocks risky commands, and activity logging. Hooks are one of the most underused features in Claude Code, and they’re the difference between an agent you have to babysit and one you can trust with a policy layer.

If you’re mapping out Claude Code’s features generally, this fits into the power-user guide alongside skills and subagents.

What a hook is

A hook ties an event to a command. When the event happens, Claude Code runs your command, passing details about what’s going on as JSON on standard input. Your command can read that, do something, and — for some events — influence what happens next.

The events you’ll use most:

Common hook events

PreToolUse Before a tool runs — can block it
PostToolUse After a tool runs — react to the result
UserPromptSubmit When you send a message
SessionStart When a session begins
Stop When the agent finishes responding

PreToolUse is the one that makes hooks powerful, because it runs before the action and can refuse it. The rest are about reacting to things that already happened.

Where hooks live

Hooks go in your settings JSON. Project hooks belong in .claude/settings.json (committed, shared with the team); personal hooks go in your user settings under ~/.claude/. The structure groups hooks by event, with an optional matcher that limits which tools the hook applies to.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "npx prettier --write \"$CLAUDE_FILE_PATH\"" }
        ]
      }
    ]
  }
}

The matcher is matched against tool names, so this hook only fires after Edit or Write, not after every tool. The exact field names and the data passed on stdin can change between releases, so cross-check the hooks documentation when you build one.

Example 1: format code after every edit

The classic hook. Every time Claude edits or writes a file, run the formatter so you never get an unformatted diff:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "npx prettier --write \"$CLAUDE_FILE_PATH\"" }
        ]
      }
    ]
  }
}

Swap prettier for gofmt, black, rustfmt, or whatever your project uses. The win is that formatting stops being something the agent has to remember — it just happens, every time, on the file that changed.

Example 2: block a dangerous command

This is where PreToolUse earns its place. A hook can inspect a Bash command before it runs and refuse it. Point a small script at the tool call and exit non-zero to block:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": ".claude/hooks/block-dangerous.sh" }
        ]
      }
    ]
  }
}
#!/usr/bin/env bash
# Reads the tool call JSON on stdin, blocks rm -rf and force pushes.
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // empty')

if echo "$cmd" | grep -Eq 'rm -rf|git push --force|DROP TABLE'; then
  echo "Blocked: refused a destructive command." >&2
  exit 2
fi
exit 0

A non-zero exit stops the tool call. This is a guardrail, not airtight security — a determined agent or a cleverly worded command can still slip through patterns you didn’t anticipate — but it catches the obvious disasters before they run.

Example 3: log what the agent does

For an audit trail, append every Bash command the agent runs to a log file. Useful when you’re running Claude Code unattended or want to review a session afterward:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt" }
        ]
      }
    ]
  }
}

Every command the agent proposes gets recorded with no effort on your part. Pair it with a SessionStart hook that writes a timestamp and you have a readable history of what happened and when.

Example 4: load context when a session starts

A SessionStart hook can inject fresh context — the current git branch, open tickets, recent deploy status — so the agent begins each session informed:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          { "type": "command", "command": "git status --short && git log --oneline -5" }
        ]
      }
    ]
  }
}

The output becomes available to the agent at the start of the session. It’s a lightweight way to keep Claude oriented without you typing the same status summary each time.

Windows and WSL notes

Hooks run shell commands, so the environment matters more here than with most Claude Code features.

  • On native Windows, commands run through your Windows shell. Tools like jq and grep aren’t there by default, so either install them or keep hooks to commands Windows has.
  • On WSL, hooks run as normal Linux commands, so the Unix tooling the examples use is available. For anything beyond a one-line formatter call, WSL is the smoother choice.
  • Keep paths consistent with the environment — Windows paths in Windows, Linux paths in WSL. If you haven’t set up WSL, the install guide covers it.

When to use a hook vs a skill

Both shape behavior, but they’re not interchangeable.

Hook or skill?

Use a hook when It must happen every time, deterministically — formatting, guardrails, logging
Use a skill when You want Claude to follow a procedure or convention when relevant
Hook strength Runs regardless of what the model decides
Skill strength Carries nuance and judgment the model applies in context

A hook is a hard rule; a skill is guidance. Format-on-save is a hook. “Follow our commit message style” is a skill. The full comparison across extension points is in skills vs MCP vs subagents.

Setting up your first hooks

  • Add a PostToolUse formatter hook matched to Edit|Write
  • Add a PreToolUse guardrail that blocks destructive commands
  • Test each hook script by hand before trusting it
  • Commit project hooks in .claude/settings.json for the team
  • Run jq-based hooks in WSL on Windows machines

Wrapping up

Hooks turn the things you want to happen every time into things that do. Start with a formatter on PostToolUse and a guardrail on PreToolUse — those two alone make Claude Code feel more trustworthy. Add logging and a SessionStart context loader as you find the manual steps you keep repeating.

For ready-made patterns worth copying, see the best Claude Code hook examples, and fit hooks into the bigger picture with the power-user guide.

Frequently asked questions

What are hooks in Claude Code?

Hooks are shell commands that Claude Code runs automatically when certain events happen — before or after a tool runs, when you submit a prompt, when a session starts, or when the agent stops. They let you enforce behavior deterministically instead of hoping the model remembers to do it.

Where are hooks configured?

In your settings JSON — typically .claude/settings.json in the project, or the user settings file in ~/.claude/. Each hook specifies an event, an optional matcher for which tools it applies to, and the command to run.

What events can trigger a hook?

Common ones include PreToolUse (before a tool runs), PostToolUse (after), UserPromptSubmit (when you send a message), SessionStart, and Stop (when the agent finishes). PreToolUse can block an action, which is what makes hooks useful for guardrails.

Can a hook block Claude from doing something?

Yes. A PreToolUse hook can return a non-zero exit or a block response to stop a tool call before it runs. That's how people prevent things like deleting files or running a command against production — the hook inspects the call and refuses it.

Do hooks work on Windows?

Yes. Hooks run shell commands, so on native Windows they run through your shell and on WSL they run as Linux commands. Keep the commands portable, or use WSL for consistency if your hooks rely on Unix tools like jq.

How is a hook different from a skill?

A skill is guidance the model may follow; a hook is a command that always runs on its event, regardless of what the model decides. Use a skill for conventions and a hook for hard automation and guardrails that must not be skipped.

Sources & further reading

Official vendor documentation referenced while writing this guide.

MG

MCSA Guru Team

IT & Systems Administration

We are working IT pros and system administrators who spend our days in Windows Server, Microsoft 365, and the wider Microsoft stack. MCSA Guru is where we write down the fixes and walkthroughs we wish we had found the first time.

MCSA Guru provides independent, educational IT guidance. Microsoft, Windows, Windows Server, Microsoft 365, Exchange, and Microsoft Teams are trademarks of Microsoft Corporation; Docker is a trademark of Docker, Inc. MCSA Guru is not affiliated with or endorsed by Microsoft or Docker. Always test changes in a safe environment before applying them in production.

Related guides

Fixing something right now?

Jump straight into the guide library or search for the exact error or task you are dealing with.