DEV Community

Piekwerk
Piekwerk

Posted on

Config files vs hooks: where agent enforcement actually belongs

A rule in CLAUDE.md is a request. A hook is a guarantee. Mixing these
two up is the most common config mistake I see: teams write "never
commit secrets" as prose, then act surprised when a secret gets
committed. This post is the split I use: what belongs in prose, what
belongs in a hook, and the four-level ladder that decides.

The difference in one sentence

Prose config changes what the model tries to do. Hooks and CI change
what is possible to do. Anything that must never happen does not
belong in prose, because prose is sampled, not executed.

The four-level enforcement ladder

For any rule you want, ask which level it actually needs:

Level 1  Linter/types/CI      deterministic, catches most code issues
Level 2  Hooks (PreToolUse)   blocks the command/file before it runs
Level 3  Scoped rules         prose, loads only for matching files
Level 4  Always-on config     prose, worth spending context on every call
Enter fullscreen mode Exit fullscreen mode

The ladder is ordered by reliability, and the reliable end is free of
tokens. Every rule you can push down a level saves context and removes
a failure mode.

What belongs in hooks

Concrete examples from my own setup:

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "command": "block-dangerous.sh" },
      { "matcher": "Edit|Write", "command": "protect-env.sh" }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Destructive commands: rm -rf outside a target dir, force-push to main, drop table. A regex in a PreToolUse hook blocks these with certainty. In prose, "be careful with rm" works maybe nine times out of ten, and you only hear about the tenth.
  • File protection: .env, keys, generated files. The hook returns a block reason and the agent adapts. No token spent until the moment of the attempt.
  • Format gates: prettier/eslint run on save via hook rather than "always format your code" in config. Deterministic, zero context.

What belongs in prose config

The things hooks cannot decide, because they need judgment:

  • Definition of done: what "verified" means for this repo. A hook can run tests; only prose can say what the agent should do when they fail (fix, not revert-and-move-on).
  • Priorities: smallest diff wins, investigate before fix, stop after verify. These shape choices, not actions.
  • Context the model cannot infer: why the weird workaround in auth exists, which parts of the codebase are generated, who consumes the API.

The trap: putting CI-fixable rules in prose

The clearest smell is any line in CLAUDE.md that starts with "always
run" or "never commit". If CI can check it, CI should check it.
Every prose duplicate of a linter rule is a line of context spent on
something the machine already guarantees, and a contradiction waiting
when the two drift.

Our kits keep prose for judgment and push everything checkable down
the ladder. That is also why the validator rejects placeholder
instruction files: prose that restates the linter is worse than no
file, because it trains you to stop reading your own config.

When you have no hook system

Older setups (plain .cursorrules, bare AGENTS.md) have no hook layer.
The ladder still applies, one rung lower: CI and linters carry the
hard guarantees, prose carries judgment, and the "hard guarantees in
prose" gap is covered by a pre-commit hook in git itself, which every
setup has.

Related reading


Our kits ship this split pre-built: prose for judgment, hooks and validator rules for guarantees, in AgentConfig Studio. Try the approach on Next.js first: free sample kit (MIT).

Top comments (0)