Guardrails for Claude Code

Intercept the
agent loop.

stdinhookexit 0 · pass  /  exit 2 · block

Six small programs that watch what Claude Code is about to do — and step in. Block a destructive command. Refuse to read a secret. Stage and format an edit. Ping you on Slack. A few hundred lines each, exhaustively tested, ready to copy.

06Hooks
420Tests pass
~80msPer call
0 · 2Exit codes
~/repo — claude
# Claude proposes an action… please clean up this directory tool Bash command rm -rf ~ PreToolUseblock-dangerous-commands ⊘ blockedexit 2 "rm -rf on home directory"
The contract

No SDK. Just a wire protocol.

plain executables
any language
the exit code decides
01 — in

Claude sends stdin

On every lifecycle event, Claude Code pipes a JSON payload — the tool, its input, the session — to your hook's standard input.

02 — decide

Your hook runs

A few hundred lines in Node, Python, or shell. Inspect the payload, check it against your rules, and choose a verdict.

03 — out

Return exit 0 or exit 2

0 lets the tool run. 2 blocks it and hands your message back to Claude as guidance.

Where they attach

Nine stops in the loop.

● covered here
○ open to contribute
SessionStart
prepare context
UserPrompt
inspect / inject
PreToolUse
block or pass
— tool —
execution
PostToolUse
react / log
Notification
alert you
Stop
finalize
covered by this repo — PreToolUse, PostToolUse, Notification ○ everything else is open for contribution
The catalog

Six hooks, tested.

node + python
MIT licensed

01block-dangerous-commands

Refuses rm -rf ~, fork bombs, curl | sh, dd to a raw disk, and the long tail of catastrophes that look harmless in a one-liner. Three tunable safety levels decide where the line sits.

block-dangerous-commands
tool Bash command rm -rf ~ ⊘ blockedexit 2 "deletes home directory"

02protect-secrets

Stops Claude from reading, editing, or quietly exfiltrating .env files, SSH keys, AWS credentials, kubeconfig — anything load-bearing. Catches the obvious paths and the clever ones: a cat piped to curl is still exfiltration.

protect-secrets
tool Read path .env.production ⊘ blockedexit 2 "sensitive credential file"

03git-safety

Branch-aware guardrails: blocks commit, merge, rebase, reset and push while you're on main, plus destructive gh CLI calls like gh repo delete. Complements block-dangerous-commands rather than overlapping it.

git-safety
branch main command git push --force origin main ⊘ blockedexit 2 "force-push to a protected branch"

04auto-stage

After Claude edits or creates a file, runs git add on it — so git diff --cached becomes the canonical "what did the agent just do" view. One less accounting step in your loop.

auto-stage
# after Edit succeeds git add src/api.ts ✓ stagedexit 0 review → git diff --cached

05format-code

Runs the right formatter on whatever Claude just wrote — ruff for Python, prettier for JS/TS/HTML/JSON/Markdown/YAML. The agent's output lands already clean, so diffs stay about logic, not whitespace.

format-code
# after Write app.py ruff format app.py ✓ formattedexit 0 1 file reformatted

06notify-permission

Pings a Slack channel when Claude is stuck on a permission prompt or has gone idle waiting on you. Start a long agentic run, switch tabs, and trust you'll be pulled back when it actually needs a human.

notify-permission
event permission_prompt tool Bash (sudo) ↗ slack#claude-runs "Claude needs you in repo X"

+ event-logger — a Python diagnostic that dumps every event's JSON so you can see an event's shape before you write a hook against it. The thing you reach for first.

Apparatus

Copy. Register. Restart.

~60 seconds
no build step
STEP i

Place the script

Anywhere your shell can chmod +x. Convention is ~/.claude/hooks/.

$ mkdir -p ~/.claude/hooks
$ cp hook-scripts/pre-tool-use/block-dangerous-commands.js \
     ~/.claude/hooks/
STEP ii

Register it

In .claude/settings.json, bind the hook to an event and matcher.

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "node ~/.claude/hooks/block-dangerous-commands.js"
      }]
    }]
  }
}
STEP iii

Restart Claude Code

The hook is live. Ask Claude to do something reckless and watch it refuse.

$ claude
 please rm -rf ~
 hook blocked:
  deletes home directory.
Marginalia

Tradeoffs, in brief.

Safety

Three levels of paranoia

criticalcatastrophes only — rm -rf ~, fork bombs, dd
high+ risky — force push, secrets, hard reset★ default
strict+ cautionary — any force push, sudo rm
Runtime

Match language to event rate

Bashtiny guards10–20ms
Nodeper-tool, JSON-heavy50–100ms
Pythonsession events200–400ms
Exits

Two numbers do the work

0pass — let the tool run
2block — message goes back to Claude
·other — non-blocking warning, logged
Open

Forthcoming hooks.

contributions welcome
see CONTRIBUTING.md
07protect-tests — block test deletion or disablingPreToolUse
08context-snapshot — preserve before compactionPreCompact
09session-summary — generate a digest on StopStop
10ntfy-notify — free mobile pushNotification
11discord-notify — webhook alertsNotification
12cost-tracker — tally tokens, estimate costPostToolUse
13rules-injector — auto-attach CLAUDE.mdUserPromptSubmit
14rate-limiter — cap tool calls per minutePreToolUse

Put a hook in the loop.

Six tested guardrails, MIT licensed, no framework to learn. Copy one and restart Claude Code — you're protected in under a minute.