Claude Code Hooks: 6 Copy-Paste Examples (Block rm -rf, Protect .env, Auto-Format)

Published Oct 15, 2026 · 7 min read · Checked against the official hooks reference

Hooks are the only part of Claude Code that are guaranteed to run. CLAUDE.md is advice and skills are instructions. A hook runs every time its event fires, whatever the model decides. If a rule must never be broken, it belongs in a hook.

How hooks work in 30 seconds

1. Block destructive shell commands

#!/usr/bin/env bash
# .claude/hooks/block-dangerous.sh
cmd=$(jq -r '.tool_input.command // ""')
if echo "$cmd" | grep -Eq 'rm -rf (/|~|\*)|git push (-f|--force)|git reset --hard|mkfs|dd if='; then
  echo "Blocked: '$cmd' looks destructive. Ask the user to run it manually." >&2
  exit 2
fi
exit 0
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [
  { "type": "command", "command": "\"${CLAUDE_PROJECT_DIR}\"/.claude/hooks/block-dangerous.sh" } ] } ] } }

2. Protect .env, lockfiles and .git

#!/usr/bin/env bash
# .claude/hooks/protect-files.sh  (matcher: "Edit|Write")
path=$(jq -r '.tool_input.file_path // ""')
case "$path" in
  *.env|*.env.*|*/.git/*|*package-lock.json|*pnpm-lock.yaml|*yarn.lock)
    echo "Blocked: $path is protected." >&2; exit 2;;
esac
exit 0

3. Auto-format after every edit

{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [
  { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write", "timeout": 30 } ] } ] } }

PostToolUse can't block, because the edit already happened. Its job is cleanup.

4. Don't let Claude stop until tests pass

#!/usr/bin/env bash
# Stop hook: exit 2 sends Claude back to work with this message
if ! npm test --silent >/tmp/cc-test.log 2>&1; then
  echo "Tests are failing. Fix them before finishing:" >&2; tail -20 /tmp/cc-test.log >&2; exit 2
fi
exit 0

Register it under "Stop" (no matcher). Guard against infinite loops: the input includes stop_hook_active, so exit 0 when it's true.

5. Desktop ping when Claude needs you

{ "hooks": { "Notification": [ { "hooks": [
  { "type": "command", "command": "notify-send 'Claude Code' 'Needs your input'" } ] } ] } }

On macOS, use osascript -e 'display notification "Needs input" with title "Claude Code"'.

6. Inject live context at session start

{ "hooks": { "SessionStart": [ { "matcher": "startup", "hooks": [
  { "type": "command", "command": "echo \"Branch: $(git branch --show-current). Open TODOs: $(grep -rc TODO src | awk -F: '{s+=$2} END {print s}')\"" } ] } ] } }

Plain stdout from a SessionStart hook is added to Claude's context.

Gotchas

Get hooks 1 and 2 free, pre-tested

The Claude Code Safety Hooks Mini-Pack includes both scripts, a settings.json and a test script.

Want the full setup with subagents, skills and CLAUDE.md templates? Claude Code Agent Starter Kit, $39.

Reference: code.claude.com/docs/en/hooks

FAQ

Where are Claude Code hooks configured?

In the hooks section of a settings file: .claude/settings.json for the project, .claude/settings.local.json for personal overrides, or ~/.claude/settings.json for every project. Run /hooks to see what is active.

How does a hook block a command?

A PreToolUse hook receives the tool call as JSON on stdin. If the script exits with code 2, Claude Code blocks the call and shows the script's stderr to Claude. Exit code 0 allows it; other non-zero codes are treated as non-blocking errors.

Can hooks protect my .env file?

Yes. A PreToolUse hook matched on Edit and Write can check the file path and exit 2 for .env files, lockfiles and the .git folder. The free VibeKit safety hooks include this hook ready to use.

Do hooks work on Windows?

Hooks run shell commands, so bash-based hook scripts need WSL or Git Bash on Windows.

The VibeKit Dispatch

One tested Claude Code / Cursor workflow every week. No hype, unsubscribe anytime.