Guidelines vs Enforcement: What CLAUDE.md Can and Cannot Do
CLAUDE.md contains suggestions — Claude follows them, but they can be overridden at runtime. True enforcement comes from two mechanisms: permissions (allow/deny lists for tools) and hooks (shell commands that run at lifecycle events). This article explains the difference and tells you which to use for which purpose.
Quick Reference
- →CLAUDE.md instructions are strong suggestions — Claude follows them but won't refuse tasks that contradict them
- →Permissions (allow/deny) are true enforcement — they block tool calls at the system level
- →Hooks are enforcement at the lifecycle level — they run shell commands before/after tool calls
- →Use CLAUDE.md for: context, conventions, style, patterns
- →Use permissions for: blocking dangerous commands, preventing file access outside scope
- →Use hooks for: automated checks that run on every edit (linting, type checking, test runs)
- →A 'NEVER' in CLAUDE.md is a strong guideline — a denylist entry is an actual block
- →Hooks can abort operations — a hook that exits non-zero stops the tool call from completing
In this article
The Confusion: Why CLAUDE.md Is Not Enough for Rules
Many engineers write CLAUDE.md rules like: 'NEVER commit to the main branch directly' or 'NEVER run database migrations in production'. These feel like hard rules. They are not. CLAUDE.md instructions are strong context that Claude uses to shape its behavior — but Claude can and will deviate from them when given a compelling reason or when the task clearly requires it.
This is not a bug. You want Claude to be able to say 'the context says avoid direct commits, but you just explicitly asked me to commit directly, so I will'. The alternative — a model that rigidly refuses based on text in a file — would be unusable. But it means CLAUDE.md is the wrong tool for non-negotiable enforcement.
CLAUDE.md is excellent for context, style, and conventions — things you want Claude to do by default and consistently. It's not the right tool for 'I must physically prevent Claude from doing X'. For that, use permissions or hooks.
Permissions: Tool-Level Enforcement
Permissions are allow/deny lists stored in settings.json files. They operate at the tool call level — before a tool call executes, the permission system checks whether it is allowed. A denied tool call never runs, regardless of what Claude or the user requests.
| Use permissions for... | Because... |
|---|---|
| Blocking destructive commands (rm -rf, git push --force) | CLAUDE.md can't physically prevent these |
| Restricting Bash to only safe commands | Reduces the blast radius of automation errors |
| Preventing file access outside project scope | Sandboxes Claude to the relevant directory |
| Requiring approval for external network calls | Controls what Claude can reach |
A denied command returns an error to Claude regardless of context. Claude cannot work around a denylist — it is enforced before the tool call is even attempted. If you deny 'Bash(git push:*)', Claude cannot push even if you explicitly ask it to.
Hooks: Lifecycle-Level Enforcement
Hooks are shell commands that run at specific points in Claude Code's execution lifecycle. They run outside of Claude's control — Claude doesn't trigger them, the harness does. This makes them genuinely independent enforcement.
| Hook event | When it runs | Common use |
|---|---|---|
| PreToolUse | Before any tool call | Validate inputs, block operations by condition |
| PostToolUse | After any tool call completes | Check output, trigger downstream actions |
| Stop | When Claude finishes a turn | Run linter, type checker, test suite automatically |
| Notification | When Claude sends a notification | Forward alerts to Slack, email, etc. |
A PreToolUse hook that exits with a non-zero code aborts the tool call. This makes hooks capable of conditional blocking: run a script that checks whether a migration is safe, and exit 1 if it is not. Claude gets the error output and stops.
Decision Framework: Which Mechanism to Use
The three mechanisms have different strengths. Choosing the right one for a given requirement determines whether your enforcement actually works.
| Requirement | Right mechanism | Why |
|---|---|---|
| Claude should prefer named exports | CLAUDE.md | Style preference — strong suggestion is appropriate |
| Claude must never run git push --force | Permissions (deny) | Non-negotiable — needs system-level block |
| Every file edit should be auto-formatted | Hooks (PostToolUse) | Automated action after every edit |
| Type checking should run after every session turn | Hooks (Stop) | Automated check at lifecycle boundary |
| Claude should avoid Redux in this project | CLAUDE.md | Architectural preference, not a hard block |
| Claude must not read files outside /src | Permissions (deny on Read) | Scope enforcement — physical boundary |
The most robust setups use all three: CLAUDE.md for context and defaults, permissions for non-negotiable blocks, hooks for automated quality checks. CLAUDE.md prevents 90% of issues through good defaults. Permissions catch the cases where Claude might otherwise comply with an explicit user request. Hooks catch the residual issues through automated verification.
Best Practices
Do
- ✓Use CLAUDE.md for context, conventions, and preferences — things you want by default
- ✓Use permissions (deny) for truly non-negotiable blocks — things Claude must never do
- ✓Use hooks for automated quality gates — linting, formatting, type checking after every edit
- ✓Combine all three: CLAUDE.md + permissions + hooks gives layered, robust enforcement
- ✓Document your enforcement setup in CLAUDE.md so engineers understand the constraints
Don’t
- ✗Don't write 'NEVER do X' in CLAUDE.md when you actually need a hard block — use deny permissions
- ✗Don't add hooks that are too slow — a PostToolUse hook that runs the full test suite will make every edit painfully slow
- ✗Don't over-constrain with permissions — blocked tool calls frustrate legitimate use cases
- ✗Don't rely on CLAUDE.md alone for security-critical constraints
Key Takeaways
- ✓CLAUDE.md instructions are strong suggestions — not hard blocks
- ✓Permissions (allow/deny lists) are system-level enforcement — Claude cannot override them
- ✓Hooks are lifecycle-level enforcement — they run outside Claude's control
- ✓Use CLAUDE.md for defaults, permissions for non-negotiable blocks, hooks for automated checks
- ✓A deny list entry is the only way to truly prevent Claude from running a specific command
Video on this topic
CLAUDE.md Rules vs Real Enforcement in Claude Code
tiktok