Every rule I care about was in my CLAUDE.md. "Never git add -A." "Never print .env." "Check the URL before saying a deploy is live." "Never kill processes by pattern." Claude Code broke each of them at least once in a few months.
I don't think it ignores them. I think they lose.
Why rules lose
CLAUDE.md is loaded at the start of the session as part of the context. Then the session fills up: files read, command outputs, errors, your corrections. By the time Claude decides how to commit, your rule is one sentence among tens of thousands of tokens, and the strongest signal in front of it is "the user wants this committed". git add -A is the shortest way to do that.
Longer sessions, more pressure, more skipped rules. Writing the rule in capitals helps a bit. Repeating it helps a bit. Neither turns "usually" into "always".
Hooks don't compete
A hook isn't in the context. It's a script Claude Code runs before (or after) a tool call, outside the model. It sees the exact command and decides. It doesn't get tired at the end of a long session.
The split I use now:
| Goes in CLAUDE.md | Goes in a hook |
|---|---|
| Style, conventions, preferences | Anything that destroys work |
| How the project is organised | Anything that leaks a secret |
| What to do when unsure | Anything you can't undo |
| Things where "usually" is fine | Things where "usually" isn't |
The test: if Claude skips this rule once, can you fix it in a minute? Then CLAUDE.md is fine. If it costs you an afternoon, a leaked key or a client's data, it needs a hook.
Write the reason for Claude
A blocked command comes back to Claude with whatever your hook printed. Use it. "Blocked" makes it retry or stall. "git add -A sweeps in other sessions' work, add files by name" makes it fix the command on the next try. Most of my blocks resolve themselves in one step, without me reading them.
Start small
Take your CLAUDE.md, find the rules where "usually" isn't good enough, and turn each into a ten-line hook. Some of mine are free as a plugin, seatbelt-lite: the deploy check, the Supabase view rule and three shell rules. The full set, with a secret guard, more SQL rules and a longer shell list, is Seatbelt.
Top comments (1)
Returning the reason and safe alternative from the hook ("git add -A sweeps in other sessions' work, add files by name") is what lets the model adjust on the next turn instead of retrying the same blocked call.
One seam to watch when moving critical rules out of CLAUDE.md into PreToolUse hooks is command indirection. A pattern matcher watching Bash invocations can miss a chained subshell, an inline script written via Edit, or a flag alias like git add --all. Backing the tool hook with a standard git pre-commit check that inspects staged file paths and diff size catches anything that slips past string matching.