Choosing Between a CLAUDE.md Rule and a Hook
A team maintains four rules they want Claude Code to follow in their repo.
For each one, decide whether it belongs in CLAUDE.md, as a PreToolUse/
PostToolUse hook, in settings.json permissions, or some combination —
and justify the choice:
- "Prefer composition over deep inheritance hierarchies in the Python codebase."
- "Never allow a commit that includes a file matching
*.pemor*_key.json." - "Run
ruff formatafter every file edit so formatting stays consistent." - "Database migrations must be reviewed by a human before being
applied to staging — Claude should never run
alembic upgradeitself."
1. "Prefer composition over inheritance" → CLAUDE.md.
This is a design-taste guideline, not a rule with a crisp pass/fail
check. There's no reliable, cheap, deterministic way to detect "did
this diff use too much inheritance" the way you can detect "does this
filename match *.pem." It requires judgment about the specific
code being written, which is exactly what CLAUDE.md is for — shaping
the agent's default inclinations, understanding that it's advisory
and won't be followed with 100% consistency, but that's an acceptable
trade-off for a stylistic preference. Trying to enforce this with a
hook would mean writing a static-analysis check elaborate enough to
judge design quality, which is both hard to build and the wrong tool
for a preference rather than a hard constraint.
2. "Never allow a commit with *.pem or *_key.json" → a hook
(PreToolUse on Bash, matching git commit) and/or permissions.deny.
This is a textbook deterministic-guardrail case: it has an exact,
cheap, mechanical check (does the diff include a file matching this
glob), the cost of a false negative is severe (a leaked private key),
and there's no reason to trust the agent's judgment over a hard
check when a hard check is this easy to write. A PreToolUse hook
on Bash that inspects the command for a git commit and greps the
staged files for the pattern, denying the call if it matches, gives
an airtight guarantee independent of how the agent got talked into
staging the file. permissions.deny can also block Read of files
matching those globs outright, which additionally stops Claude from
ever seeing key material it shouldn't handle in the first place —
the two mechanisms are complementary here, not alternatives.
3. "Run ruff format after every file edit" → a PostToolUse
hook on Edit/Write.
This is mechanical, applies after every matching tool call with no
judgment involved, and is exactly the "fires at a fixed lifecycle
point regardless of what Claude decides" case hooks are designed
for. Putting this in CLAUDE.md as an instruction ("please run ruff
format after edits") would work most of the time but not
reliably — the agent might forget in a long session, get distracted
mid-task, or simply not judge it necessary for a trivial one-line
change. A PostToolUse hook matching Edit|Write that shells out to
ruff format <file> runs every time, with zero dependence on the
agent remembering.
4. "Migrations must be human-reviewed before touching staging;
Claude should never run alembic upgrade itself" → a hook (deny) as
the enforced layer, with a CLAUDE.md note explaining the why for
the agent's own judgment.
Like case 2, this is a hard constraint where the cost of a mistake
(an unreviewed schema change hitting staging) is high enough that
advisory instruction isn't sufficient — a PreToolUse hook denying
any Bash call matching alembic upgrade enforces it unconditionally.
But it's worth also stating the rule and its reasoning in CLAUDE.md
("migrations require human review before staging — this is enforced
by a hook, but you should also never propose running upgrade
directly"), because that helps the agent choose a better path
proactively (e.g., generating the migration and telling the human to
review and apply it) rather than just repeatedly hitting a hard
block and having to work around it. The hook is the safety net; the
CLAUDE.md entry is what makes the agent's first attempt already
correct instead of needing the net to catch it.
General principle across all four: the deciding question is "does this rule have a cheap, reliable, mechanical check, and is the cost of the agent getting it wrong high?" When the answer to both is yes, use a hook (and/or permissions). When the rule requires judgment about specific code or context, or the cost of an occasional miss is low, CLAUDE.md is the right — and cheaper to build — mechanism. The two are frequently used together rather than as strict alternatives.
Share this question