Why is Claude Code ignoring CLAUDE.md?

/context

Check the Memory files list. A file missing there was never loaded.

Answer

Start with /context and check the Memory files list — if your file is not there, Claude cannot see it, and nothing about the wording matters. If it did load, the usual causes are a vague instruction, a contradiction with another CLAUDE.md, or the expectation that the file enforces anything: it is delivered as context, so compliance is not guaranteed.

What it does

There are four documented causes, and they are worth checking in order — the first is common and the cheapest to rule out.

The file never loaded. Run /context and look under Memory files. If it is not listed, Claude cannot see it. Usually the file sits in a subdirectory below your working directory, where it loads only when Claude reads a file there.

The instruction is vague. "Use 2-space indentation" is checkable; "format code nicely" is not. Vague instructions are the ones that appear ignored.

Two files contradict each other. If a nested CLAUDE.md or a rule in .claude/rules/ says something different, Claude may pick either. Neither ordering nor scope resolves this.

You expected enforcement. CLAUDE.md content is delivered as a user message after the system prompt, not as part of it. Claude reads it and tries to follow it. There is no strict-compliance guarantee.

How to check

Work down that list in order. Rewording an instruction that was never in context is wasted effort, and that is the most common version of this problem — so start by ruling it out:

/context

If the file is not under Memory files, nothing about its wording matters yet. Move it, or check whether it lives in a subdirectory Claude has not read this session.

If it did load, read it again looking for the other three causes: an instruction too vague to verify, a contradiction with another file, or an expectation that CLAUDE.md enforces rather than suggests.

How to fix it

Match the fix to what you found.

It never loaded — move it somewhere that loads, or accept that it loads on demand. See where CLAUDE.md goes.

It is vague — make it checkable. "Use 2-space indentation" instead of "format code nicely".

Two files disagree — remove one. Neither ordering nor scope decides the winner; Claude may pick either.

It must happen every time — no wording will make it reliable, because CLAUDE.md is context rather than enforcement. Write it as a hook: a shell command registered against a lifecycle event in .claude/settings.json, which runs whatever Claude decides. The example below shows the shape.

For something you want at the system prompt level rather than as a user message, --append-system-prompt puts it there:

claude --append-system-prompt "Always run make lint before committing"

That does raise adherence, at the cost of passing it on every invocation — which makes it a fit for scripts rather than interactive work.

Example

To open the files rather than just list them:

/memory

For path-scoped rules and files in subdirectories, which load on demand rather than at startup, /context only tells you the state right now. The InstructionsLoaded hook logs each load as it happens. It is not a command you type — it is an event you register in .claude/settings.json. Omitting matcher logs every load; the values it takes are load reasons rather than tool names — session_start, nested_traversal, path_glob_match, include, compact — and path_glob_match is the one that answers "did my path-scoped rule fire".

{
  "hooks": {
    "InstructionsLoaded": [
      {
        "hooks": [
          { "type": "command", "command": "cat >> /tmp/claude-instructions.log" }
        ]
      }
    ]
  }
}

Claude Code pipes the event to that command's stdin, so the log accumulates one entry per file as Claude picks it up. This hook can only watch: its exit code is ignored and it cannot stop a file from loading.

A hook that *enforces* hangs off a tool call instead, where the matcher names the tool rather than a load reason. This one runs after every file edit — the "always do X" that CLAUDE.md could only ask for:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh" }
        ]
      }
    ]
  }
}

matcher names the tools it watches — here both editing tools. The script gets the call as JSON on stdin, including the path that was written, so it can format exactly that file. The difference from an instruction is that nothing decides whether to obey it.

If the instruction disappeared after /compact, the cause is specific. Project-root CLAUDE.md is re-read from disk and re-injected. Nested CLAUDE.md files and rules with paths: frontmatter are not — they reload the next time Claude reads a matching file. An instruction given only in conversation is gone; put it in CLAUDE.md to make it persist.

Common mistakes

Rewriting before verifying. /context first. It answers a different question than "is my wording good enough".

Assuming a subdirectory file is active. It loads on demand. In a session where Claude never opens that directory, it never loads at all.

Treating CLAUDE.md as configuration. It shapes behaviour; it does not enforce it. Use a hook for anything that must run.

Missing the file size effect. Adherence falls off as the file grows — 200 lines is the documented target, not a cliff edge. If yours has outgrown it, move parts into path-scoped rules rather than making the instructions louder.