Knaph

Keeping an Agent in Check26 of 37

A Hook That Says No

This exercise is optional, for readers who know a little Python. In [Hooks: Rules That Always Run](hooks-rules-that-always-run) you saw a hook that blocks rm -rf, and in [Asking Permission](asking-permission) you saw rules like allow npm run test * and deny git push *. Here you'll write the heart of a hook that applies such rules to every shell command an agent wants to run. ## The input First the rules, one per line: deny or allow, a space, then a pattern. Then a line with ---. Then the commands the agent wants to run, one per line: ``text deny rm -rf * deny git push * allow git * allow npm run test * --- git status git push origin main ls -la ` In a pattern, * stands for any characters, or none. Everything else must match exactly, and a pattern must fit the **whole** command, from its first character to its last. The function matches(pattern, command) is already written for you and does exactly this. ## Your task Write decide(command, rules). It works the way Claude Code's rules do: 1. Check the deny rules first, in the order they were written. The first one that matches **blocks** the command, whatever any allow rule says. 2. If no deny rule matches, check the allow rules in order. The first one that matches **allows** the command. 3. If no rule matches at all, the agent must **ask** you. It returns the verdict and the rule that decided. The rest of the program prints one line per command: `text ALLOW git status (allow git *) BLOCK git push origin main (deny git push *) ASK ls -la (no rule) ` One surprise to notice: the rule deny rm -rf * does **not** block sudo rm -rf /tmp/x, because a pattern must match from the start of the command, and this one starts with sudo. (echo rm -rf /`, which only prints some text, isn't blocked either, and rightly so.) Simple patterns are easy to slip past, which is one reason real agents use rules and hooks together, with a sandbox underneath.

Sign in to try this exercise.