How Claude Code Thinks/Configuration & Memory
Intermediate8 min

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

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.

What CLAUDE.md Is Good For

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.

Allow specific safe commands; deny dangerous ones at the system level
Use permissions for...Because...
Blocking destructive commands (rm -rf, git push --force)CLAUDE.md can't physically prevent these
Restricting Bash to only safe commandsReduces the blast radius of automation errors
Preventing file access outside project scopeSandboxes Claude to the relevant directory
Requiring approval for external network callsControls what Claude can reach
Permissions Are System-Level Blocks

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 eventWhen it runsCommon use
PreToolUseBefore any tool callValidate inputs, block operations by condition
PostToolUseAfter any tool call completesCheck output, trigger downstream actions
StopWhen Claude finishes a turnRun linter, type checker, test suite automatically
NotificationWhen Claude sends a notificationForward alerts to Slack, email, etc.
Stop hook runs type check and lint after each turn; PostToolUse hook auto-formats every edited file
Hooks That Exit Non-Zero Abort the Operation

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.

RequirementRight mechanismWhy
Claude should prefer named exportsCLAUDE.mdStyle preference — strong suggestion is appropriate
Claude must never run git push --forcePermissions (deny)Non-negotiable — needs system-level block
Every file edit should be auto-formattedHooks (PostToolUse)Automated action after every edit
Type checking should run after every session turnHooks (Stop)Automated check at lifecycle boundary
Claude should avoid Redux in this projectCLAUDE.mdArchitectural preference, not a hard block
Claude must not read files outside /srcPermissions (deny on Read)Scope enforcement — physical boundary
Layer All Three

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

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