Anatomy of a hook
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "npx prettier --write \"$(jq -r .tool_input.file_path)\"" }
]
}
]
}
}
Three parts: the event, a matcher (a regex on the tool name for tool events; omit it to match everything), and one or more commands. The command receives a JSON object on stdin describing the event — for tool events that includes tool_name and tool_input, which is how the example above finds the file that was just edited. Put it in .claude/settings.json to share with the repo, ~/.claude/settings.json for yourself, or run /hooks and let Claude Code write it.
The events
PreToolUse— before a tool runs. The only event that can block: exit 2 cancels the call and Claude reads your stderr as the reason.PostToolUse— after a tool succeeds. Formatters, linters, type-checks on the edited file.UserPromptSubmit— when you send a prompt; can inject context or reject the prompt.Stop/SubagentStop— when the agent (or a subagent) is about to finish. Exit 2 here means "not done yet" — the classic use is refusing to stop while tests fail.PreCompact— before/compact; save anything you need from the full transcript.SessionStart— when a session begins or resumes; load environment, print reminders.Notification— when Claude Code wants your attention (permission prompt, idle); wire it to a desktop or Slack notification.
Blocking dangerous commands
#!/usr/bin/env bash
# .claude/hooks/guard.sh -- PreToolUse, matcher: Bash
cmd=$(jq -r '.tool_input.command')
if echo "$cmd" | grep -Eq 'rm -rf|git push --force|DROP TABLE|--no-verify'; then
echo "blocked by guard.sh: $cmd" >&2
exit 2
fi
exit 0
Exit code 2 is the contract: the tool call does not happen, and whatever the hook printed to stderr becomes the explanation Claude sees, so it can choose a different approach instead of retrying the same thing. Any other non-zero exit is logged but does not block.
"Do not stop until the tests pass"
#!/usr/bin/env bash
# Stop hook: refuse to finish while the suite is red
if ! npm test --silent >/tmp/claude-test.log 2>&1; then
echo "Tests are failing; fix them before finishing:" >&2
tail -n 40 /tmp/claude-test.log >&2
exit 2
fi
Pair it with a --max-turns cap in headless runs so a genuinely unfixable failure does not loop forever.
Hooks vs CLAUDE.md vs permissions
CLAUDE.md is advice the model reads; /permissions is a list of what it may do without asking; hooks are code that runs regardless. Use CLAUDE.md for style and context, permissions for the allow/ask boundary, and hooks for anything that must be true every time — formatting, secrets scanning, test gates, audit logs. They compose: a permission lets Bash(git *) run, a PreToolUse hook still blocks git push --force.
Hooks and the gateway
Hooks run locally and never touch the API, so nothing changes when Claude Code points at ANTHROPIC_BASE_URL=https://aiprimetech.io. One useful combination: a Stop hook that prints /cost-style usage from your own logs, so a team on pay-as-you-go credits sees the price of every session as it ends.
Frequently asked questions
What are Claude Code hooks?
Shell commands that Claude Code runs automatically at events in its loop — before/after tool calls, on prompt submit, on stop. They are deterministic, unlike instructions in CLAUDE.md.
How do I block a command with a hook?
A PreToolUse hook with matcher Bash that inspects tool_input.command from stdin and exits with code 2; the stderr text is shown to Claude as the reason.
Where do hooks go?
.claude/settings.json in the project (shared) or ~/.claude/settings.json (personal), under the hooks key; /hooks edits them interactively.
Run Claude Code on the gateway
Same models, two environment variables, credits at 7.69× face value — or a flat-rate unlimited plan.
Get an API key See unlimited plansAI Prime Tech is an independent API gateway and is not affiliated with, endorsed by, or sponsored by Anthropic. “Claude” and “Claude Code” are trademarks of Anthropic. Claude Code features described here follow Anthropic’s public documentation at the time of writing and change frequently; prices and model lists on this page are read from this gateway’s live settings.