Claude Code Hooks: A Practical Setup Guide for Automating Your Workflow (2026)
Claude Code hooks are shell commands defined in your settings.json that fire automatically at specific points in Claude Code's lifecycle, such as before a tool runs, after it finishes, or when a session starts. They give you deterministic control over the agent instead of hoping the model remembers to run a linter or send a notification.
Why hooks matter
Without hooks, you rely on the model to "remember" to format code, block a dangerous command, or ping you when it's stuck waiting for input. That works most of the time, but not always, and "most of the time" is not good enough when a hook could just enforce the rule every single time. Hooks move that responsibility out of the LLM and into your own scripts, which run outside the model's control and can't be talked out of executing.
Common use cases developers are shipping in 2026:
- Auto-format or lint every file Claude edits before the change is accepted
- Block writes to
.env,secrets/, or production config directories - Send a desktop or Telegram notification when Claude finishes a task or needs input
- Log every tool call (with token cost) to a local file for budget tracking
- Run tests automatically after a code edit and feed failures back into the session
The hook lifecycle events
Claude Code exposes hook events at different stages. The ones you'll use most often:
| Event | Fires when | Typical use |
|---|---|---|
| SessionStart | A new Claude Code session begins | Load project context, check API key validity |
| PreToolUse | Before a tool (edit, bash, etc.) executes | Block dangerous commands, require confirmation |
| PostToolUse | After a tool finishes | Auto-format, run linter, log token usage |
| Notification | Claude sends a notification (e.g. waiting for input) | Push alert to phone/desktop |
| Stop | Claude finishes responding | Trigger a build, send a summary |
Basic configuration
Hooks live in your user-level ~/.claude/settings.json or project-level .claude/settings.json. Each hook entry has a matcher (which tool names it applies to) and a list of hooks with the command to run:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_TOOL_FILE_PATH\""
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/block-dangerous.sh"
}
]
}
]
}
}
Claude Code pipes a JSON payload describing the event to your command's stdin (tool name, file path, arguments). Your script reads that JSON, decides what to do, and returns an exit code: 0 to allow the action, non-zero to block it and surface a message back to Claude.
A real example: blocking risky bash commands
A minimal block-dangerous.sh that rejects rm -rf and force pushes:
#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // ""')
if [[ "$cmd" == *"rm -rf"* || "$cmd" == *"push --force"* ]]; then
echo '{"decision":"block","reason":"Destructive command blocked by hook"}'
exit 1
fi
exit 0
This runs on every single bash call, with zero chance of the model "forgetting" to check. That's the whole point of hooks: rules that used to live in a prompt now live in code that always executes.
Hooks + a cheaper API endpoint
If you're running Claude Code with a lot of hooks and automated loops (tests, formatters, notifications), your token usage goes up fast, especially with PostToolUse hooks that re-read file contents for linting context. Two things help keep costs sane:
- Prompt caching so repeated system prompts and file context aren't re-billed every turn
- A relay endpoint priced below official rates, so the same workflow costs less per token
This is where Safa API fits in if you're pointing Claude Code at a custom base URL. It's a one-endpoint relay for Claude, GPT, and Gemini models, priced below the official Anthropic/OpenAI rates, with prompt caching supported so hook-heavy workflows (lots of small repeated calls) don't rack up a huge bill. Setup is the same two environment variables Claude Code already expects:
export ANTHROPIC_BASE_URL="https://aisafa.xyz/v1"
export ANTHROPIC_API_KEY="your-safa-api-key"
No credit card is required to sign up, and Alipay is accepted for top-ups, which is a real hassle-saver if you're outside the US and Anthropic's billing has ever rejected your card. Since it's a single key across Claude, GPT, and Gemini, you can also point a PostToolUse hook's own LLM call (say, a commit-message generator) at a different model without juggling three separate API keys.
Frequently Asked Questions
Do hooks slow down Claude Code?
Slightly, since each hook is a subprocess call, but well-written hooks (a formatter, a grep check) typically add well under a second. The tradeoff is worth it for guarantees like "never write to .env" that a prompt instruction alone can't provide.
Can hooks call external APIs, like sending a Slack message?
Yes. A hook is just a shell command, so it can curl any webhook, including Slack, Telegram, or a custom endpoint, when the Notification or Stop event fires.
Do hooks work with a custom API base URL?
Yes, hooks are independent of which model provider you're using. Whether Claude Code is pointed at Anthropic directly or at a relay like Safa API via ANTHROPIC_BASE_URL, hooks fire the same way since they operate on the tool lifecycle, not the model connection.
官方直连 · 一个接口接入 Claude / GPT / Gemini · 7×24 稳定
免费注册试用 →