Safety hooks for Claude Code that protect against destructive operations, enforce write boundaries, and gate sensitive actions. Essential when running autonomous workers with --dangerously-skip-permissions. Use when: setting up a new Claude Code environment, configuring safety for dispatch/pipeline workers, or auditing existing hook coverage.
Safety hooks for Claude Code that act as the real enforcement layer when running autonomous workers with --dangerously-skip-permissions.
When using Dispatch or Pipeline to spawn autonomous workers, those workers run with --dangerously-skip-permissions because they're headless (no terminal to prompt for approval). Without hooks, they can do anything -- force-push to main, delete files, write secrets, modify your Claude config.
Guardrails hooks are the safety net. They fire on every tool use regardless of permission mode, blocking or prompting for dangerous actions.
These are generalised, production-tested hooks. Copy them to ~/.claude/hooks/ and wire them into your settings.json.
| Hook | Type | Purpose |
|---|---|---|
bash-precheck.py | PreToolUse (Bash) | Blocks destructive commands, protects sensitive paths, gates commits, prevents credential reads |
write-boundary.py | PreToolUse (Edit/Write) | Restricts file writes to project directory + /tmp, extra restrictions for workers |
Hard deny (always blocked):
git reset --hard, git push --force, git clean -f/etc/, /usr/, /System/, ~/.ssh/, ~/.aws/, ~/.gnupg/npm publish, pnpm publish, yarn publishDROP TABLE, TRUNCATE TABLElaunchctl service modifications, crontab edits~/.claude/hooks/, settings.json, CLAUDE.md)Prompt (ask user, allow or deny):
rm -rf and recursive rmdocker rm, docker stop, docker system prune)kill -9, killallcurl POST / wget --post~/.ssh/, ~/.aws/, ~/.npmrc/, ~/.netrc/)Commit gates (blocks commit until fixed):
main, master, staging)cp examples/bash-precheck.py ~/.claude/hooks/
cp examples/write-boundary.py ~/.claude/hooks/
chmod +x ~/.claude/hooks/bash-precheck.py ~/.claude/hooks/write-boundary.py
Add to your ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/bash-precheck.py" }]
},
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/write-boundary.py" }]
}
]
}
}
Edit the hooks to match your setup:
BLOCKED_TOOLS in bash-precheck.py -- add/remove CLI tools that should use package.json scripts instead of direct invocationPROTECTED_BRANCHES -- change from {"staging", "main", "master"} to your branch namesSENSITIVE_PATHS -- add paths specific to your environmentALWAYS_ALLOWED in write-boundary.py -- add directories workers should be able to write toCLAUDE_ALLOWED_WRITE_PATHS env var -- colon-separated additional write pathsClaude Code hooks fire on every tool use, even with --dangerously-skip-permissions:
Agent calls Bash("git push --force")
-> PreToolUse hooks fire
-> bash-precheck.py reads the command
-> Matches destructive pattern
-> Returns deny with reason
-> Command is blocked, agent sees the error
Three possible outcomes:
permissionDecision: "deny") -- tool blocked, agent sees reasonpermissionDecision: "ask") -- user prompted to allow or denyWorkers running headless with --dangerously-skip-permissions never see "ask" prompts -- they auto-approve. This is why the hooks use "deny" for genuinely dangerous operations and "ask" only for things the user might reasonably want to allow.
The hooks detect worker sessions by checking for /tmp/claude-workers/<session_id>.meta. Workers get stricter rules:
This prevents a dispatched worker from modifying its own safety constraints.