How to block dangerous Bash commands in Claude Code
Most Claude Code accidents are not exotic. They are an ordinary cleanup command with a slightly wrong target, or a "start clean" that throws away uncommitted work. A PreToolUse hook on the Bash tool can catch the common ones before they run.
The failures worth catching
A short list covers most of the real risk in a Next.js project:
- Cleanup that removes too much.
rm -rfaimed at/,~,$HOME,.,..,*, or atsrcorapp. Flag order should not matter. Removing.nextornode_modulesis routine and should stay allowed. - Overwriting a shared branch.
git push --force,-f, or a+refpush.--force-with-leaseis the safer form and can be allowed. - Throwing away uncommitted work.
git reset --hard,git clean -f,git checkout -- .,git restore .. - Secrets landing in the transcript.
cat,greporheadon.envor.env.local, plusprintenvand a bareenv. Once a value is printed it is in the conversation. - Publishing or deploying without you.
npm,pnpm,yarnorbun publish, andvercel --prodorvercel deploy --prod.
How the hook works
Claude Code sends the pending command to your script as JSON on stdin. If the script exits with code 2, the command does not run and the script's stderr is returned to Claude. Write the message so it explains the rule and offers a safer route, for example "ask the user to run the deploy themselves".
// .claude/hooks/guard-bash.mjs (sketch)
import { readFileSync } from "node:fs";
const { tool_input } = JSON.parse(readFileSync(0, "utf8"));
const cmd = tool_input?.command ?? "";
const rules = [
[/git\s+push\b.*(--force\b(?!-with-lease)|\s-f\b)/, "force push is blocked; use --force-with-lease"],
[/git\s+reset\s+--hard/, "reset --hard discards uncommitted work"],
[/\b(npm|pnpm|yarn|bun)\s+publish\b/, "publishing is a human decision"],
];
for (const [re, why] of rules) {
if (re.test(cmd)) {
console.error(`guard-bash: ${why}`);
process.exit(2);
}
}
Register it under PreToolUse with a Bash matcher in .claude/settings.json, then restart Claude Code and confirm with /hooks.
It is pattern matching, not a sandbox
This is the important caveat. A regex guard catches the common mistakes and nothing more. A command rephrased to get around the pattern can slip past, and a chained command can defeat a denylist. Treat the hook as a seatbelt, not a wall:
- Keep Claude Code's permission prompts switched on.
- Keep code review, tests and CI.
- Prefer allow-lists over deny-lists where you can, because an allow-list fails closed.
When it blocks something safe
Every block message should name the hook and the rule, so you can see why it fired. Your options are to run the command yourself in a terminal, or to edit the rule. If you want Claude to deploy, for example, remove the publish and production-deploy rule on purpose, and re-run any test that covered it so the change is deliberate.
Going further
A tested version of this guard, with the rules above and self-tests for each, ships in the Next.js Claude Code workflow pack.
This article was written with AI assistance.