Claude Code hooks: settings hooks and mod hooks

A hook is code Claude Code runs when something happens: you send a prompt, Claude is about to run a tool, a session ends. Settings hooks are commands you list in a settings file. Mod hooks are JavaScript or TypeScript functions that a plugin loads into Claude Code.

Updated

Settings hooks

You list settings hooks in one of these files:

  • ~/.claude/settings.json for all your projects;
  • .claude/settings.json for one project, committed so your team gets them too;
  • .claude/settings.local.json for one project, only for you.

A plugin can ship them as well, in its hooks/hooks.json. A hook can run a shell command, send an HTTP request, call an MCP tool, or ask a model for a decision. A command hook gets the event as JSON on stdin and answers with its exit code and output.

The events you'll use most

EventWhen it runs
PreToolUseBefore a tool call runs. Block or change it here.
PostToolUseAfter a tool call succeeds. Run a formatter, log the change.
UserPromptSubmitWhen you send a prompt, before Claude reads it.
NotificationWhen Claude Code sends a notification, for example to ask for permission.
StopWhen Claude finishes its reply.
SessionStart, SessionEndWhen a session starts or resumes, and when it ends.
PreCompactBefore Claude Code compacts the context.

There are more than 30 events in all, from subagents to worktrees. The hooks reference lists every one.

Block a dangerous command with PreToolUse

This script stops any shell command containing rm -rf. Exit code 2 tells Claude Code to block the call and show your message to Claude:

~/.claude/hooks/block-rm.sh
#!/bin/bash
cmd=$(jq -r '.tool_input.command')
if echo "$cmd" | grep -q 'rm -rf'; then
  echo "rm -rf is blocked" >&2
  exit 2
fi

Make it executable with chmod +x, then register it for the Bash tool:

settings.json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{ "type": "command", "command": "~/.claude/hooks/block-rm.sh" }]
    }]
  }
}

When the script exits 0 and prints nothing, Claude Code carries on and asks for permission as usual. A hook can refuse a call, but keeping quiet doesn't approve one.

Mod hooks

A mod is a plugin with a hooks module: a JavaScript or TypeScript file that Claude Code loads into its own process. Its hooks are plain functions. Each one gets an event such as tool.call or ui.render, and can let it through, change it, or handle it so the usual behavior never runs.

hooks/register.js
export function register(on) {
  on('tool.call', async ($, e, next) => {
    // look at the call, then let it run
    return next(e)
  })
}

Running inside Claude Code lets a mod do what a settings hook can't. It can hold a risky command while it asks you about it, draw a pane next to the conversation, or add a slash command that answers at once without a Claude turn. Its hooks also share variables, so one hook can count something and another can show the count.

Which kind should you write?

  • A settings hook, if you want to block, allow or log something with a script, in any language.
  • A mod, if you want something on screen, or want to rewrite a prompt or a tool call. Or find one that already exists.

These guardrail mods have the most stars. They watch tool calls before they run:

All guardrails mods