Hooks let you automate workflows in Claude Code by triggering actions at specific moments: before a tool runs, after it completes, or when Claude stops and waits for input. With hooks, you can enforce rules (block commits to main), run quality checks automatically (tests after every file change), or surface warnings before risky operations (file deletion).
Hooks live in your settings.json and execute through your machine, not Claude. They're reliable, fast, and under your control. For the full shape of that file — scopes, precedence, and the other top-level keys — see the settings.json guide.
A hook has three parts:
Hooks are defined in JSON inside one of six possible locations — ~/.claude/settings.json for global rules, .claude/settings.json for project rules you commit to the repo, .claude/settings.local.json for machine-specific overrides, managed-policy settings for org-wide enforcement, plugin bundles, and skill or agent frontmatter. The full locations table below covers precedence. The harness that runs Claude Code executes hooks, so they work reliably even if Claude tries to bypass them.
Key: Hooks run on your machine, not through Claude. This means they're trustworthy for enforcing rules Claude shouldn't be able to override.
This hook watches for git commits and blocks any attempt to commit directly to main or master, forcing Claude to use a feature branch instead.
When to use: On any project with multiple developers or a main-to-prod deployment pipeline.
Add this to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git commit.*)",
"hooks": [
{
"type": "command",
"command": "if git rev-parse --verify HEAD 2>/dev/null && [[ $(git rev-parse --abbrev-ref HEAD) =~ ^(main|master)$ ]]; then echo '❌ Cannot commit directly to main/master. Create a feature branch first.'; exit 1; fi"
}
]
}
]
}
}
How it works: Runs before any Bash command that looks like a git commit. If you're on main or master, exits with error and prints a message. Claude sees the error and switches to creating a branch.
Whenever Claude writes or edits a file, run your test suite to catch broken tests immediately before Claude moves on.
When to use: On projects with fast test suites (< 30 seconds). Skip this if tests take > 1 minute.
Add this to ~/.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "if [ -f package.json ]; then npm test -- --run 2>&1 | head -50; elif [ -f pytest.ini ] || [ -f pyproject.toml ]; then python -m pytest --tb=short -q 2>&1 | head -50; fi"
}
]
}
]
}
}
How it works: After Write or Edit completes, runs npm test (or pytest) and shows the first 50 lines of output. Claude sees test failures immediately and can fix them.
When Claude pauses to wait for your input, automatically show git status so you see what changed without asking.
When to use: Always. This is a pure information hook with no downsides.
Add this to ~/.claude/settings.json:
{
"hooks": {
"Stop": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "echo '\\n━━━ Git Status ━━━'; git status --short 2>/dev/null || echo 'Not a git repo'"
}
]
}
]
}
}
How it works: On every Stop event (when Claude finishes and waits for input), runs git status --short. You see file changes at a glance without needing to type the command yourself.
Prevents accidental file deletion by showing a warning and requiring confirmation before Bash runs rm, rmdir, or git rm commands.
When to use: On any project where data loss is a risk. Always safer to warn first.
Add this to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(rm|rmdir|git rm)",
"hooks": [
{
"type": "command",
"command": "echo '⚠️ WARNING: About to delete file(s). Confirm this was intentional.' && read -p 'Continue? (y/N): ' -n 1 response && [[ $response == 'y' ]] || exit 1"
}
]
}
]
}
}
How it works: Before any Bash command containing rm, rmdir, or git rm, prompts you to confirm. If you don't type 'y', the command is blocked.
Keep a timestamped record of every tool Claude runs successfully — what it did, when. Useful for security audits or debugging unexpected behavior. Add the same hook under PostToolUseFailure as well if you also want to log failed calls.
When to use: On sensitive projects, team setups, or when debugging mysterious behavior.
Add this to ~/.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "mkdir -p \"$CLAUDE_PROJECT_DIR/.claude\" && INPUT=$(cat); T=$(echo \"$INPUT\" | jq -r '.tool_name'); echo \"$(date '+%Y-%m-%d %H:%M:%S') | $T\" >> \"$CLAUDE_PROJECT_DIR/.claude/tool-audit.log\""
}
]
}
]
}
}
How it works: After each successful tool call, the hook ensures .claude/ exists, reads the hook payload from stdin with jq, extracts the tool_name field, and appends a timestamped line to .claude/tool-audit.log. Claude Code passes hook data as JSON on stdin — not environment variables — so jq is the shortest path to a specific field. Requires jq installed; on macOS brew install jq. To also capture failed tool calls, duplicate the block under PostToolUseFailure.
Claude Code ships more than two dozen hook events. The three you'll reach for 90% of the time:
PreToolUse — Before a tool runs (block risky operations, ask for confirmation)PostToolUse — After a tool succeeds (run cleanup, tests, formatting)Stop — When Claude finishes a response and waits for input (show context, status)Other events worth knowing: SessionStart and SessionEnd (session lifecycle), UserPromptSubmit (before Claude processes your prompt), PostToolUseFailure (only fires when a tool errored), SubagentStart / SubagentStop (for teammate agents), PreCompact / PostCompact (context compaction), FileChanged (watched file changed on disk), and CwdChanged. The authoritative list lives at the official hooks reference — Anthropic has been adding events every few releases, so check the docs before you bet a workflow on an event name you remember.
Bash — All shell commands (or Bash(pattern) for specific commands)Write — File creationEdit|MultiEdit — File modificationRead — File reading.* — Any tool (for broad logging)Each hook entry has a type field with four options:
command — Run a shell command (the examples above). The workhorse type.http — POST the hook payload to a URL. Good for server-backed policy enforcement across a team.prompt — Feed text back into Claude's context at the hook point. Useful for reminders.agent — Spawn a subagent to handle the hook. For advanced workflows that need LLM judgment.If a hook takes > 5 seconds, Claude's workflow becomes annoying. Run quick checks, not full test suites.
PreToolUse hooks that exit with error code 1 will block the operation. PostToolUse hooks run after success, so they can't prevent the operation but can inform you (tests failed, formatting done, etc.).
Put hooks that enforce project rules (no commits to main, no .env writes) in .claude/settings.json at the project root so they're version-controlled and shared with your team. Put personal preference hooks (show git status, format code) in ~/.claude/settings.json globally.
Hooks can be defined in six different places. The mental model is additive, not override: Claude Code merges hook arrays across scopes, so a PreToolUse hook in your user settings and a PreToolUse hook in a plugin will both run. Managed policy settings are the exception — they can restrict which scopes are allowed to load hooks in the first place, and disableAllHooks set at the managed level overrides disableAllHooks anywhere else. Run /hooks inside Claude Code to see every source currently active.
| Location | Scope | Shareable? |
|---|---|---|
~/.claude/settings.json | All your projects | No — local to your machine |
.claude/settings.json | Single project | Yes — commit to the repo |
.claude/settings.local.json | Single project | No — gitignored |
| Managed policy settings | Organization-wide | Yes — admin-controlled |
Plugin hooks/hooks.json | While plugin enabled | Yes — bundled with plugin |
| Skill / agent frontmatter | While component active | Yes — in the component file |
jq:
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
CWD=$(echo "$INPUT" | jq -r '.cwd')
Common fields on stdin: session_id, transcript_path, cwd, permission_mode, hook_event_name. Tool events add tool_name and tool_input. A few environment variables are also set: $CLAUDE_PROJECT_DIR (project root), $CLAUDE_PLUGIN_ROOT (plugin install dir, if any), and $CLAUDE_ENV_FILE (on SessionStart / CwdChanged / FileChanged — append export KEY=value lines into this file to persist env vars for later Bash commands in the session).
If a hook isn't working, check that your JSON is valid (use jq to parse .claude/settings.json), confirm the settings file location is correct, and test the matcher pattern — try a broad one first like .*.
Hooks should be reliable, but if something goes wrong, you can temporarily disable all hooks by adding "disableAllHooks": true to your settings.json, then reload Claude Code.
Put project-wide rules (block commits to main, enforce lint) in .claude/settings.json at the project root so your team gets them on checkout. Put personal preferences (show git status, format on save) in ~/.claude/settings.json so they apply everywhere but aren't shared. Use .claude/settings.local.json for secrets or machine-specific commands that should stay gitignored.
Claude Code passes a JSON payload on stdin. Parse it with jq: INPUT=$(cat); TOOL=$(echo "$INPUT" | jq -r '.tool_name'). Common fields include tool_name, tool_input, session_id, cwd, and permission_mode. A handful of environment variables are also set, including $CLAUDE_PROJECT_DIR and $CLAUDE_PLUGIN_ROOT.
Yes — PreToolUse hooks can block. If your command exits with a non-zero status, Claude sees the error and cancels the tool call. PostToolUse hooks run after the tool has already completed, so they can report problems but cannot prevent the action that already happened.
PreToolUse fires before the tool runs and can block it by exiting non-zero — use it for guardrails like "no commits to main" or confirmation prompts before rm. PostToolUse fires after the tool succeeds and cannot prevent the action — use it for reactions like running tests after Edit or Write, or formatting. There is also a PostToolUseFailure event for when the tool failed.
Yes — and the JSON on stdin includes agent_id and agent_type when a subagent triggered the hook, so you can filter by agent. There are also dedicated SubagentStart and SubagentStop events for subagent lifecycle.
Related reading: the MCP servers guide for how hooks layer on top of MCP tool calls, and the CLAUDE.md guide for how project rules load before hooks fire.
The Claude Code Starter Kit includes these 5 hooks plus 4 more production-ready examples, 5 CLAUDE.md templates, 10 slash commands, settings profiles, and 20 power prompts — free download.
Download free →Saved you time? Tip the maker in BTC — no account, no signup, just paste.
bc1qs04leape97ner4wqa98n94l9n0gv9aa84eg4ux
Hand-built single-file games, quizzes, and visualizers — no signup, no tracking, no cost.