Answers
How do I block dangerous commands in Claude Code?
Use a PreToolUse hook, which Claude Code runs before every tool call whatever the model decides. Immiscible’s hook sends each Bash command, file edit, web fetch and MCP call to a rule a person wrote and refuses, asks or allows; it fails closed.
Use a PreToolUse hook: Claude Code runs it before every tool call, and a hook that exits with code 2 blocks the call whatever the model wanted. Immiscible’s hook sends each Bash command, file write, web fetch and MCP call to a rule a person wrote, then refuses it, asks you with the reasons and an approval link, or lets it through; if Immiscible cannot be reached, it refuses.
#How do I install it?
npx immiscible initIn a Claude Code project (one with .claude/ or CLAUDE.md), init shows the change to .claude/settings.json, asks, and adds one entry:
{
"matcher": "Bash|Write|Edit|MultiEdit|NotebookEdit|WebFetch|mcp__(?!immiscible__(check_action_status|explain_decision|spend_summary|find_waste|unwatched_keys)$).*",
"hooks": [
{
"type": "command",
"command": "node --env-file-if-exists=\"$CLAUDE_PROJECT_DIR/.env\" \"$CLAUDE_PROJECT_DIR/.claude/hooks/immiscible-claude-code-hook.mjs\" || exit 2",
"timeout": 60
}
]
}Beside it, init adds Claude Code deny rules to the same file, in the same diff, so Claude Code itself refuses the worst commands and reading .env, before any hook runs:
"permissions": {
"deny": ["Bash(rm -rf:*)", "Bash(rm -fr:*)", "Bash(sudo rm:*)", "Bash(git push --force:*)", "Bash(git push -f:*)", "Bash(git reset --hard:*)", "Bash(git clean -f:*)", "Read(./.env)", "Read(./.env.*)"]
}The matcher covers every Bash command, file change, web fetch and MCP call, except Immiscible’s own read-only tools (asking Immiscible whether the agent may ask Immiscible would only loop). || exit 2 makes a missing file or a crash block the call instead of letting it through. Without the CLI: npm install -g @immiscible/claude-code-hook and immiscible-claude-code-hook --print-config. Check it with npx immiscible doctor, which also runs the hook against an address nothing answers on to prove it refuses.
#How do I decide which commands are allowed?
Write a rule for the agent’s tool calls that names the domains it may reach, such as github.com, registry.npmjs.org and your own. A shell command that posts to anywhere else is then refused (recipient_not_allowed), and an MCP server not named (mcp:github, or mcp:*) is refused too. In the console: Agents, Agent limits, Add a rule, an action rule for tool.call with those domains. See mandates.
#How is this different from Claude Code’s permissions deny list?
Claude Code’s own permissions.deny rules match tool names and patterns in your settings, and they are the right first layer: use them. The hook adds what a pattern cannot: a decision that knows where a command sends data, asks a named person and waits, counts bursts of calls, applies one rule across Claude Code and your other agents, can be frozen from the console or Slack, and leaves a signed record. The two work together; an allow from the hook still goes through Claude Code’s own permissions.
#Can it stop rm -rf?
Yes, in three layers. Claude Code’s own deny rules, which init adds, refuse rm -rf, force pushes and reading .env before the hook runs. Then, under every Immiscible rule and at every standing, a destructive command (rm, git push --force, git reset --hard, git clean, a history rewrite, curl ... | sh, writing over ~/.bashrc or .git/hooks) asks a person, and so do any git push, a deploy, a publish, a package install from the network, anything run with sudo and anything that names a path outside the project. A command too long to send whole, spelt in escape codes or built from a variable asks too. A secrets file or the environment leaving the machine is refused outright. Under the default rule, General tasks, an intern asks before anything that is not provably read-only; once the agent is past its intern stage, edits, test runs and builds inside its project go ahead (localWrites: "allow-after-intern", shown on the agent’s page, and an owner can set it to ask).
A pattern can still miss a determined disguise, and a program the agent runs can delete files by itself. Run the agent where a mistake is recoverable (a container, a branch, a backup), and keep the hook for what crosses a boundary as well: network calls, MCP tools, payments and data.
#How do I apply it to a whole team?
Commit .claude/settings.json and .claude/hooks/immiscible-claude-code-hook.mjs to the repository, and give each person their own agent key in .env (npx immiscible init per person). For a policy people cannot turn off, Claude Code’s managed settings can carry the same hook entry. Every person’s agent then shares one set of rules, one kill switch and one record in the console.
#What does it not do?
- A new agent starts at the workspace’s starting tier: intern, unless an owner has chosen junior in the console. Under the default rule, an intern asks a person before every tool call that changes something, except read-only calls (
ls,git status, reading a file), which go ahead and are recorded. A junior agent edits files and runs tests and builds inside its project without asking; pushes, deploys, publishes, installs, destructive commands and anything outside the project still ask. The project is the one the hook names (CLAUDE_PROJECT_DIR, or the working directory); an older hook that does not send the project gets a person for its edits. See autonomy tiers for how an agent earns the next rung. - It sees what Claude Code passes to the hook: the tool, its input and the session. It does not read the model’s reasoning.
- Claude Code’s hosted inference is not enforced by the hook; to meter Claude Code’s model spend, point it at the gateway with
ANTHROPIC_BASE_URL=https://immiscible.fly.dev/anthropic. See one budget across providers. - The hook’s tool calls are limited to 300 a minute per agent (
IMMISCIBLE_HOOK_RPMon your own server), apart from the agent’s other requests; past it the hook refuses, because it fails closed. Tool calls also have their own burst line, 200 in 10 minutes by default, after which a person is asked; see how many tool calls before a person is asked.
The full reference is the Claude Code hook.