Every question we've been asked, answered. Official docs
exit 2 = block. exit 0 = allow. Install 8 safety hooks: npx cc-safe-setup
Shell scripts that run at specific points in Claude Code's lifecycle:
They're configured in ~/.claude/settings.json and enforced at the process level — the model cannot bypass them.
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.
Not really. You can:
npx cc-safe-setup to install pre-built hooks (zero coding)npx cc-safe-setup --create "your description" to generate hooks from Englishexit 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.
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'
Yes. Output JSON to stdout:
echo '{"decision":"approve","reason":"Safe test command"}'
exit 0
This skips the permission prompt for that specific tool call.
It's a regex matched against the tool name:
"Bash" — only Bash commands"Edit|Write" — file modifications"" (empty) — matches ALL tools"Read" — file readsNo. 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
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.
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.
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★).
Run diagnostics: npx cc-safe-setup --doctor
Common causes:
chmod +x ~/.claude/hooks/your-hook.sh#!/bin/bash"Bash" won't fire for Edit/Write toolspython3 -c "import json; json.load(open('$HOME/.claude/settings.json'))"which jq — install if missingQuick fix: npx cc-safe-setup --quickfix
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.
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.
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\"}"
Three ways:
npx cc-safe-setup (installs 8 safety hooks)npx cc-safe-setup --install-example block-database-wipe~/.claude/hooks/, chmod +x, add to settings.jsonnpx cc-safe-setup --uninstall
Or manually: remove the hook entries from ~/.claude/settings.json and delete the script files.
Scripts: ~/.claude/hooks/
Configuration: ~/.claude/settings.json (global) or .claude/settings.json (project)
Project-level hooks override global hooks for the same matcher.