Paths Subjects Questions Quizzes Pricing Search
Advanced Open Free

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:

  1. "Prefer composition over deep inheritance hierarchies in the Python codebase."
  2. "Never allow a commit that includes a file matching *.pem or *_key.json."
  3. "Run ruff format after every file edit so formatting stays consistent."
  4. "Database migrations must be reviewed by a human before being applied to staging — Claude should never run alembic upgrade itself."
Solution

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

← Back to CLAUDE.md and Context Configuration practice

We use cookies for product analytics to improve OmniAtlas. See our Privacy Policy.