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.jsonfor all your projects;.claude/settings.jsonfor one project, committed so your team gets them too;.claude/settings.local.jsonfor 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
| Event | When it runs |
|---|---|
PreToolUse | Before a tool call runs. Block or change it here. |
PostToolUse | After a tool call succeeds. Run a formatter, log the change. |
UserPromptSubmit | When you send a prompt, before Claude reads it. |
Notification | When Claude Code sends a notification, for example to ask for permission. |
Stop | When Claude finishes its reply. |
SessionStart, SessionEnd | When a session starts or resumes, and when it ends. |
PreCompact | Before 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:
#!/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:
{
"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.
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:
- blast-radius 0.1.0 See what a risky command would change before it runs. 249.1k
- agents-md 0.1.0 AGENTS.md as project instructions, by one option. Under claude-md-or-agents-md a project with no instruction files of its own gets its AGENTS.md files instead: a prompt.context hook asks $.fs.ancestors for every AGENTS.md and .claude/AGENTS.md from the filesystem root down to the working director... 150.7k
- ruflo-rvf 0.3.7 mod + skill RVF format for portable agent memory, session persistence, and cross-platform transfer. As a mod (ADR-445): a write guard that keeps secrets out of memory, sessions and transfers, /rvf-mod, and a status file the console shows 74.6k
- openviking-memory 0.7.11 mod + skill Long-term semantic memory for Claude Code, powered by OpenViking. Auto-recall relevant memories at session start and capture important information during conversations. 39.9k
- tool-timing-badge 0.2.0 Times every tool call and wraps the engine's ToolUse rendering with a colored duration badge from the surface's own element table, on the terminal, Desktop and mobile. Hooks tool.call and ui.render using JSX. 32.6k