Claude Code Hooks FAQ

Every question we've been asked, answered. Official docs

TL;DR: Hooks are shell scripts that run before/after Claude Code tools. exit 2 = block. exit 0 = allow. Install 8 safety hooks: npx cc-safe-setup
Basics
What are Claude Code hooks?

Shell scripts that run at specific points in Claude Code's lifecycle:

  • PreToolUse — before any tool (Bash, Edit, Write) executes
  • PostToolUse — after a tool completes
  • Stop — when Claude finishes responding

They're configured in ~/.claude/settings.json and enforced at the process level — the model cannot bypass them.

How is this different from CLAUDE.md?

CLAUDE.md is a prompt-level instruction. It works well at the start of a session but degrades as context fills up. After 100+ tool calls, Claude may "forget" rules.

Hooks run on every single tool call, enforced by the runtime. Even if Claude wants to ignore them, exit 2 physically prevents the action.

Use both: CLAUDE.md for guidelines, hooks for hard constraints.

Do I need to know bash to use hooks?

Not really. You can:

  1. Use npx cc-safe-setup to install pre-built hooks (zero coding)
  2. Use npx cc-safe-setup --create "your description" to generate hooks from English
  3. Use the Hook Builder web tool
  4. Copy-paste from the Cheat Sheet
How It Works
What does exit code 2 do?

exit 2 blocks the tool call. The model receives a message that the operation was blocked and must try a different approach.

exit 0 allows the operation (default).

exit 1 is treated as a hook error and is silently ignored — don't use it for blocking.

What JSON does the hook receive on stdin?

For PreToolUse (Bash):

{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}

For PreToolUse (Edit):

{"tool_name":"Edit","tool_input":{"file_path":"app.js","old_string":"...","new_string":"..."}}

For PostToolUse:

{"tool_name":"Bash","tool_input":{"command":"ls"},"tool_result":"file1.txt\nfile2.txt"}

Extract with: cat | jq -r '.tool_input.command // empty'

Can I auto-approve commands via hooks?

Yes. Output JSON to stdout:

echo '{"decision":"approve","reason":"Safe test command"}'
exit 0

This skips the permission prompt for that specific tool call.

What does the "matcher" field do?

It's a regex matched against the tool name:

  • "Bash" — only Bash commands
  • "Edit|Write" — file modifications
  • "" (empty) — matches ALL tools
  • "Read" — file reads
Performance & Safety
Do hooks slow down Claude Code?

No. Each hook runs in ~5ms (bash + jq). Even with 10 hooks chained, total overhead is <50ms per tool call — imperceptible compared to the API latency.

You can measure: npx cc-safe-setup --benchmark

Can Claude disable or modify hooks?

Claude can edit settings.json via the Edit tool. To prevent this:

npx cc-safe-setup --install-example protect-claudemd

This hook blocks edits to CLAUDE.md and settings files.

What if my hook has a bug?

If a hook exits with code 1 (error), Claude Code treats it as a hook failure and ignores it — the tool call proceeds. This is by design: buggy hooks don't block all operations.

Only exit 2 blocks. If your hook accidentally exits 2 for everything, use npx cc-safe-setup --doctor to diagnose.

Can I use Python/TypeScript instead of bash?

Yes. The command field can run anything:

{"type": "command", "command": "python3 ~/.claude/hooks/my-guard.py"}

cc-safe-setup includes Python examples: examples/python/destructive_guard.py

For TypeScript, see safety-net (1,185★).

Common Issues
My hook doesn't fire — what's wrong?

Run diagnostics: npx cc-safe-setup --doctor

Common causes:

  1. Not executable: chmod +x ~/.claude/hooks/your-hook.sh
  2. Missing shebang: First line must be #!/bin/bash
  3. Wrong matcher: "Bash" won't fire for Edit/Write tools
  4. settings.json syntax error: Validate with python3 -c "import json; json.load(open('$HOME/.claude/settings.json'))"
  5. jq not installed: which jq — install if missing

Quick fix: npx cc-safe-setup --quickfix

My hook blocks everything

Your hook is returning exit 2 for all inputs. Debug by testing manually:

echo '{"tool_input":{"command":"ls"}}' | bash ~/.claude/hooks/your-hook.sh
echo "Exit: $?"

If it exits 2 for ls, your regex is too broad. Check the grep -qE pattern.

Multiple hooks — does order matter?

Hooks in the same group run sequentially. If any hook exits 2, the tool call is blocked — remaining hooks don't run.

Important: Each hook consumes stdin. If you chain multiple hooks in the same group, the second hook gets empty stdin. Put each hook in a separate matcher group, or use tee.

Can hooks access the internet?

Yes. Hooks are regular shell scripts — they can make HTTP requests, send notifications, write to databases, etc.

Example: send a Slack notification when a dangerous command is blocked:

curl -s -X POST "$SLACK_WEBHOOK" -d "{\"text\":\"Blocked: $COMMAND\"}"
Installation
How do I install hooks?

Three ways:

  1. One command: npx cc-safe-setup (installs 8 safety hooks)
  2. Individual: npx cc-safe-setup --install-example block-database-wipe
  3. Manual: Save script to ~/.claude/hooks/, chmod +x, add to settings.json
How do I uninstall hooks?
npx cc-safe-setup --uninstall

Or manually: remove the hook entries from ~/.claude/settings.json and delete the script files.

Where are hooks stored?

Scripts: ~/.claude/hooks/

Configuration: ~/.claude/settings.json (global) or .claude/settings.json (project)

Project-level hooks override global hooks for the same matcher.