A coworker told me Claude Code kept inserting em dashes into AWS resource strings. The AWS API rejected every call with InvalidParameterValue. He had a clear rule in his CLAUDE.md saying not to use them. The rule wasn't doing anything.

I hear a version of this complaint constantly. Engineers add a directive to CLAUDE.md, the directive gets ignored, and the conclusion is that Claude Code isn't ready for production. The conclusion is wrong. The directive was in the wrong place. CLAUDE.md is advisory by design. If the rule has to hold every time, no exceptions, the right home is a hook, not a paragraph in a markdown file.

This post is the routing map. Five mechanisms, one decision tree, five worked examples. Pick the right layer for the directive and Claude Code stops feeling like a model that ignores you.

Why Claude Code Ignores Half of Your CLAUDE.md

The first thing to internalize: CLAUDE.md is not a config file. Anthropic's own memory documentation puts it plainly: "CLAUDE.md content is delivered as a user message after the system prompt, not as part of the system prompt itself. Claude reads it and tries to follow it, but there's no guarantee of strict compliance." Strict compliance is not on the table. The model decides, every turn, how literally to apply what it read.

That's a feature, not a bug. Anthropic closed the feature request asking for hard @enforce directives in CLAUDE.md as "not planned." The advisory behavior is intentional. The enforcement path runs through a different mechanism.

The failure mode compounds with size. Anthropic's best practices target under 200 lines per CLAUDE.md file: "Longer files consume more context and reduce adherence... If your CLAUDE.md is too long, Claude ignores half of it because important rules get lost in the noise." The number isn't arbitrary. The AGENTIF benchmark (NeurIPS 2025) measured instruction adherence across frontier models and found that when instruction length passes roughly 6,000 words, instruction satisfaction collapses to near zero. Two hundred lines of CLAUDE.md at typical density lands right at that cliff.

So the failure pattern most teams hit isn't a model problem. It's a mechanism mismatch. The rule was right. The enforcement was advisory. And the file was too long for the advice to land.

The Five Mechanisms, Side by Side

Claude Code gives you five places to put a directive. Each loads at a different time, costs different amounts of context, and offers a different enforcement guarantee. Pick the right one and the directive sticks. Pick the wrong one and you write the same rule three times before realizing the rule was never the problem.

MechanismLifecycleEnforcementBest fit
Root CLAUDE.mdLoaded into every sessionAdvisoryPersistent project context Claude should always know
Subdirectory CLAUDE.md and path-scoped rules (.claude/rules/)Loaded when Claude reads matching filesAdvisoryConventions that only apply to one part of the codebase
settings.json permissionsLoaded at session start, evaluated per tool callDeterministicStatic tool boundaries (block specific commands, paths, MCP tools)
Skills (.claude/skills/)Description always in context; body loads when triggered or when you type /skill-nameAdvisory (skill body is instructions, not enforcement)Procedures and reference material Claude needs sometimes
Hooks (.claude/settings.json hooks block)Fires on a configured event (PreToolUse, PostToolUse, SessionStart, etc.)Deterministic (exit code 2 blocks the action)Anything that must run every time without asking

A vocabulary note before going further. Two of these mechanisms get conflated constantly. Path-scoped rules live in .claude/rules/ with paths: frontmatter and govern instructions for matching files. Settings rules live in settings.json under permissions and govern tool boundaries. They are not the same thing. Path-scoped rules are advisory file-scoped guidance; settings rules are deterministic tool-call gates.

A Decision Tree: Routing by Directive Type

The shift that matters: stop asking "what can each mechanism do?" and start asking "what kind of directive is this?" The mechanism follows from the answer.

ROUTE BY DIRECTIVE TYPE Must fire every time, no exceptions? HOOK PreToolUse PostToolUse SessionStart Tool or command boundary? SETTINGS permissions.deny permissions.allow Scoped to one part of the codebase? PATH-SCOPED .claude/rules/ worker/CLAUDE.md Procedure or reference material? SKILL .claude/skills/ /skill-name Persistent context Claude should know? CLAUDE.md root project under 200 lines

Walk it left to right. The first question is the strictest: does this rule have to fire every time, regardless of what Claude judges to be reasonable in the moment? If yes, you want a hook. Anthropic's features overview phrases the same test: "You want something to happen every time without asking → write a hook." That's the canonical line for hook routing.

If it isn't strict enforcement, ask whether this is a tool or command boundary. Blocking curl to a specific host, denying Bash(rm -rf:*), restricting which MCP tools can run unprompted: those are settings rules, not instructions. The next two questions narrow scope. Is the directive only relevant inside one directory? Path-scoped rules and subdirectory CLAUDE.md files load lazily when Claude reads matching files. Is it a multi-step procedure or reference material Claude needs sometimes? That's a skill: the description sits in context cheap, the body loads only on relevance. If none of the above apply and Claude genuinely needs to know this on every task, root CLAUDE.md is the right home. Keep it under 200 lines.

The shorthand I keep in my head when I'm deciding: if a directive must always happen no matter what, or if failure would be catastrophic: write a hook. If it's a process I run frequently and want automated: write a skill (here's the hands-on guide for actually building one). The other three layers (settings rules, path-scoped rules, root CLAUDE.md) fill in around that core pair.

Five Routing Decisions, Worked

Five common directives, routed through the tree.

1. "Never commit secrets." Routes to a PreToolUse hook on Write|Edit|MultiEdit that scans the new content for credential patterns and exits with code 2 to block. A line in CLAUDE.md saying "never commit secrets" is a request, not a guard. One subtlety: exit code 1 won't block. The hooks reference is explicit: "Claude Code treats exit code 1 as a non-blocking error and proceeds with the action... If your hook is meant to enforce a policy, use exit 2." Many hooks ship broken because their author assumed Unix conventions.

2. "Use our team's conventional commit format." Routes to a skill, not CLAUDE.md. The skills documentation recommends disable-model-invocation: true for workflows like /commit and /deploy where you want explicit user invocation: "You don't want Claude deciding to deploy because your code looks ready." A /commit skill bundles the format rules, the staged-change inspection, and the message generation into one invocation. The skill body never bloats CLAUDE.md, and it only runs when you ask for it.

3. "Always use our auth helper inside worker/." Routes to a worker/CLAUDE.md file. The root CLAUDE.md doesn't need to know about worker-specific patterns. A subdirectory file loads only when Claude reads files in that directory, which keeps the root file lean and gives the worker code its own contract. One caveat the memory docs flag: nested CLAUDE.md files don't re-inject after /compact until Claude next opens a file in that subdirectory. If worker/ rules are critical, also encoding them as a path-scoped rule with paths: ["worker/**/*"] provides a backstop.

4. "Apply our Claude API best practices when writing SDK code." Routes to a skill with a description like "Use when writing code that imports the Anthropic SDK or calls the Claude API." The description and trigger text (capped at 1,536 characters combined) stay in context so Claude knows the skill exists; the body, including longer guidance on prompt caching, tool use, and streaming, loads only when triggered. Trying to put this in root CLAUDE.md would inflate every conversation with material that's relevant to maybe a tenth of tasks.

5. "Block rm -rf." Routes to settings.json with "permissions": { "deny": ["Bash(rm -rf:*)"] }. Settings deny rules fire before allow rules, regardless of what Claude reasons about the command. For destructive operations specifically, defense in depth makes sense: pair the settings deny with a PreToolUse hook on Bash that pattern-matches more permissively, since shell parsing has historically had bypass cases (more on that in a moment).

What does the layered version of example 5 look like as configuration? A settings.json snippet combining the static deny rule with a hook backstop:

.claude/settings.json
{
"permissions": {
"deny": ["Bash(rm -rf:*)", "Bash(rm -rf /*)"]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/block-destructive.mjs",
"timeout": 3000
}]
}
]
}
}

Line 3 is the settings rule: a static glob the permission system evaluates per tool call. Lines 7-13 are the hook backstop: a script that gets every Bash invocation and can inspect the full command, exit 2 to block, and return a stderr message Claude reads back as feedback. The two layers are independent and complementary. The settings rule covers the common case cheaply. The hook covers the long tail and any future parser bug.

The Three Routing Mistakes I See Most

Putting non-negotiables in CLAUDE.md. This is the em-dash story. The fix isn't louder phrasing or more **bold** markdown. CLAUDE.md is advisory. If a violation costs you an API rejection, a leaked secret, or a broken deploy, write a hook. Anthropic's features overview is direct about it: "An instruction like 'never edit .env' in CLAUDE.md or a skill is a request, not a guarantee. A PreToolUse hook that blocks the edit is enforcement." The most consequential version of this mistake I see in production is review-automation severity rubrics living in CLAUDE.md instead of in a hook or a skill body the reviewer subagent loads on every invocation. When the rubric is advisory, the model normalizes findings to fill the available label spectrum and your merge gate escalates on a "Critical" that meant something different last run. I walked through that failure mode and the five mitigation patterns in Claude grades severity on a curve.

Letting reference material crowd the root file. Style guides, API patterns, debugging playbooks, full coding conventions: these don't all need to load on every conversation. They bloat CLAUDE.md past 200 lines and adherence drops. Move reference material into skills, where the description costs little context and the body loads on demand. The signal that you've crossed the line: you can no longer skim your CLAUDE.md and find a specific rule in under ten seconds.

Treating a skill like always-on context. A skill's body is gated behind the model's relevance judgment against its description. If you need Claude to always know something (current date, project structure, build commands), a skill is the wrong tool. Root CLAUDE.md is right for context that's universally relevant. A SessionStart hook is right for context that needs to be injected freshly each session, like a date or a counter that would be stale if it lived in a static file. Skills are for sometimes-relevant procedures, not always-on facts.

When the Model Breaks Down

The framework above is the right default. It's not a guarantee, and being honest about where it fails matters for production use.

Even the deterministic layer has had bypass cases. In April 2026, Adversa AI documented that Claude Code's permissions.deny rules were silently bypassed when shell commands chained more than 50 subcommands. A malicious or sloppy command at position 51+ could slip past the parser. Anthropic patched it in v2.1.90, but the episode is a useful reminder that the "hard" enforcement layer has a real implementation surface that can degrade. For safety-critical patterns, layering settings deny rules and PreToolUse hooks together is cheap and pays off the day a parser bug ships.

The second crack: hook-heavy setups can break themselves. GitHub issue #34713 tracks an open bug where Claude Code displays "hook error" labels for hook executions regardless of success or failure. In setups with multiple plugins stacked, Claude can see hundreds of false error signals per session and prematurely abort multi-step tasks even when every hook ran clean. Five duplicate issues confirm the pattern. Until that fix lands, "move every non-negotiable to a hook" has a ceiling: if you're stacking many hooks, you're trading advisory misses for compounding noise.

The third crack is design intent, not a bug. Anthropic closed the request for stronger CLAUDE.md enforcement as "not planned." That places non-trivial engineering burden on teams: every serious rule has to be translated into a hook script with correct exit codes, JSON output, and shell isolation. The architecture is honest about its trade-offs, and you should be too. If a rule is in CLAUDE.md, it's a request. If you need a guarantee, you write the hook.

Pick the Right Layer the First Time

Most of the "Claude Code ignored my instructions" frustration I hear about traces back to one of three errors: a non-negotiable parked in CLAUDE.md, reference material crowding out the rules that matter, or a skill being asked to do an always-on job. The decision tree fixes the routing. The five worked examples show what each layer looks like in practice. The vocabulary discipline (settings rules versus path-scoped rules, advisory versus deterministic) prevents the conflations that break setups.

If you'd rather have the routing decisions made for you, working through them inside your team's codebase is what a Claude Code Infrastructure Setup engagement delivers. I configure the CLAUDE.md(s), the skills, the hooks, and the path-scoped rules around how your team already ships. For broader context on how this fits a manager's view of governance rather than a practitioner's view of routing, the engineering manager's governance guide is the companion piece. If you've routed your rules to the right layers but the upgrade to Opus 4.7 left you wondering whether the rules still work the way you wrote them, the opus-compatibility-scanner is the audit for exactly that: the routing decisions in this post tell you where directives belong, and the scanner tells you whether the directives you already have survive 4.7's literal reading. And if you haven't yet decided whether Claude Code is the right surface in the first place, Claude vs Claude Code covers that earlier routing decision. And if you want to see this routing problem in the wild, the six questions 400 engineers asked in a 90-minute training is the field-observation companion.

A directive in the right layer feels almost invisible. It just works. Pick the right one and you stop fighting the model.