We use Claude Code at OneOnMe, an AI coding assistant that runs in our terminal. I have 80+ rules written into its memory. Three tool calls into any non-trivial task, it decides to ignore half of them.
Most published advice for shaping it says the same thing: write a CLAUDE.md with your rules and the assistant will follow them. I tried that. Then I layered on dozens more rules in what’s called feedback memory, accumulated across sessions. Things like verify state before describing it to a sub-agent and snapshot live resources before PUT/DELETE. Read in isolation, they’re a complete operational discipline.
The problem isn’t that the rules are wrong. It’s that Claude treats them as advice and then exercises judgment about which advice applies right now. That’s the right behavior for product decisions. It’s the wrong behavior for operational ones, things like verifying state before describing it, reading a file before editing it, running review before commit. Those shouldn’t be subject to model judgment. They should just happen.
That isn’t Claude’s failure. It’s mine, for treating I documented it as the system enforces it.
What actually enforces
Claude Code has a feature called hooks: shell scripts the harness runs at specific points in the assistant’s lifecycle. A PreToolUse hook fires before any tool runs, and a non-zero exit blocks the tool call. A Stop hook fires when the assistant tries to end a turn and can refuse the stop until a condition is met.
Hooks are not advice. They’re gates. They remove the decision from the model.
The pattern I keep coming back to: anything I noticed myself skipping mid-task got promoted from a memory rule to a hook. The memory often still exists, but it’s redundant now. The hook makes it true.
Some of what we run at OneOnMe:
- Investigation before edits. A
PreToolUsehook on theEdittool blocks the first edit of a session unless an investigation step (file read, grep, web fetch) ran first. - Review before commit. A
Stophook checks for unreviewed code in the diff and refuses to release the turn until/code-reviewruns. - Per-gate bypass files. When skipping a gate is genuinely the right call, we drop a reason file at
/tmp/cc-gates/<session>/skip_<gate>. The gate reads it before blocking. Skipping is allowed, but it requires us to type a reason. Judgment is back in the loop, but only when we explicitly invoke it.
A commit, in slow motion
Here’s what runs when we commit a UI-touching change:
- Pre-edit: investigation hook checks for prior file reads. Blocks if none.
- Mid-edit: parallel Claude Code sessions write to a
.claude/scheduled_tasks.lock. If it exists, we’re warned before any exclusive operation. - Pre-commit: lensed
/code-reviewauto-dispatches. Lenses (security, data-integrity, migration, performance) activate based on which files changed. Domain-specific agents fire alongside it. - Visual changes:
/visual-qadispatches with a screenshot and a design-philosophy file. The agent never sees source code or class names. Only the rendered output. That information firewall is the point: the agent can’t rationalize implementation choices it can’t see. - Stop: if any gate’s bypass file is missing or stale, the
Stophook refuses the turn until we either run the gate or write a fresh skip-reason.
None of this is in our prompt. It’s all infrastructure. Claude doesn’t choose to run these checks. It can’t.
The tradeoff
Hooks are harder to evolve than rules. Adding a rule takes one paragraph. Adding a hook means a shell script, a matcher pattern, an entry in settings.json, and accepting that the hook will run on every applicable event until we remove it. Most things don’t need to be hooks. Most things can stay as a rule that gets followed most of the time.
But for operational rules, the things that shouldn’t be subject to model judgment, hooks are the only structure that holds. The question is which of your rules you want Claude to decide about, and which you want to make impossible to skip.