The Results
The Discovery
The hooks were firing after the write. That’s the whole problem, and it took three rounds of building against a live enforcement scaffold to see it clearly.
PostToolUse hooks execute after Claude Code has already committed the write to disk. By the time the hook runs, the file exists. The violation has landed. The hook can detect it, log it, surface a warning — and it does all of those things correctly — but it cannot undo what already happened.
True enforcement requires PreToolUse: the hook fires before the write is executed. Exit code 2 stops the operation entirely. The file never touches disk. That is a categorically different guarantee, and you only reach it by moving a single setting in settings.json.
This is not a subtle difference. It’s the difference between an alarm that sounds after the window breaks and a lock that keeps it from opening. Both are useful. They are not the same thing.
PostToolUse = audit trail. PreToolUse = actual governance. You need both. They serve different purposes. But if your goal is to prevent violations from landing on disk, only one of them does that.
What the Numbers Actually Mean
The assertion counts vary across rounds because each Opus agent writes a slightly different test suite. This is expected behavior, not a flaw. The agent generates tests against the structure it observes, and that structure can be expressed in different ways across runs.
What the numbers confirm is that the doctrine held consistently across all three agent invocations. The agent was different every run. The structure it tested against held every time. That is the actual guarantee — not that 256 is a magic number, but that 0 failures at any count means the enforcement scaffold survived contact with a live agent.
| Round | Assertions | Notes |
|---|---|---|
| Round 1 | 536 | Initial scaffold build |
| Round 2 | 159 | Refinement pass |
| Round 3 | 256 | Enforcement rebuild — 0 failures |
The Three Intentional Violations
All three were detected. The hooks work. That’s important to establish before anything else — the detection layer is solid. The violations were visible, logged, and surfaced exactly as designed.
The problem was that they needed to move from PostToolUse to PreToolUse for true prevention. Detection tells you what happened. Prevention stops it before it lands. Both matter. They are not interchangeable, and conflating them produces a governance system that feels rigorous while leaving the actual violation window open.
Running intentional violations is the only reliable way to know which category your hooks are in. If you haven’t tested with deliberate bad inputs, you don’t know whether your enforcement is preventing or merely reporting.
Why This Matters for AI Governance
Most teams testing AI governance stop at “did it detect the violation?” That’s a reasonable first question. It’s not the right last question.
This work answered a harder one: at what point in the execution chain does enforcement actually prevent damage? The answer is specific and non-obvious — only if it fires before the write. You don’t find this in documentation. You find it by running against enforcement, watching a violation-triggering write succeed, and then seeing the hook fire on a file that already exists on disk.
That moment — file exists, hook triggered, but it’s too late — is the lesson. It is a moment that only happens in live testing against a real enforcement scaffold, with real agent writes, against rules that are actually being evaluated. You cannot reason your way to it from first principles.
The implication for teams building AI governance: instrument both sides of the write boundary. PreToolUse stops it. PostToolUse records it. Neither one alone gives you the full picture. The timing of your hook is a governance decision, and it should be an explicit one.
The One-Line Fix
Move the hook registration from PostToolUse to PreToolUse in settings.json. That’s it. One key change in one configuration file.
The more durable part of the fix is documentation. The root cause — enforcement timing, what PreToolUse actually does, why exit code 2 is the correct signal — belongs in CLAUDE.md with a date attached. Every enforcement rule should have a traceable history: what it prevents, why it was written, when it was added. Rules without root causes get removed by the next person who doesn’t understand why they exist.
That’s not a theoretical risk. It’s a standard failure mode in any governance system that grows faster than its documentation.