If you have ever used Claude Code to edit a file and then immediately typed prettier --write or npm test in a separate terminal, you already know the pattern hooks are designed to eliminate. Claude Code hooks are shell commands that execute automatically at defined points in the tool-call lifecycle — no prompting, no post-edit rituals, no manual oversight.
This guide covers the complete hooks system: what hooks are, how to configure them, which exit codes matter, and six practical use cases you can copy today.
What Hooks Are
A hook is a shell command that Claude Code runs before or after it executes one of its built-in tools. The built-in tools include:
- Bash — running terminal commands
- Edit — making changes to an existing file
- Write — creating or overwriting a file
- Read — reading a file's contents
- WebFetch, WebSearch, and others
Every time Claude Code is about to invoke one of these tools, it can first run a hook you have defined. Every time Claude Code finishes invoking one of these tools, it can run a separate hook you have defined. The hook is just a shell command — it can be a one-liner, a script path, or a chained command.
Where to Configure Hooks
Hooks live in settings.json under the hooks key. There are two locations:
| Location | Scope |
|---|---|
.claude/settings.json (project root) | Applies to everyone on the project |
~/.claude/settings.json (home directory) | Applies to all projects for your user |
Both files use the same schema. If you want your team to share a lint-on-edit rule, put it in the project-level file and commit it to the repository. If you want personal logging or notifications, put it in your user-level file.
Hook Types: PreToolUse and PostToolUse
There are exactly two hook types.
PreToolUse — runs before Claude executes the matched tool. If this hook exits with a non-zero code, Claude Code aborts the tool call entirely. This is how you block operations.
PostToolUse — runs after Claude has already executed the matched tool. The exit code of a PostToolUse hook does not block anything. Use this for side effects: formatting, testing, logging, notifications.
The Hook Format in settings.json
The hooks key contains an object with PreToolUse and/or PostToolUse arrays. Each entry in the array specifies a matcher (which tool to watch) and a hooks array (the commands to run).
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo 'About to run bash'"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "npm run lint --fix"
}
]
}
]
}
}
The matcher field is a string that matches the tool name exactly (case-sensitive): "Bash", "Edit", "Write", "Read", "WebFetch", etc. The type field is always "command". The command field is the shell string that will be executed.
Hook Exit Codes
For PreToolUse hooks, exit codes carry enforcement weight:
- Exit 0 — allow the tool call to proceed
- Any non-zero exit — block the tool call; Claude Code aborts the operation and reports the failure
For PostToolUse hooks, exit codes are informational only. The tool has already run. A failing PostToolUse hook will surface an error in the output but will not undo what was done.
This asymmetry is intentional. PreToolUse is for gates and guards. PostToolUse is for reactions.
Six Real-World Use Cases
Complete AI Builder Bootcamp
Claude, Python automation & full-stack — 12 live sessions with Yash Thakker.
The Complete AI Builder Bootcamp is the best AI development course for learning Claude AI, prompt engineering, Python automation, and full-stack web development. This intensive 6-week live bootcamp teaches you how to build AI-powered applications using Claude Projects, Claude Artifacts, Claude Code, and the complete Claude ecosystem. You'll master prompt engineering techniques, learn to create custom Claude connectors and MCP integrations, build Python automation workflows, develop full-stack websites with AI assistance, and create AI marketing agents.
The bootcamp includes 12 live Zoom sessions with Yash Thakker, founder of AISOLO Technologies and instructor to 350,000+ students. You'll build 8+ portfolio projects including AI playbooks, full-stack note-taking applications, Python automation scripts, marketing agents, and personal portfolio websites. The curriculum covers AI fundamentals, Claude Projects and Artifacts, Claude Co-work, Claude plugins and skills, Claude Code for Python development, full-stack development, AI marketing, and capstone projects.
Students receive 1-year access to all recordings, permanent Discord community access, a certificate of completion, and personalized career guidance. All enrollments include a 7-day money-back guarantee. This is the most comprehensive Claude AI bootcamp available, taking students from zero AI knowledge to expert AI builder in 6 weeks.
1. Auto-Format Every File Edit
The most common use case. After Claude edits or writes any file, run Prettier automatically.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "prettier --write $(cat)"
}
]
},
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "prettier --write $(cat)"
}
]
}
]
}
}
If your formatter is ESLint with --fix, swap the command:
{
"type": "command",
"command": "eslint --fix --quiet $(cat)"
}
The formatter runs immediately after each file operation, so Claude's next action always sees clean, formatted code.
2. Run Tests After File Changes
Every time Claude writes a file, run the test suite. This catches regressions before Claude moves on to the next step.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "npm test --silent"
}
]
}
]
}
}
For large test suites, scope it to the affected module rather than running everything:
{
"type": "command",
"command": "npm test -- --testPathPattern=$(cat) --silent"
}
3. Log Every Bash Command for an Audit Trail
In security-sensitive environments, you need a record of every command Claude runs. A PreToolUse hook on Bash that appends to a log file gives you this without blocking anything.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"[$(date -u +%Y-%m-%dT%H:%M:%SZ)] BASH: $CLAUDE_TOOL_INPUT\" >> ~/.claude/audit.log"
}
]
}
]
}
}
This exits 0 every time (because it only appends and echoes), so it never blocks the command. It just records it. Review ~/.claude/audit.log after any session to see the full command history.
4. Block Dangerous Commands
Use a PreToolUse hook on Bash to inspect the command before it runs and reject it if it matches a dangerous pattern.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'CMD=$(cat); if echo \"$CMD\" | grep -qE \"rm -rf /|dd if=|mkfs|shutdown|reboot\"; then echo \"Blocked: dangerous command detected\" >&2; exit 1; fi'"
}
]
}
]
}
}
When this hook exits with code 1, Claude Code aborts the Bash call and surfaces the error message. Claude will see the block and either rephrase the approach or ask you to confirm explicitly.
For a more maintainable setup, move the logic into a script:
#!/bin/bash
# ~/.claude/hooks/check-bash.sh
CMD=$(cat)
BLOCKED_PATTERNS=("rm -rf /" "dd if=" "mkfs" ":(){:|:&};:" "chmod -R 777 /")
for pattern in "${BLOCKED_PATTERNS[@]}"; do
if echo "$CMD" | grep -qF "$pattern"; then
echo "Blocked: matched dangerous pattern '$pattern'" >&2
exit 1
fi
done
exit 0
Then reference it:
{
"type": "command",
"command": "bash ~/.claude/hooks/check-bash.sh"
}
5. Auto-Commit After Significant Edits
For workflows where you want a commit checkpoint after Claude finishes writing a file:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "git add -A && git diff --cached --quiet || git commit -m 'auto: claude write checkpoint'"
}
]
}
]
}
}
The git diff --cached --quiet || guard prevents empty commits — it only commits if there are staged changes.
Use this with caution in projects with pre-commit hooks, since each Write will trigger the full pre-commit pipeline.
6. Send a Slack Notification When a Long Task Finishes
After Claude finishes a Bash command (which might have been a long build or deploy), send a Slack notification.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "curl -s -X POST -H 'Content-type: application/json' --data '{\"text\":\"Claude Code finished a bash task.\"}' $SLACK_WEBHOOK_URL"
}
]
}
]
}
}
Note: $SLACK_WEBHOOK_URL is read from your shell environment, not hardcoded in the JSON. Never put the raw webhook URL directly in settings.json.
User-Level vs. Project-Level Hooks
Project-level (.claude/settings.json):
- Lives in the repository root
- Committed to version control
- Shared with every contributor
- Best for: lint rules, test runners, safety guards your whole team should have
User-level (~/.claude/settings.json):
- Lives in your home directory
- Never committed
- Applies to every Claude Code session on your machine
- Best for: personal audit logs, Slack notifications, personal safety rules
When both files exist, Claude Code merges them. Project-level hooks and user-level hooks both run. There is no override — both lists execute.
Testing Hooks with --debug
Before relying on a hook in production, verify it works with the --debug flag:
claude --debug
In debug mode, Claude Code prints each hook invocation — the matcher, the command, the exit code, and any stdout/stderr output. This is the fastest way to confirm your hook is being triggered and that the exit code is what you expect.
A common mistake: the hook command path is not in $PATH at the time Claude Code runs. If your hook fails silently, check whether the binary is accessible from a non-interactive shell (the environment Claude Code hooks run in).
Security Considerations
Hooks run with your OS user's full permissions. There is no sandboxing. A hook that runs curl or bash has the same access you do.
Two rules:
-
Never put secrets in hook command strings. If your hook needs an API key or a webhook URL, read it from an environment variable (like
$SLACK_WEBHOOK_URLabove) or from a file outside the repository. The command string insettings.jsoncan end up in logs, process listings, and version control history. -
Review third-party settings.json files. If you clone a repository that includes
.claude/settings.jsonwith hooks already defined, read those hooks before running Claude Code. A malicious hook inPreToolUseon Bash could exfiltrate data on every command.
The same common sense that applies to .github/workflows applies here.
Summary: Hook Decision Table
| Goal | Hook Type | Matcher | Exit on block? |
|---|---|---|---|
| Auto-format files | PostToolUse | Edit, Write | No |
| Run tests after write | PostToolUse | Write | No |
| Audit log all bash | PreToolUse | Bash | No (exit 0) |
| Block dangerous commands | PreToolUse | Bash | Yes (exit 1) |
| Auto-commit checkpoints | PostToolUse | Write | No |
| Slack notifications | PostToolUse | Bash | No |
Hooks are one of the most underused features in Claude Code. They close the gap between "Claude made a change" and "the change is verified, formatted, and logged" — without any additional prompting or terminal switching on your part.