A Claude Code agent without hooks is an intern with sudo. It will not be malicious. It will be confidently wrong about rm -rf, git push --force, or terraform destroy. You will find out at 2 AM.
Hooks are the seatbelt. They run before or after every tool call, inspect the payload, and can refuse the call by exiting with status 2. They are bash scripts. They are 20 to 70 lines each. You can have them in production today.
These seven are the set I run on every repo. The Hooks Builder generates all of them with a checkbox.
1. Block rm -rf outside /tmp
The unspectacular one. The agent will not type rm -rf / on purpose. It might type rm -rf $TEMP_DIR where $TEMP_DIR is empty. Same outcome.
The hook checks every rm -rf against an allowlist of paths (your /tmp, your gitignored build directories, that is it). Anything else is blocked.
2. Block git push --force on protected branches
Force-pushes to feature branches are fine. Force-pushes to main, master, prod*, or release/* are the kind of incident you have to write a post-mortem for.
The hook reads the push command, checks the target branch against a regex, and refuses if it matches a protected pattern.
3. Block terraform destroy and friends
Infrastructure teardown is human-gated, period. The hook refuses:
terraform destroykubectl delete namespaceaws s3 rbaws ec2 terminate-instancesgcloud sql instances delete
Add the verbs you care about. The hook is one grep -E away.
4. 200K-token per-task budget guard
The runaway loop story. The agent gets into a retry cycle on a flaky test, iterates 80 times, and the bill arrives 14 days later. The hook tracks cumulative tokens per task and refuses new tool calls past the cap. 200K tokens covers most legitimate work with margin. Sessions that approach the cap should stop and ask the human to slice the task smaller.
5. AWS profile allowlist (the $47K hook)
The one that saved me from a five-figure NAT Gateway incident in this post.
The hook reads the active AWS_PROFILE, compares it against a per-repo allowlist file (.claude/aws-allowed-profiles), and refuses any AWS mutation if the profile is not on the list. Bonus: a second file lists resource names that exist in both staging and prod, and the hook refuses any command that mentions one until the human confirms the profile explicitly.
This is the most valuable hook in the set if you have any cloud access from a Claude Code session.
6. Log every tool call
Audit trail. Append every tool call name and timestamp to .claude/audit.log. When something weird happens, you have the receipt. It is 10 lines of bash and zero performance cost.
7. Confirm edits to .github/workflows and similar
CI/CD config is a privilege escalation surface. A workflow edit can run code on GitHub-hosted runners with whatever secrets your repo has. Refuse Edit and Write tool calls to .github/workflows/*.yml unless the human confirms.
Same idea applies to:
terraform/migrations/(anything that changes the schema)kustomization.yaml,Chart.yaml, helm values files- IAM policy files
What this looks like in settings.json
The wiring is a single hooks block in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": ".claude/hooks/block-rm-rf.sh" },
{ "type": "command", "command": ".claude/hooks/block-force-push.sh" },
{ "type": "command", "command": ".claude/hooks/block-terraform-destroy.sh" },
{ "type": "command", "command": ".claude/hooks/aws-account-guard.sh" }
]
},
{
"matcher": ".*",
"hooks": [
{ "type": "command", "command": ".claude/hooks/budget-guard.sh 200000" }
]
},
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": ".claude/hooks/confirm-workflows.sh" }
]
}
],
"PostToolUse": [
{
"matcher": ".*",
"hooks": [
{ "type": "command", "command": ".claude/hooks/log-tool-use.sh" }
]
}
]
}
}
Drop this into .claude/settings.json, put the seven scripts in .claude/hooks/, mark them executable with chmod +x .claude/hooks/*.sh, and restart your Claude Code session. The hooks fire before any matching tool call.
What I am not telling you
I am not telling you to write all seven from scratch tonight. I am telling you to look at your repo, decide which two or three are most relevant, and ship those this week. The builder will write the rest when you are ready.
Every audit I run that ends with a near-miss being avoided has hooks. Every audit that ends with a five-figure incident does not. The difference between the two is a half day of work.
Receipts
- 7 hooks running on 14 of my own repos and 5 client repos.
- Mean hook firings per developer per week: 11 (mostly the budget guard and the rm-rf catcher).
- Real incidents prevented in the audit cohort over 90 days: 4.
- Time to deploy the full set on a new repo: 45 minutes including testing.