Claude Code hooks explained: config structure, matchers, and a copy-paste PreToolUse guard Anthropic's Claude Code supports hooks—deterministic shell commands or HTTP endpoints that fire at fixed points in a session to block, allow, or reshape actions. A developer explains the configuration structure, the two modes for reporting decisions, and provides a copy-paste PreToolUse guard that blocks recursive rm commands. The hook JSON format for PreToolUse now requires a hookSpecificOutput object with a permissionDecision field, replacing the deprecated top-level decision field. If you have only ever used Claude Code's permission prompts, hooks are the next layer down: deterministic shell commands or HTTP endpoints, MCP tools, prompts, or subagents that fire at fixed points in a session and can block, allow, or reshape what happens next. The feature is powerful, but the configuration has a few sharp edges that trip people up — especially the JSON your hook prints back, which changed and is easy to get wrong from memory. This post walks through the current config structure, the two ways a hook reports a decision, and a copy-paste PreToolUse guard that blocks recursive rm . Hooks live in a settings file .claude/settings.json for a project, ~/.claude/settings.json for your user . The shape is three levels of nesting, and naming them makes the rest of the docs click: { "hooks": { "PreToolUse": { "matcher": "Bash", "hooks": { "type": "command", "command": "${CLAUDE PROJECT DIR}/.claude/hooks/block-rm.sh", "if": "Bash rm " } } } } PreToolUse — the lifecycle point. Events fall into three cadences: once per session SessionStart , SessionEnd , once per turn UserPromptSubmit , Stop , and on every tool call PreToolUse , PostToolUse . "matcher": "Bash" — a filter for tool name . hooks array — the thing that actually runs. There are five types: command , http , mcp tool , prompt , and agent .The if field narrows further using permission-rule syntax — Bash rm here means "only spawn this handler when the Bash subcommand matches rm ," so the script never even launches for npm test . It holds exactly one rule; there is no && or || , so multiple conditions mean multiple handlers. A matcher that contains regex characters is tested with JavaScript's RegExp.prototype.test , which succeeds on a match anywhere in the value. So: Edit also matches NotebookEdit . If you mean only the Edit tool, write ^Edit$ . mcp