Anything you keep asking the agent to do “every time” is a candidate for a hook: a command Claude Code runs itself at a fixed point in the session. These patterns cover the usual cases. Put project ones in .claude/settings.json so the whole team gets them, and personal ones (sounds, notifications) in ~/.claude/settings.json.
When to use it
When a rule must hold on every turn and is easy to check mechanically: formatting, type errors, failing tests, protected files, a reminder that must survive compaction. If the rule needs judgment case by case, keep it in CLAUDE.md.
How to do it
- Format after every edit.
PostToolUsewith matcherEdit|Write, running Prettier (or your formatter) ontool_input.file_path. Nothing shows in the conversation on success; check the file. - Type-check after every edit.
PostToolUseonEdit|Writerunningnpx tsc --noEmit 1>&2 || exit 2. The edit is already on disk, so exit 2 cannot undo it, but the compiler errors go to Claude on stderr and it fixes them in the next step. This turns the advice in Verifying agent output into something that happens without asking. - Do not let Claude stop on red tests. A
Stophook of typeagentruns the test suite in a subagent and answersok: falsewith a reason when tests fail, so Claude keeps working. Give it a longertimeoutthan the 60-second default. AStophook that blocks eight times in a row is overridden, so it cannot loop forever. - Guard files and commands.
PreToolUseonEdit|Write(orBash) that checks the path or command and exits 2 with a reason on stderr:.env, lockfiles,.git/, a commit tomain. Claude reads the reason and looks for another way. Keep permission rules as the hard gate; the hook adds an explanation Claude can act on. - Restore context after compaction.
SessionStartwith matchercompact. Whatever the command prints to stdout is added to the context, for examplegit log --oneline -5or “use pnpm, not npm”. For context that every session needs from the start, CLAUDE.md is the simpler place. - Trim noisy output before Claude reads it. A
PreToolUsehook onBashcan rewrite the command throughupdatedInput, for example piping a test run through a filter for failing lines, so a log of thousands of lines costs hundreds of tokens instead of tens of thousands. Third-party tools such as rtk package this as a ready hook. See Reducing token usage. - Get notified.
Notificationwith an empty matcher fires when Claude waits for input or permission; narrow it with matchers such aspermission_promptoridle_prompt. Run a desktop notification command, or a tool such as peon-ping, which plays a sound and sets the terminal tab title.
Example
A Node + TypeScript service, .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" },
{ "type": "command", "command": "npx tsc --noEmit 1>&2 || exit 2" }
]
}
],
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.ts" }
]
}
],
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{ "type": "command", "command": "echo 'Use pnpm. Run pnpm test before saying done.'; git log --oneline -5" }
]
}
]
}
}When Claude writes await fs.promises.exists(path), the type-check hook fails with the compiler error, Claude reads it and switches to fs.promises.access, before you look at the diff. The protect-files.ts script is on the hooks page.
Common mistakes
- Exiting 1 to block. Only exit 2 or a JSON
denyblocks. Exit 1 is a hook error, and the action goes ahead. - Relying on a hook as the only lock. A crash or timeout lets the action through unless the hook has
"onFailure": "block". Pair important blocks with permission rules. - Two hooks rewriting the same command. With two
updatedInputhooks onBash, the last to finish wins at random. Keep one rewriter per tool. - Slow checks on every edit. A full test run after each
Editmakes every step slow. Put fast checks (format, type-check) onPostToolUseand the slow suite onStop. - Forgetting
chmod +x. A script hook that is not executable does not run.
Related
- Hooks in Claude Code: events, configuration, exit codes and gotchas.
- Verifying agent output: the checks these hooks automate.
- Reducing token usage: output filtering and other savings.
- Managing context: what compaction drops and how to bring it back.