Free tool
Keep Claude Code from deleting production.
Tick the tools your agent deploys with. Get the permission rules for settings.json, a hook script that also blocks the forms those rules miss, and a section for CLAUDE.md.
Merge this into .claude/settings.json in your project. If the file already has permissions or hooks, add these entries to its lists. The settings run the hook script, so save that too. Until you do, every shell command is blocked.
{
"permissions": {
"deny": [
"Bash(git push --mirror *)"
],
"ask": [
"Bash(git reset --hard *)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/guardrails.sh\" || exit 2",
"onFailure": "block"
}
]
}
]
}
}
What it blocks
Each tool gets the commands that delete something you can't get back: a deployed app, a project, a volume, a database or its tables. railway down, fly apps destroy, heroku pg:reset, docker volume rm, kubectl delete namespace, terraform destroy and DROP DATABASE through psql are denied outright. So are the flags that skip a confirmation, like vercel remove --yes and terraform apply -auto-approve. A force push to main, a force push with --all, and git push --mirror are denied too.
A few commands are part of normal work but still change real things, so they ask instead: terraform apply, vercel remove without --yes, and git reset --hard. Claude Code stops and shows you the command, and you say yes or no.
Each command gets a rule for every spelling its CLI documents. railway delete also answers to railway remove and railway rm, and railway project delete to railway projects rm. Fly runs as fly or flyctl, and OpenTofu's tofu takes the same commands as terraform. Each CLI's docs are listed under Sources. The hook also catches Railway run through npx @railway/cli or pnpm dlx @railway/cli.
Install it
- Save the hook script as
.claude/hooks/guardrails.shin your project. It needsjqon your machine. - Merge the settings into
.claude/settings.json. If the file already haspermissionsorhooks, add these entries to the lists it has. - Paste the section into your
CLAUDE.md. - Start a new Claude Code session and ask it to run one of the blocked commands. It should refuse with the hook's reason.
Commit all three files, and everyone who opens the project in Claude Code gets the same guard.
Why a hook as well as permission rules
Claude Code's permission rules match the command text, and its docs say a rule "isn't a security boundary around the program". Bash(rm *) stops rm -rf build/ but not /bin/rm -rf build/ or bash -c 'rm -rf build/'. Deny rules do catch a command inside a chain like cd app && railway down.
The hook reads every command before it runs. It joins lines that end in \, splits the command at ;, &, | and new lines, and looks for the guarded words anywhere in each part. So it also catches a full path, bash -c, flags in another order or run together like -af, and options between the words like kubectl -n prod delete ns. When it finds one it exits with code 2, which blocks the command, and tells Claude why. An ask match prints the JSON that makes Claude Code ask you instead. The settings also set onFailure to block, so on Claude Code v2.1.295 or later a hook that can't start or times out blocks the command rather than letting it run.
The CLAUDE.md section tells the agent the same rules up front, so it stops and asks you before it reaches for a blocked command. Claude Code treats CLAUDE.md as context, not as a rule it enforces. That's why the section ships with the other two.
What it can't stop
Permission rules and hooks run on your own machine, and they only read the text of a command. An agent with a shell can work around them: a command built at run time, a script it writes and runs, an alias, or a CLI the guard doesn't know. A plain git push --force while main is checked out names no branch, so the hook can't tell it from a push to a feature branch. The hook also blocks echo railway down, grep 'railway down' and a commit message that names a blocked command, because it matches text and doesn't parse the shell. Treat it as a guard against mistakes, not against an agent set on getting through.
The real check belongs where the deploy happens. Give the agent a token that can't touch production, or put production behind a step only a human can take.
Where Ployz fits
I build Ployz, and it puts that check on the server. When a coding agent saves or deploys a change that would remove a running service, delete a volume, detach a volume or remove a domain, Ployz refuses with approval_required and opens an approval for you. The same goes for removing a server, or draining the last server a global service runs on. It works the same from the CLI, from ployz mcp, and from an agent connected to Ployz Cloud. You approve or deny it in Ployz Cloud, and no setting turns it off. An agent connected to Ployz Cloud can't approve its own request. An agent running the ployz CLI with your login or a token can, so give it a short-lived token you can revoke.
Keep this guard as well. Commands like git push --force and terraform destroy never go through Ployz, so only the guard sees them. To try Ployz with your own agent, paste Read ployz.dev/agents.md and deploy this repo to my server. into Claude Code, or start free on your own servers.
Questions
What are Claude Code hooks?
Hooks are commands Claude Code runs at fixed points. You set them in ~/.claude/settings.json for all your projects, .claude/settings.json for one project, .claude/settings.local.json for just you, or managed policy settings for a whole organization. A PreToolUse hook with the matcher Bash runs before every shell command Claude wants to run and gets the command as JSON on stdin. It blocks the command by exiting with code 2, and Claude gets the hook's stderr as the reason. A hook that exits 0 and prints JSON with permissionDecision set to deny blocks it too. Any other non-zero exit lets the command run, so a guard hook must exit 2 or print that JSON.
How do Claude Code permissions work?
Claude Code checks the rules in the permissions block of .claude/settings.json deny first, then ask, then allow, and the first match decides. A rule like Bash(railway down *) matches the command and anything after it. Deny and ask rules also catch the command inside a chain like cd app && railway down. They match the command text, though, so /usr/bin/railway down or bash -c 'railway down' gets past them. The hook catches those.
Where does the CLAUDE.md file go?
At the root of your project, as CLAUDE.md or .claude/CLAUDE.md. Claude Code reads it at the start of every session, along with a CLAUDE.local.md if you keep one. If the project has an AGENTS.md and no CLAUDE.md or CLAUDE.local.md, Claude Code v2.1.277 or later reads the AGENTS.md instead. CLAUDE.md is context, not a rule Claude Code enforces, which is why this tool pairs it with deny rules and a hook.
Can Claude Code get around a deny rule or a hook?
Yes, if it tries. Both run on your machine and read the command text. A command built at run time, a script file, or a program the guard doesn't know about runs anyway. Use them to stop mistakes, and keep production behind a token the agent doesn't have or an approval on the server.
Why does the hook need jq?
The hook reads the command out of the JSON Claude Code sends, and jq decodes it exactly, escapes included. Without jq the hook can't read the command, so it blocks every shell command and says jq is missing rather than letting them all through.
Sources
- Claude Code: configure permissions
- Claude Code: hooks reference
- Claude Code: CLAUDE.md and memory
- Railway CLI: down
- Railway CLI: delete
- Railway CLI: service
- Railway CLI: volume
- Railway CLI: environment
- Railway CLI: bucket
- flyctl apps destroy
- flyctl volumes destroy
- Vercel CLI: remove
- Vercel CLI: project
- Heroku CLI commands
- Docker: system prune
- Docker: volume rm
- Docker: compose down
- Docker: container rm
- Docker: compose rm
- Kubernetes: namespaces
- Kubernetes: persistent volumes
- Helm: uninstall
- PostgreSQL: DROP DATABASE
- PostgreSQL: dropdb
- MySQL: DROP DATABASE
- MariaDB: mariadb-admin
- Terraform: destroy
- Terraform: apply
- Git: push
- Git: reset
- Ployz CLI
- Ployz: destructive actions ask first
- Ployz: connect an agent
Deploy with Claude Code and other coding agents
See how it worksFacts checked 2026-10-17.