Why Claude Code Ignores Your CLAUDE.md, and What to Do
By Andrey Boyko
Published Updated
Reviewed against Claude Code docs on
Claude Code ignores parts of your CLAUDE.md because the file is context that the model reads, not configuration that the program enforces. Anthropic’s memory documentation says Claude treats these files as context, not enforced configuration, and that there is no guarantee of strict compliance (checked 2026-10-23). If a rule must hold every single time, it does not belong in CLAUDE.md. It belongs in a hook, a small script that runs on every matching action, whatever the model decides (unless hooks are switched off).
These are the docs as read on 2026-10-23; we did not test them in a live Claude Code session, and the version was not recorded.
How do I check that CLAUDE.md actually loaded?
Run /context and read the list under “Memory files”. If your file is not there, the model never saw it, and no amount of rewording will help. You can also run /memory, which lists your CLAUDE.md files and other memory file locations.
One catch: a CLAUDE.md inside a subdirectory does not appear in that launch list. It loads on demand, once Claude reads, writes or edits another file in that subdirectory. The docs also say a file is delivered as a user message after the system prompt, not as part of it (memory docs, checked 2026-10-23).
How long should a CLAUDE.md be?
Aim for under 200 lines per file, but treat that as a target, not a limit. The docs word it exactly that way: “target under 200 lines per CLAUDE.md file. Longer files consume more context and reduce adherence.” The only hard ceiling is a different thing: a file over 4 MiB is skipped rather than loaded (memory docs, checked 2026-10-23).
Our own global file is the anti-example. Measured with wc -l on 2026-10-23, it is 583 lines under 17 top-level sections, nearly three times the docs’ target. One block, the memory protocol, runs from line 259 to line 540: 282 lines, about 48 percent of the file. It grew one reasonable rule at a time. The docs say longer files “consume more context and reduce adherence” (checked 2026-10-23), so a rule buried in a file like that has more to compete with. The sibling guide has the structure we use for new files.
Length is not the only cause. The docs also say that if two instructions contradict each other, Claude may pick one arbitrarily, and that specific instructions beat vague ones. A long file is where contradictions hide.
What do I do with a rule that must always happen?
Move it out of CLAUDE.md and into a hook. The docs say it directly: “To block an action regardless of what Claude decides, use a PreToolUse hook instead” (memory docs, checked 2026-10-23).
A hook is a shell command that Claude Code runs at a fixed point in its work. A PreToolUse hook runs just before a tool call, such as running a shell command or editing a file. It receives the details of the call as JSON on standard input. Two exit codes matter:
- Exit code 2 blocks the call, and whatever the script writes to stderr (the channel a script uses to print an error message) is passed back to Claude as feedback.
- Any other exit code besides 0 and 2, with no valid JSON output, lets the action proceed on most events. A guard that crashes fails open (unless you set it to block on failure), so it needs a test.
Exit code 0 does not approve the call either. It just hands the call on to the normal permission flow (hooks guide, checked 2026-10-23).
What does a working guard look like?
Here is a simplified version of a guard we run, which stops a bare docker compose down (that command removes every service in a project, not one). It is registered as a PreToolUse hook with the matcher Bash.
#!/usr/bin/env bash
# PreToolUse hook (matcher: Bash). Exit 2 blocks the call; stderr goes back to Claude.
CMD=$(jq -r '.tool_input.command // empty')
if echo "$CMD" | grep -qE 'docker[ -]compose.* down( |$)'; then
echo "Blocked: 'docker compose down' stops every service in the project. Name the service instead, e.g. 'docker compose stop db'." >&2
exit 2
fi
exit 0
Register the script in ~/.claude/settings.json (all projects) or .claude/settings.json (one project). It must be executable (chmod +x), it needs jq installed, and you should replace the placeholder path with the full path of your script (the docs examples use ~/ in commands, but we did not verify that ~ expands in every setup).
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "/path/to/block-compose-down.sh" }
]
}
]
}
}
You can test it without Claude Code by piping the JSON a hook would receive into the script:
echo '{"tool_input":{"command":"docker compose down"}}' | ~/.claude/hooks/block-compose-down.sh; echo "exit=$?"
echo '{"tool_input":{"command":"docker compose stop db"}}' | ~/.claude/hooks/block-compose-down.sh; echo "exit=$?"
We ran this on 2026-10-22. The first command printed the “Blocked:” message and returned exit 2. The second, which names a service, returned exit 0. We piped JSON into the script rather than running a live Claude Code session, so that the block takes effect in a session rests on the docs, not on our run.
This version is deliberately short and misses variants: $(...) substitutions (a command run inside another command), a down glued to a semicolon or a closing bracket, and down passed in as an argument (echo down | xargs docker compose, where xargs hands one command’s output to another as arguments). Our production script splits the command on &&, ||, ;, | and newlines first.
Why we run one at all: on 2026-08-31 a test-writer sub-agent cleaned up after an end-to-end run with a bare docker compose down. That took down an always-on stand of six services, including its tunnel. We restored it with the project’s up script and lost no data. The header of our hook names that incident as the reason it exists. Today the same rule is also a line in our global CLAUDE.md, which points to the hook as its enforcement. The line tells the model; the hook is what enforces it.
What if a clear rule still fails?
Then the rule needs a check, not better wording. On 2026-08-25 our global rule “write like a person, less text” already existed. It still failed twice on the same client message: the first draft was a seven-paragraph status report, and the second was still too tidy. The header of our hook puts it this way: a prose rule nobody can check is not a control.
So we added a check, made of two hooks. After any file write, the first flags a client-message file as unchecked. When Claude tries to finish its turn, the second refuses while any file is still flagged. The normal way to clear a flag is a rewrite that cuts something, and the clear must name what was cut. The gate compares the file with the version that tripped it, so a pass that removed nothing does not count. This is our own setup, not something the docs prescribe.
What survives /compact, and what does not?
Compaction summarises a long conversation to free up space. Your project-root CLAUDE.md survives it: after /compact, Claude re-reads the file from disk. Unscoped files in .claude/rules/ are re-injected the same way.
What does not survive is anything that existed only in the conversation: instructions you typed mid-session are gone unless they were also written to a file. Nested CLAUDE.md files and path-scoped rules come back only after Claude touches matching files again. If a rule must persist, the docs say to drop the paths: frontmatter or move it to the project-root CLAUDE.md (context window docs, checked 2026-10-23).
What are people reporting that the docs do not explain?
Three open GitHub issues describe rules that did not hold. These are individual user reports. Anthropic has not confirmed them as bugs in the text we read, and we have not reproduced any of them, so we state no cause. All three were open on 2026-10-22.
- #90542: a user with an approximately 700-line CLAUDE.md reports the rules were loaded and quoted but did not govern behaviour. By their own account, the places guarded by a PreToolUse hook held, and the places with only a written rule did not.
- #96326: users report replies drifting into English despite a CLAUDE.md rule requiring another language.
- #90450: a user reports that in Auto Mode, which steers Claude to read files through Bash, nested CLAUDE.md files and path-scoped rules did not load. The memory docs say a Bash
catcounts as a read, so the two disagree, and we have not tested which is right.
With nested files, check what loaded in your session with /memory and /context.
Next step
For the structure we use for short, routed CLAUDE.md files, the AccrueDev course covers context loading and quality gates. For the file itself, start with the free guide to writing a CLAUDE.md. AccrueDev is not affiliated with Anthropic.
Frequently asked questions
Is CLAUDE.md a hard rule for Claude Code?
No. Anthropic's docs say Claude treats CLAUDE.md as context, not enforced configuration, and that there is no guarantee of strict compliance. For a rule that must hold every time, the docs point to a hook instead.
How many lines should a CLAUDE.md have?
The docs say to target under 200 lines per file, and that longer files consume more context and reduce adherence. It is a target, not a hard limit. Separately, a file larger than 4 MiB is skipped entirely.
How do I know my CLAUDE.md was loaded?
Run /context and look at the list under Memory files, or run /memory to see which CLAUDE.md files and other memory locations Claude Code knows about. A CLAUDE.md in a subdirectory is not listed at launch because it loads on demand.
Does CLAUDE.md survive /compact?
The CLAUDE.md in your project root does: Claude Code re-reads it from disk after compaction. Instructions you only gave in conversation do not survive, and nested CLAUDE.md files reload only when Claude works in that subdirectory again.