CLAUDE.md & the Memory Hierarchy

Teach Claude your project's DNA

Intermediate 14 minBuilder
What you'll be able to do
  • Map the four-tier CLAUDE.md hierarchy and predict the exact order in which files concatenate into context
  • Explain why instructions are advisory context (not system prompt) and when to reach for hooks instead
  • Use /init to generate and refine a project CLAUDE.md, including the interactive CLAUDE_CODE_NEW_INIT=1 flow
  • Write short, concrete, project-specific instructions and apply the 'would Claude err without this?' test
  • Predict how CLAUDE.md behaves across subdirectories and after /compact
At a glance

CLAUDE.md is how you teach Claude Code your project's DNA — build commands, conventions, architecture — once, so you don't re-explain it every session. This lesson covers the four-tier memory hierarchy, how files are discovered and concatenated, the /init command, and the crucial fact that these instructions are advisory context, not enforced configuration.

  1. 1The mental model: a persistent briefing, not a config file
  2. 2The four-tier hierarchy
  3. 3Discovery and load order
  4. 4Advisory context, not enforced config
  5. 5Bootstrapping with /init
  6. 6Keep it short, keep it concrete
  7. 7What survives compaction

The mental model: a persistent briefing, not a config file

Every Claude Code session starts with a fresh context window — Claude remembers nothing from yesterday. A CLAUDE.md file is how you carry knowledge across that gap. Think of it as the onboarding doc you hand a new teammate: the build command, the test runner, the directory layout, the two or three conventions they'd get wrong on day one.

The single most important thing to internalize: CLAUDE.md is loaded as context, not as enforced configuration. Claude reads it at session start and tries to follow it — but there is no hard guarantee of compliance, exactly as a human teammate might forget a guideline under pressure. This one fact explains almost every design decision in the system, so hold onto it.

A good CLAUDE.md answers a precise question for every line: "Would Claude make a mistake here without this instruction?" If Claude already does the right thing — say, it naturally runs your tests — the rule is noise. Delete it. The goal is a small, dense file of facts Claude can't guess, not a style manual.

There is a second, complementary system worth naming up front: auto memory, where Claude writes its own notes (build commands it discovered, a debugging insight) to MEMORY.md. The split is clean: you write CLAUDE.md; Claude writes auto memory. This lesson focuses on CLAUDE.md, the part you control.

Key insight

The test for every line

Before adding a line to CLAUDE.md, ask: would Claude err without this? "Run pytest -q, not the full suite" passes the test — Claude can't guess your preference. "Write clean code" fails it — it's vague and Claude tries anyway. Curate for facts, not vibes.

The four-tier hierarchy

CLAUDE.md instructions can live in four places, each with a different scope. They are listed here from highest precedence to lowest, but precedence here doesn't mean override — it means load order. All discovered files concatenate into context; nothing replaces anything. The rule to memorize: the closer a file is to your working directory, the later it loads — and the last thing Claude reads carries the most weight.

TierLocationScopeWho writes it
1. Managed policymacOS: /Library/Application Support/ClaudeCode/CLAUDE.md<br/>Linux/WSL: /etc/claude-code/CLAUDE.md<br/>Windows: C:\Program Files\ClaudeCode\CLAUDE.mdOrganization-wide, every repoIT / DevOps
2. User~/.claude/CLAUDE.mdYou, across all your projectsYou
3. Project./CLAUDE.md or ./.claude/CLAUDE.mdThe team, via source controlTeam
4. Local./CLAUDE.local.mdYou, this project only (gitignore it)You

Think of it as concentric rings, from broadest to most specific. The managed policy tier is special: deployed by an organization via MDM/Group Policy/Ansible, it applies to every session on the machine and cannot be excluded by individual settings — that's the whole point of a policy. (In a monorepo you can skip other CLAUDE.md files with the claudeMdExcludes setting, but never the managed-policy file.)

Because everything concatenates with project-level last, your project's ./CLAUDE.md is read after the user file, and your private ./CLAUDE.local.md is appended after that — so your most specific, most local instructions get the final word.

Note

Local vs. worktrees

CLAUDE.local.md is gitignored and exists only in the worktree where you created it — it does not follow you across git worktrees. To share personal instructions across worktrees, import a home-directory file instead: put @~/.claude/my-project-notes.md in your project CLAUDE.md.

Tip

Managed policy without a separate file

Organizations can skip deploying a file entirely and put the content inline via the claudeMd key in managed-settings.json (e.g. {"claudeMd": "Always run make lint before committing."}). It carries the same precedence as a managed-policy CLAUDE.md and is honored only from managed/policy settings.

Discovery and load order

How does Claude find these files? It walks up the directory tree from your current working directory, checking each directory for CLAUDE.md and CLAUDE.local.md. Run Claude in foo/bar/ and it loads foo/bar/CLAUDE.md, foo/CLAUDE.md, and any CLAUDE.local.md alongside them — all the way up to the filesystem root.

The ordering rule is precise and worth committing to memory:

  • Across the tree, content is ordered from the filesystem root down to your cwd. So foo/CLAUDE.md appears before foo/bar/CLAUDE.md — instructions closer to where you launched are read last.
  • Within a single directory, CLAUDE.local.md is appended after CLAUDE.md — your personal notes are the last thing read at that level.
text
# Launched in foo/bar/ — load order, top = first, bottom = last (most weight):
Managed policy CLAUDE.md      (if deployed)
~/.claude/CLAUDE.md           (user)
foo/CLAUDE.md                 (ancestor project)
foo/CLAUDE.local.md
foo/bar/CLAUDE.md             (closest project)
foo/bar/CLAUDE.local.md       <- read last, most weight

Subdirectories below your cwd behave differently. Claude discovers CLAUDE.md files in subdirectories too, but it does not load them at launch. Instead, a subdirectory's CLAUDE.md is pulled in on demand — only when Claude actually reads a file in that directory. This keeps a big monorepo's context lean: you only pay for the instructions in the parts of the tree you're actually touching.

Tip

HTML comments are free

Block-level HTML comments (<!-- maintainer note -->) are stripped before injection — they cost zero context tokens. Use them for notes to human maintainers. Caveat: comments inside code blocks are preserved, and all comments stay visible if you open the file with the Read tool.

Note

Debugging what loaded

Run /memory to list every CLAUDE.md, CLAUDE.local.md, and rules file currently loaded. If a file you expected isn't there, Claude literally can't see it. For deeper tracing of when and why files load, use the InstructionsLoaded hook.

Advisory context, not enforced config

Here is the most consequential detail in the whole system, stated exactly: CLAUDE.md content is delivered as a user message after the system prompt — it is not part of the system prompt itself. Claude reads it and tries to follow it, but there is no guarantee of strict compliance, especially for vague or conflicting instructions.

Three practical consequences follow:

  1. Specific beats vague — every time. Concrete, verifiable instructions are followed far more reliably than fuzzy ones.

    Write thisNot this
    Use 2-space indentationFormat code properly
    Run npm test before committingTest your changes
    API handlers live in src/api/handlers/Keep files organized
  2. Contradictions get resolved arbitrarily. If two files give conflicting guidance for the same behavior, Claude may pick one at random. Periodically review your files (and nested ones) to prune outdated or clashing rules.

  3. For guarantees, don't use CLAUDE.md at all. If something must happen at a specific moment — lint before every commit, block writes to a path — write a hook. Hooks run as shell commands at fixed lifecycle events and apply regardless of what Claude decides. That's the enforcement layer; CLAUDE.md is the advice layer.

If you genuinely need an instruction at the system-prompt level, the escape hatch is the --append-system-prompt flag — but it must be passed on every invocation, so it suits scripts and automation, not interactive use.

Watch out

Don't put hard guarantees in prose

"Never push to main" written in CLAUDE.md is a suggestion Claude can drift from, and it can even be dropped during compaction. A safety rule you cannot afford to lose belongs in a PreToolUse hook or a permissions.deny rule — the client enforces those no matter what Claude decides.

Bootstrapping with /init

You rarely write a CLAUDE.md from a blank page. Run /init and Claude analyzes your codebase, then generates a starter file with the build commands, test instructions, and project conventions it discovers. From there you refine — add the handful of things Claude wouldn't discover on its own (the gotchas, the "never touch legacy/" rules).

Key behaviors:

  • If a CLAUDE.md already exists, /init does not overwrite it — it reads what's there and suggests improvements instead. Safe to re-run.
  • It's codebase-aware beyond just Claude: running /init in a repo with an existing AGENTS.md reads it and folds the relevant parts into the generated CLAUDE.md. It also reads other tool configs like .cursorrules and .windsurfrules.

For a richer setup, set the environment variable CLAUDE_CODE_NEW_INIT=1 to enable an interactive, multi-phase flow:

bash
CLAUDE_CODE_NEW_INIT=1 claude
# then, inside the session:
/init

In that mode /init asks which artifacts to set up — CLAUDE.md files, skills, and hooks — explores the codebase with a subagent, asks follow-up questions to fill gaps, and presents a reviewable proposal before writing any files.

Tip

Already using AGENTS.md?

Claude Code reads CLAUDE.md, not AGENTS.md. If your repo standardizes on AGENTS.md for other agents, don't duplicate — create a CLAUDE.md whose first line is @AGENTS.md (an import), then add any Claude-specific instructions below it. A ln -s AGENTS.md CLAUDE.md symlink also works if you have nothing Claude-specific to add.

Keep it short, keep it concrete

CLAUDE.md is loaded into the context window at the start of every session, spending tokens alongside your actual conversation. That cost drives the cardinal rule: target under 200 lines per file. Longer files consume more context and reduce adherence — the irony of a bloated instruction file is that the very rules you wrote get lost in the noise and ignored.

A discipline that keeps files tight:

  • Apply the test to every line: would Claude err without it? If not, cut it.
  • Structure with markdown headers and bullets. Claude scans organized sections the way you do; dense paragraphs are harder to follow.
  • Add a rule when you've hit the same problem twice — Claude repeats a mistake, a review catches something it should've known, or you type the same correction you typed last session. That's the signal to write it down.
  • Move the wrong-shaped content out. A multi-step procedure belongs in a skill (loads only when invoked). An instruction that only matters for one part of the tree belongs in a path-scoped rule under .claude/rules/ — it loads only when Claude touches matching files.

When a file genuinely needs to grow, split by topic into .claude/rules/. A rule with a paths: frontmatter (e.g. src/api/**/*.ts) loads only when Claude reads matching files, saving context everywhere else. (Note: @path imports help organize but do not save context — imported files still load in full at launch.)

Example

A lean project CLAUDE.md

markdown
# Project: billing-service

## Commands
- Build: `make build`
- Test (fast): `pytest -q tests/unit`
- Lint: `make lint` (must pass before commit)

## Conventions
- API handlers live in `src/api/handlers/`
- Money is always cents (int), never float
- Never edit `legacy/` — it's frozen

<!-- maintainers: keep this under 50 lines -->

Every line earns its place: each is a fact Claude would otherwise get wrong.

What survives compaction

Long sessions eventually hit /compact, which summarizes the conversation to reclaim context. A natural worry: does my CLAUDE.md survive? For the part that matters most, yes.

  • Project-root CLAUDE.md is re-injected from disk after /compact. Claude re-reads the file and puts it back into the freshly compacted session. Your core project instructions persist by design.
  • Nested subdirectory CLAUDE.md files are not automatically re-injected. They reload the next time Claude reads a file in that subdirectory — the same on-demand mechanism as initial discovery.

This yields a useful diagnostic. If an instruction vanished after compaction, it was one of two things: (a) it was given only in conversation and never written to a file, or (b) it lives in a nested CLAUDE.md that hasn't been re-triggered yet. The fix for (a) is the recurring theme of this lesson — if you want it to persist, write it to CLAUDE.md, don't just say it in chat.

Key insight

The throughline

Fresh context each session, advisory not enforced, re-injected on compaction — every behavior points to one habit: durable knowledge goes in a file; hard guarantees go in a hook. Conversation is for the work of the moment, not for things you need to last.

Try it: Author and stress-test a project CLAUDE.md

Pick a real repo of yours. 1) Bootstrap: run /init, let Claude generate a starter CLAUDE.md, and read what it inferred about your build/test commands and conventions. 2) Curate: apply the line test — for each line, ask 'would Claude err without this?' and delete the ones that fail. Add 2-3 facts Claude couldn't discover (a gotcha, a 'never touch X' rule, the exact fast-test command). Keep the whole file under ~50 lines. 3) Verify loading: run /memory and confirm your file is listed. Add an HTML comment (<!-- note to maintainers -->) and confirm via behavior that it doesn't reach Claude. 4) Test the hierarchy: create a ./CLAUDE.local.md with a personal preference (e.g. your sandbox URL) and re-run /memory to see both files loaded; gitignore the local file. 5) Prove the advisory boundary: write 'never run destructive commands' into CLAUDE.md, then recognize why a real safety rule belongs in a PreToolUse hook or permissions.deny rule instead. Write three sentences: what /init got right, what you had to add by hand, and one rule you moved from CLAUDE.md to a hook (or path-scoped rule) and why.

Key takeaways

  1. 1Four tiers concatenate (never override), highest-to-lowest precedence: Managed Policy → User (~/.claude/CLAUDE.md) → Project (./CLAUDE.md or ./.claude/CLAUDE.md) → Local (./CLAUDE.local.md). Closer-to-cwd loads last and carries the most weight.
  2. 2Managed-policy files (macOS /Library, Linux /etc/claude-code, Windows /Program Files) apply org-wide and cannot be excluded by individual settings.
  3. 3CLAUDE.md is delivered as a user message after the system prompt — advisory, not enforced. Specific instructions beat vague ones; for guaranteed behavior use hooks.
  4. 4/init scans the codebase to generate a starter CLAUDE.md (and only suggests improvements if one exists); CLAUDE_CODE_NEW_INIT=1 enables an interactive multi-phase flow.
  5. 5Keep each file under 200 lines — bloat reduces adherence. Every line should answer 'would Claude err without this?'; HTML comments strip out for free.
  6. 6Project-root CLAUDE.md re-injects from disk after /compact; nested subdir files reload only on the next read in that directory.

Quiz

Lock in what you learned

Check your understanding

0 / 4 answered

1.You launch Claude in a subdirectory of a project. Which CLAUDE.md is read LAST and therefore carries the most weight?

2.Your team has a hard rule: never push directly to main. Where should this live to be reliable?

3.You run /init in a repository that already contains a CLAUDE.md. What happens?

4.Your CLAUDE.md has grown to 600 lines and Claude has started ignoring rules that used to work. What's the best explanation and fix?

Go deeper

Hand-picked sources to keep learning