/hooks is a built-in Claude Code slash command that opens an interactive menu for reviewing your configured hooks. Hooks let you run your own shell commands (or HTTP calls, prompts, agents, and MCP tools) automatically at defined points in Claude Code's lifecycle, giving you deterministic control over formatting, linting, testing, notifications, and guardrails instead of relying on the model to choose to run them.
Note: There is no /hooks-generator command and no --auto-format, --auto-test, --post-edit, or similar flags. The real workflow is: run /hooks to inspect what is configured, then edit your settings.json to add or change hooks. This page documents that real, documented behavior.
The /hooks command
Type /hooks in an interactive Claude Code session to open a menu that shows:
- Every configured hook event and how many hooks are attached to it
- The matcher groups under each event
- Full handler details (the command, URL, or prompt that runs)
- The source of each hook (User, Project, Local, Plugin, Session, or Built-in)
The /hooks menu is a read-only browser. To add, change, or remove hooks, edit the JSON settings files directly (see below).
Where hooks are configured
Hooks are defined under a top-level hooks key in a settings file. Locations, in order of precedence:
| Location |
Scope |
Shareable |
~/.claude/settings.json |
All your projects |
No |
.claude/settings.json |
One project |
Yes (commit to repo) |
.claude/settings.local.json |
One project |
No (gitignored) |
| Managed policy settings |
Organization-wide |
Yes |
Configuration structure
The hooks object nests three levels: event name, a list of matcher groups, and a list of handlers per group.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
}
]
}
]
}
}
Hook events
Documented lifecycle events include (non-exhaustive):
- Session:
SessionStart, SessionEnd, Setup
- Per turn:
UserPromptSubmit, Stop, Notification
- Per tool call:
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest
- Other:
SubagentStart, SubagentStop, PreCompact, PostCompact
Matchers
The matcher field filters when a hook fires. For tool events it matches the tool name:
"*", "", or omitted: match everything
Edit|Write: a |-separated list of tool names
mcp__memory__.*: a JavaScript regex (used here to match all tools from an MCP server)
For SessionStart the matcher is the session source (startup, resume, clear, compact); other events match against their own values (see the reference).
Handler fields
A command handler runs a shell command. Common fields:
type (required): "command", "http", "mcp_tool", "prompt", or "agent"
command: the shell command or executable path
timeout: seconds before the hook is canceled
async: run in the background without blocking
How a command hook receives data
Command hooks receive a JSON object on stdin describing the event. They do not use $FILE_PATH-style substitution variables. Available top-level input fields include session_id, transcript_path, cwd, permission_mode, and hook_event_name, plus event-specific fields such as the tool input. Parse stdin (for example with jq) to read the file path or command being acted on.
The hook's behavior is driven by its exit code:
- Exit
0: success; JSON on stdout (if any) is parsed for a decision
- Exit
2: blocking error; stderr is shown and the action is blocked
- Other codes: non-blocking error; stderr is shown and execution continues
Example: format files after edits
A PostToolUse hook that runs your formatter after Claude edits or writes a file. The hook script reads the edited file path from the JSON on stdin.
.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh"
}
]
}
]
}
}
.claude/hooks/format.sh:
#!/bin/bash
FILE=$(jq -r '.tool_input.file_path')
[ -n "$FILE" ] && npx prettier --write "$FILE"
Example: block a destructive Bash command
A PreToolUse hook can deny a tool call before it runs by returning a permission decision on stdout (with exit 0):
.claude/hooks/block-rm.sh:
#!/bin/bash
CMD=$(jq -r '.tool_input.command')
if echo "$CMD" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked"
}
}'
else
exit 0
fi
Example: notify when a turn finishes
A Stop hook that fires when Claude finishes responding:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Done\" with title \"Claude Code\"'",
"async": true
}
]
}
]
}
}
When to use hooks
- Run formatters, linters, or type-checks deterministically after every edit
- Enforce guardrails (block dangerous commands) at
PreToolUse
- Set up or tear down environment state at
SessionStart / SessionEnd
- Send notifications at
Stop or Notification
Disabling hooks
To turn off all user and project hooks (managed-policy hooks are unaffected), set in settings:
{
"disableAllHooks": true
}
Related custom commands
Because hooks are configured by editing settings files, you can wrap your own workflow in a custom slash command (a user-created Markdown file in .claude/commands/). For example, create .claude/commands/add-format-hook.md describing the change you want, then invoke it with /add-format-hook. That is a custom recipe you author, not a built-in feature, and it still results in edits to settings.json using the structure above.