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.

The three-level config structure

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:

{