Are you the author? Sign in to claim
Copy-paste Claude Code hooks that actually do something — auto-format, block dangerous commands, test gates, notificatio
Copy-paste Claude Code hooks that actually do something useful — auto-format on edit, block dangerous commands, notify you when a run finishes, gate on tests, and cut permission prompts. Every recipe is real, dependency-free, and current with the latest hook schema.
Hooks are shell commands Claude Code runs automatically at points in its loop — before a tool runs, after a file is edited, when a session starts, when the turn ends. They're how you turn "an agent that asks nicely" into "an agent that physically can't rm -rf your repo and always leaves the code formatted." Most examples online are stale; this repo tracks the current schema (the if: conditions, permissionDecision, updatedInput, and the full event list).
# drop the scripts into your project
git clone https://github.com/rongtnt/claude-code-hooks-cookbook /tmp/cc-hooks
mkdir -p .claude/hooks && cp /tmp/cc-hooks/hooks/* .claude/hooks/ && chmod +x .claude/hooks/*
Then add the recipes you want to .claude/settings.json (commit it to share with your team) or ~/.claude/settings.json (all projects, just you). Restart Claude Code or run /hooks to reload. That's it.
$CLAUDE_PROJECT_DIRis set by Claude Code to your project root, so the paths below work from any subdirectory.
| # | Recipe | Event | What it does |
|---|---|---|---|
| 1 | Auto-format on edit | PostToolUse | Formats every file Claude writes |
| 2 | Block dangerous commands | PreToolUse | Denies rm -rf, force-push, curl | sh, … |
| 3 | Protect secrets & escapes | PreToolUse | No edits to .env, lockfiles, or outside the repo |
| 4 | Gate on tests before finishing | Stop | Won't let Claude stop on a red suite |
| 5 | Notify when done / when it needs you | Notification + Stop | Desktop + Slack ping |
| 6 | Inject repo context at session start | SessionStart | Branch, dirty files, TODOs, recent commits |
| 7 | Auto-approve read-only commands | PreToolUse | Stop clicking "approve" on ls/git status |
| 8 | Audit-log every tool call | PostToolUse | One greppable JSONL line per action |
Runs your formatter on any file Claude edits — Prettier / Ruff / rustfmt / gofmt / shfmt, whichever is installed. Note the ${tool_input.file_path} interpolation, so no stdin parsing is needed.
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write|MultiEdit",
"hooks": [ { "type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-on-edit.sh",
"args": ["${tool_input.file_path}"] } ] } ] } }
A PreToolUse hook that returns a deny decision when a Bash command matches a destructive pattern (rm -rf, git push --force, disk writes, fork bombs, curl | sh). Claude gets told why and moves on.
{ "hooks": { "PreToolUse": [ { "matcher": "Bash",
"hooks": [ { "type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous-commands.py" } ] } ] } }
→ hooks/block-dangerous-commands.py
Tip: you can also block without a script using the built-in
if:condition —{ "type": "command", "if": "Bash(rm *)", "command": "..." }— which only fires the hook when the command matches. The script version gives you finer control and a friendlier reason.
Denies edits to .env, *.pem, lockfiles, and anything resolving outside the project root — the two ways an agent quietly does damage.
{ "hooks": { "PreToolUse": [ { "matcher": "Edit|Write|MultiEdit",
"hooks": [ { "type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/protect-paths.py" } ] } ] } }
On Stop, run the suite. If it's red, return {"decision":"block","reason":"..."} — Claude keeps fixing instead of declaring victory. Guards against infinite loops via stop_hook_active.
{ "hooks": { "Stop": [ { "hooks": [ { "type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/test-gate.py", "timeout": 300 } ] } ] } }
→ hooks/test-gate.py (set TEST_CMD to your command)
Desktop notification (macOS osascript / Linux notify-send) when Claude finishes or needs input, plus Slack if SLACK_WEBHOOK_URL is set. Never babysit a long run again.
{ "hooks": {
"Notification": [ { "hooks": [ { "type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/notify.py" } ] } ],
"Stop": [ { "hooks": [ { "type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/notify.py", "args": ["done"] } ] } ] } }
A SessionStart hook that prepends live repo state — branch, uncommitted files, recent commits, open TODOs — so Claude starts oriented instead of re-discovering your repo every session.
{ "hooks": { "SessionStart": [ { "matcher": "startup|resume",
"hooks": [ { "type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-context.sh", "timeout": 10 } ] } ] } }
Fast-tracks a tight allowlist of harmless commands (ls, cat, git status, rg…) with a permissionDecision: "allow", so you stop approving the obviously-safe ones. Never denies — unknown commands fall through to the normal flow.
{ "hooks": { "PreToolUse": [ { "matcher": "Bash",
"hooks": [ { "type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/auto-approve-readonly.py" } ] } ] } }
→ hooks/auto-approve-readonly.py
A PostToolUse hook that appends one JSONL line per tool call to .claude/tool-log.jsonl — a cheap, greppable record of what the agent actually did.
{ "hooks": { "PostToolUse": [ { "matcher": "*",
"hooks": [ { "type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/log-tool-calls.py" } ] } ] } }
| Event | Fires | Can block? | Typical use |
|---|---|---|---|
PreToolUse | before a tool runs | ✅ | guardrails, auto-approve, rewrite input |
PostToolUse | after a tool succeeds | ⚠️ (post-hoc) | format, lint, log, add context |
UserPromptSubmit | before your prompt is processed | ✅ | inject context, redact secrets |
Stop | Claude finishes the turn | ✅ | test/lint gates, notify |
SessionStart | session begins / resumes | — | inject repo context |
Notification | Claude sends a notification | — | desktop / Slack pings |
SubagentStop | a subagent finishes | ✅ | validate subagent output |
PreCompact | before context compaction | ✅ | save state |
There are ~30 events in total (task, worktree, permission, MCP-elicitation, file-watch…) — see the official reference.
0 → success. stdout is parsed as JSON for decision control.2 → blocking error. stderr is shown to Claude; the action is blocked (for blockable events). The simplest possible guardrail.stderr shows in the transcript, run continues.Deny a tool (PreToolUse):
{ "hookSpecificOutput": { "hookEventName": "PreToolUse",
"permissionDecision": "deny", "permissionDecisionReason": "why" } }
Force Claude to keep going (Stop / PostToolUse / UserPromptSubmit):
{ "decision": "block", "reason": "what still needs doing" }
Rewrite a tool's input before it runs (PreToolUse):
{ "hookSpecificOutput": { "hookEventName": "PreToolUse",
"updatedInput": { "command": "npm run test:ci" } } }
sudo. Only run scripts you've read.matcher is an exact string / A|B list, or a regex if it contains other characters (^Notebook, mcp__.*).PostToolUse fires after the tool already ran — it can add context or complain, but it can't un-ring the bell. Use PreToolUse to actually stop something.Stop while your hook itself triggers more work can loop — gate on stop_hook_active (recipe 4 does)..claude/settings.json (shared), .claude/settings.local.json (private), or ~/.claude/settings.json (global).Got a hook you actually use? PRs welcome — one recipe per PR, dependency-free if you can, and include the settings.json snippet. See CONTRIBUTING.md.
MIT — do whatever you want. If it saves you a 3am rm -rf, a ⭐ is appreciated.
Claude Code hook that writes a forward-only why-block (decisions, trade-offs, assumptions, limitations) into your PR des
Hook-based token compressor for 5 AI CLI hosts (Claude Code, Copilot CLI, OpenCode, Gemini CLI, Codex CLI). Up to 95% ba
Blocks dangerous git and shell commands from being executed by AI coding agents
One command to install 6 essential safety hooks in 10 seconds — zero dependencies