Claude Code Hooks: Every Event and the Hooks Real Plugins Ship
Mert DüzgünPublished 7 minute read
Claude Code hooks are commands that Claude Code runs at fixed points in a session: before a tool call, after a file edit, when a session starts, when Claude stops. On November 14, 2026 we scanned the 2,195 most-starred GitHub repos behind CCM listings (out of 6,321) and found 1,076 hook config files in 477 of them. The numbers below come from that scan.
What Claude Code hooks are
The hooks guide calls hooks "user-defined shell commands" that give you "deterministic control: certain actions always happen rather than relying on the LLM to choose to run them."
Command hooks read the event as JSON on stdin and answer with an exit code and, if they want, JSON on stdout.
Where hooks live
Per the hooks reference, you can define hooks in:
~/.claude/settings.json, for all your projects.claude/settings.json, for one project (you can commit it).claude/settings.local.json, for one project, gitignored when Claude Code writes to it- managed policy settings, set by an admin for an organization
- a plugin's
hooks/hooks.json, active while the plugin is enabled - skill frontmatter, from when the skill is invoked to the end of the session
- subagent frontmatter, while that subagent runs
Hooks from all levels merge. To see every configured hook and where it comes from:
-
Inside Claude Code, type:
/hooks
Two environment variables matter most. CLAUDE_PROJECT_DIR is the project root where the session started. CLAUDE_PLUGIN_ROOT is the plugin's install directory, which plugins use to find their own scripts. In our scan, 1,759 hook commands use ${CLAUDE_PLUGIN_ROOT} and 432 use $CLAUDE_PROJECT_DIR.
Claude Code hooks events list
The reference lists these events (as of November 15, 2026):
- Session:
SessionStart,Setup,SessionEnd,InstructionsLoaded,ConfigChange,CwdChanged,DirectoryAdded,FileChanged - Prompts:
UserPromptSubmit,UserPromptExpansion,MessageDisplay,Notification - Tools:
PreToolUse,PermissionRequest,PermissionDenied,PostToolUse,PostToolUseFailure,PostToolBatch - Turns and agents:
Stop,StopFailure,SubagentStart,SubagentStop,TaskCreated,TaskCompleted,TeammateIdle - Context and models:
PreCompact,PostCompact,PreModelSwitch,PostModelSwitch - Worktrees:
WorktreeCreate,WorktreeRemove - Model Context Protocol (MCP) input:
Elicitation,ElicitationResult
That is 33 events (the grouping is ours).
Matchers
A matcher filters which hooks run for an event. For tool events it matches the tool name. Edit|Write is an exact list. Other characters make it an unanchored JavaScript regex, like mcp__memory__.*. "*", an empty string or no matcher matches everything. SessionStart matches startup, resume, clear, compact or fork. Some events take no matcher at all, including UserPromptSubmit and Stop.
The most common matchers in our scan: PreToolUse on Bash in 143 repos, PostToolUse on Write|Edit (any order, with or without MultiEdit) in 132, PostToolUse on Bash in 59, and SessionStart on startup|resume|clear|compact in 39.
Handler types
The docs list five handler types. command runs a shell command. http posts the event to a URL. mcp_tool calls a tool on a configured MCP server. prompt asks a model for a single-turn judgment. agent runs a multi-turn check with tool access and is marked experimental.
Real repos almost always pick command: 3,597 handlers in our scan, against 16 prompt, 6 mcp_tool and 5 agent. We also counted 19 handlers typed process, a type the hooks docs do not list. Most commands run on node (1,484), bash or sh (638) or python (423).
Exit codes and blocking
Exit 0 means success. On SessionStart, UserPromptSubmit, UserPromptExpansion and PostModelSwitch, plain text on stdout is added to Claude's context.
Exit 2 is a blocking error, and stderr becomes the reason. It blocks on PreToolUse, UserPromptSubmit, Stop, SubagentStop, PreCompact and several more. It does not block on PostToolUse, SessionStart or Notification. Any other exit code without valid JSON is a non-blocking error: the action goes ahead and the transcript shows a hook error notice.
For PreToolUse you can also exit 0 and print a decision:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}
permissionDecision can also be allow, ask or defer. When several hooks answer, the order is deny, then defer, then ask, then allow.
The hooks real plugins ship
Here are the top 10 Claude Code events by the number of repos that use them, from our November 14, 2026 scan. We left out event names from other agents' formats, such as lowercase postToolUse and BeforeTool. We also left out modules (55 repos). It is not an event: in a plugin's hooks/hooks.json it points to a mod's code.
| Event | Repos | Typical use |
|---|---|---|
| SessionStart | 304 | Load context or set up the environment |
| PreToolUse | 226 | Guard Bash commands before they run |
| PostToolUse | 224 | Format and lint files after edits |
| Stop | 175 | Run checks or save state when Claude finishes |
| UserPromptSubmit | 154 | Add context to each prompt |
| SessionEnd | 93 | Save memory or clean up (MemPalace, oh-my-openagent) |
| PreCompact | 72 | Save state before compaction (MemPalace) |
| SubagentStop | 59 | Hooked by claude-mem and mem0 |
| PostToolUseFailure | 50 | Hooked by mem0 and everything-claude-code |
| SubagentStart | 45 | Hooked by mem0 and ruflo |
Some real examples behind those rows:
- Superpowers has one hook. On
startup|clear|compact, its session-start script reads the using-superpowers SKILL.md and puts it in Claude's context. - React runs
cat .claude/instructions.mdonSessionStartoutside the compiler folder. Supabase installs packages at session start and runs a format and lint script after edits. - Bun has a pre-bash guard that returns the same JSON
denyshape shown above, plus a post-edit formatter. - tldraw blocks
yarnin Bash and tells Claude the repo uses pnpm. ItsStophook runs Prettier on changed files. - claude-mem hooks
Setup,SessionStart,UserPromptSubmit,PostToolUse,Stopand more to record and reload memory. mem0 hooks nine Claude Code events for the same kind of job, and MemPalace runs scripts onStop,SessionEndandPreCompact. - Pake uses
PostToolUseto tell Claude, throughadditionalContext, thatdist/cli.jsmust be rebuilt when files inbin/change. - claude-code-best-practice wires 30 events to a Python hooks script, which makes it a handy reference for event names.
How to add a hook
All three configs below are examples. Each goes in .claude/settings.json in your project root. If you use more than one, merge them under a single "hooks" object.
Format files after every edit
This is the formatter from the hooks guide. It runs Prettier on each file Claude writes or edits (it needs jq and Node).
-
In
.claude/settings.json, add:{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "f=$(jq -r '.tool_input.file_path') && npx prettier --write --ignore-unknown \"$f\"" } ] } ] } }
Block a dangerous Bash command
-
Save this script as
.claude/hooks/block-rm-rf.sh:#!/bin/bash COMMAND=$(jq -r '.tool_input.command // empty') if echo "$COMMAND" | grep -q 'rm -rf'; then jq -n '{hookSpecificOutput: {hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: "Destructive command blocked by hook"}}' fi exit 0 -
In your terminal, at the project root, make it executable:
chmod +x .claude/hooks/block-rm-rf.sh -
In
.claude/settings.json, register it:{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm-rf.sh" } ] } ] } }
The simpler way also works: print a reason to stderr and exit 2. Claude sees the reason and can try another approach. A string match like this one is easy to get around, so treat it as a guard against mistakes and keep your permission deny rules in place.
Load context at session start
Plain text from a SessionStart hook goes into Claude's context. This one shows the last five commits on every start, resume, clear and compaction.
-
In
.claude/settings.json, add:{ "hooks": { "SessionStart": [ { "matcher": "startup|resume|clear|compact", "hooks": [ { "type": "command", "command": "git log --oneline -5 2>/dev/null || true" } ] } ] } }
Check that the hooks loaded
-
Inside Claude Code, type:
/hooks
Install ready-made git guards
The git-guardrails-claude-code skill sets up hooks that block git push, reset --hard, clean, branch -D and similar commands. It has about 549,000 skills.sh installs (as of November 13, 2026).
-
In your terminal, install it:
npx -y skills add mattpocock/skills --skill git-guardrails-claude-code --agent claude-code
Read hooks before you install a plugin
The hooks reference warns: "Command hooks execute shell commands with your full user permissions." When you install a plugin that ships a hooks/hooks.json, its commands run on your machine on every matching event. Open that file and the scripts it calls before you install. Ten hook commands in our scan run curl or wget directly, and scripts can reach the network too.
In an interactive session, Claude Code holds back settings-file hooks until you accept the workspace trust dialog for the folder. In -p or SDK sessions there is no dialog, so a repo's committed .claude/settings.json hooks run. Before you script claude -p over a repo you did not write, the docs suggest reviewing its .claude/ files or turning hooks off for that run.
-
In your terminal, run Claude Code with hooks off (replace
your promptwith your task):claude -p --settings '{"disableAllHooks": true}' "your prompt"
Our Claude Code security post covers more of this.
Where to start
Start with the PostToolUse formatter. It runs after the edit, so it cannot block anything, and you can check it on the next file Claude edits. Add a PreToolUse Bash guard next, since 143 repos in our scan already do. For panes or rules that rewrite tool calls, Claude Code mods register function hooks instead. To see how others combine hooks with skills, browse skills on CCM and open a repo's .claude/settings.json.
More from the blog
- Claude Code Agent Teams and Subagents: Setup and Real NumbersHow Claude Code agent teams and subagents work, how to set them up, and what 8,490 agent files from the repos behind CCM show about models and tools. 7 minute read
- Claude Code Mods: What They Are and the Best Ones to InstallThe best Claude Code mods in our catalog, how mods differ from plugins, skills and hooks, and how to install one from a Claude Code mods marketplace. 7 minute read
- Claude Code Security: Is It Safe? Three Incidents and Our ScanClaude Code security in fall 2026: three incidents, our scan of 2,195 repos behind CCM, and how to check permissions, settings.local.json and MCP servers. 8 minute read