Skip to content

Steering Claude Code: hooks are law, prose is guidance

How I configured the Claude Code customization layers for a multi-brand insurance monorepo — skills and subagents for the grunt work, hooks for the non-negotiables — and the one lesson that made it reliable: hooks are law; CLAUDE.md and skills are only guidance.

On the multi-tenant insurance monorepo, the repo was consistent enough that an agent could do real work in it — one right way to build a form, encoded everywhere. The question was no longer can an agent contribute, but how do I make it a teammate I can trust on client work?

The aim was concrete: offload the repetitive, error-prone parts — scaffolding a new application form, writing its Playwright tests — and enforce the non-negotiables automatically, rather than hoping the model remembers them while a task drifts and the context window fills up.

Claude Code gives you several places to put instructions, and the mistake I made early was treating them as interchangeable. They are not. Each layer has a different guarantee, and the work was matching each constraint to the layer that actually holds it.

  • Skills — the repeatable procedures. The grunt tasks that have exactly one correct shape: scaffold a new application form, generate its Playwright and visual-regression tests. A skill encodes the procedure once so the agent doesn’t re-derive it (and re-mistake it) every time.
  • Path-scoped rules — the local steering. Guidance that should only load when the agent is working in a particular part of the tree, so the subagents running a task get the right context without carrying the entire rulebook in every prompt.
  • A code-review subagent — the second pass. A dedicated reviewer that runs after the main thread finishes its change, checking the diff against the repo’s conventions before it reaches a human. The developer still reviews — but the obvious stuff is already caught.
  • Hooks — the enforcement. Deterministic code that runs on every tool call and can block it. This is where the genuine non-negotiables live: a hook fails any write that introduces a server action not wrapped in our Sentry instrumentation, so observability isn’t a thing the agent might remember — it’s a thing it cannot skip.

The lesson: hooks are law, everything else is guidance

Section titled “The lesson: hooks are law, everything else is guidance”

The single distinction that made the whole setup reliable is the split between advisory guidance and hard enforcement.

CLAUDE.md and skills are guidance. The model reads them, weighs them against everything else in its context, and usually complies. Hooks are law: they are ordinary code that runs regardless of what the model is “thinking,” and they either pass the action or block it. There is no “usually.”

So reliability came from putting each constraint on the layer whose guarantee matches how much it matters:

  • Repeatable procedures → skills. If the agent occasionally deviates, a human catches it in review; the cost of a miss is low.
  • Genuine non-negotiables, like wrapping server actions in Sentry → hooks. A miss here ships a blind spot to a client, so “usually” isn’t good enough. It has to be enforced deterministically.

My early version got this exactly wrong. I encoded the hard constraints as prose in CLAUDE.md — “always wrap server actions in Sentry instrumentation,” stated plainly, near the top.

It worked, right up until it didn’t. The agent honored those rules while the task was simple and the context was roomy. Then a task got complex, or the context filled up, and the constraints quietly dropped — not with an error, but with a plausible-looking diff that just happened to skip the instrumentation. A prose rule competes for the model’s attention with everything else in the window, and under pressure it loses.

That failure is what forced the layer distinction. A non-negotiable stated as prose is a hope. The same non-negotiable expressed as a hook is a guarantee. Once I’d been burned by the first, I stopped writing hard constraints as sentences.

This isn’t only client work — I run the same layering on Loopy Demos, where the whole setup is open to inspection in the repo’s .claude/ directory:

  • Path-scoped rules in .claude/rules/ load automatically when the agent touches src/content/ — house style, tone, and format conventions that are irrelevant everywhere else.
  • A PreToolUse hook blocks any content write containing an em or en dash, because the house style bans them. It’s a fifteen-line Python script that exits non-zero and hands the reason back to the agent to retry — deterministic, and impossible to argue with.
  • A subagent (demo-copywriter) researches a pedal and drafts its page copy, dispatched by a /copy command and then reviewed in the main loop — the same “delegate the draft, review the result” shape as the client project’s review subagent.
  • Another hook blocks slow “sanity” production builds, so the agent can’t burn minutes on a check that a faster type-check already covers.

The through-line is identical: procedures in skills and subagents, non-negotiables in hooks.

I expected this to be a prompting problem — find the right wording and the agent behaves. It turned out to be a documentation and information-architecture problem. Deciding what loads when, and what’s advisory versus enforced, mattered far more than clever phrasing.

Writing the approach up as a guide is what actually crystallized the framework. Once I had to explain why the Sentry rule was a hook and the form-scaffolding was a skill, the principle underneath — match the layer’s guarantee to the constraint’s importance — became obvious, and the rest of the setup fell into place around it.

That’s the transferable part. The specific skills and hooks are particular to one insurance monorepo; the discipline of putting each constraint on the right layer is what makes an agent a teammate you can hand real work to.