An AI coding agent on your laptop runs with your shell. It can rm, it can curl secrets | nc, it can write to .github/workflows. The native guardrail in Claude Code is an allowlist: you pre-grant a set of permitted tools and it auto-denies the rest. That works, but it's blunt. It decides on the tool name, not on what the call is about to do. Bash is either allowed or it isn't.

I wanted the gate to read each action instead. Read-only stuff runs. A test run runs. A write inside the directory I scoped runs. A force push, a package install, a write to .env, a command I don't recognize: stop and ask me.

The mechanism for that is a PreToolUse hook plus a small classifier. Both are about 60 lines of the part that matters. Here's how they fit together.

How a PreToolUse hook works

Claude Code lets you register a hook that fires before any tool call. The hook is just a command. Claude pipes a JSON event on stdin, then blocks on your process until it exits. What you print on stdout decides what happens next.