Claude Code Learning Hub
中文 Mingyu's Library

Hub / Course / D4

D4 · CLAUDE.md & rules

CLAUDE.md is the persistent instruction file every session carries with it. This lesson covers its levels and loading rules, what to write and what to leave out, and how to split instructions by path with .claude/rules/.

The one-sentence version: CLAUDE.md lives in context permanently, so "short and specific" beats "long and comprehensive" by a wide margin; conventions that apply to only some files belong in rules files with paths:, loaded on demand.

Why this lesson comes today

In D1 we saw that every turn of Claude Code's agentic loop starts from the same context, and CLAUDE.md is the fixed, present-in-every-session part of it — loaded at session start and carried on every turn after. D2 then did the math: every line of permanent context keeps costing tokens. Connect the two and you get this lesson's central question: where should this always-present file live, what should it say, and how should it be split once it grows long.

It also paves the way forward: Skills in D7 are the other half — "loaded on demand". CLAUDE.md and rules without paths: sit in context permanently, while skills load only when invoked; telling these two classes apart is the basis of any configuration setup. And Hooks in D8 handle what CLAUDE.md cannot: CLAUDE.md is advice, hooks are enforcement.

Core concepts, explained

What CLAUDE.md is: persistent instructions you write

OfficialEvery Claude Code session starts from a blank context. Two mechanisms carry knowledge across sessions: CLAUDE.md (instructions you write) and auto memory (notes Claude keeps for itself). Both load at the start of every session. This lesson focuses on the former; for auto memory, three facts are enough for now: it is on by default, it lives in ~/.claude/projects/<project>/memory/, and each session loads only the first 200 lines or 25KB of its index, MEMORY.md.

OfficialThe key framing: both kinds of memory are "context", not "enforced configuration". Claude reads them and does its best to follow them, but nothing hard-constrains it — to block an action no matter what, use a PreToolUse hook (expanded in D8). The more specific and concise the instructions, the more reliably they are followed.

Four levels, one concatenation order

OfficialCLAUDE.md can live in four places, scoped from widest to narrowest. At load time they are concatenated into context in that wide-to-narrow order, so project instructions appear after user instructions; the official note on the global CLAUDE.md says the same: both are present, and when instructions conflict, the project level wins.

LevelLocationWho it servesIn version control?
Managed policymacOS /Library/Application Support/ClaudeCode/CLAUDE.md (Linux/WSL and Windows have their own paths)The whole organization, pushed by IT; individuals cannot opt outManaged by IT
User~/.claude/CLAUDE.mdYou, across all projectsNo
Project./CLAUDE.md or ./.claude/CLAUDE.mdThe team, shared with the sourceYes, committed to git
Project-local./CLAUDE.local.mdYou, this project onlyNo — add it to .gitignore

Loading rules: everything upward, on demand downward

OfficialAt startup, Claude Code walks from the current working directory all the way up to the filesystem root, loading every CLAUDE.md and CLAUDE.local.md it passes. All files are concatenated into context, never overriding one another: files higher up the directory tree come first, files closer to the working directory come later; within one directory, CLAUDE.local.md comes after CLAUDE.md. Subdirectories work the other way around: a CLAUDE.md below the working directory is not loaded at startup — it is brought in on demand once Claude actually reads a file in that directory.

Loaded at session start (in this order) Managed policy CLAUDE.md User-level ~/.claude/CLAUDE.md & rules/ Ancestor-directory CLAUDE.md (monorepo) Project root CLAUDE.md / .claude/CLAUDE.md + rules without paths: in .claude/rules/ CLAUDE.local.md (personal, not committed) Lower rows are narrower in scope and read later in the concatenation Loaded on demand (no startup cost) CLAUDE.md in subdirectories Loads when Claude reads files there Rules files with paths: Load when a glob-matching file is read They enter context only once triggered — ideal for conventions scoped to some files
CLAUDE.md level loading: the left column is fully concatenated into context at session start; the right column loads only when triggered.

OfficialA few easily missed details: block-level HTML comments (<!-- … -->) in CLAUDE.md are stripped before injection into context, so they make free human-facing maintenance notes that cost no tokens; a file can pull in other files with the @path/to/import syntax, and imported files are expanded and loaded at startup too (relative paths resolve against the containing file, up to 4 levels of recursion; wrapping the reference in backticks suppresses the import); an import in a project file that points outside the working directory triggers a confirmation dialog the first time. Also, Claude Code reads CLAUDE.md, not AGENTS.md — for repos that already have an AGENTS.md, the official advice is a one-line @AGENTS.md import in CLAUDE.md, or simply a symlink.

What belongs in it, and what doesn't

OfficialThe test is a single question: for every line, ask "if I delete this, will Claude make a mistake because of it?" If not, delete it. The official trade-off table:

Write thisNot this
Build/test/deploy commands Claude cannot guessAnything inferable from the code (directory layout, dependency lists)
Code style rules that differ from the defaultsUniversal language conventions (Claude already knows them)
How to run tests and the preferred test runnerDetailed API documentation (link to it instead)
Repo etiquette: branch naming, PR conventionsInformation that changes frequently
Project-specific architecture decisionsLong explanations and tutorials
Environment quirks (required environment variables)File-by-file codebase descriptions
Common pitfalls and unintuitive behaviorUnverifiable platitudes like "write clean code"

OfficialSize target: keep a single CLAUDE.md under 200 lines. An overlong file still loads in full, but it crowds the context and lowers compliance. Three writing principles: specific and verifiable ("indent with 2 spaces" beats "format properly"), structured (headings plus lists, not long paragraphs), and conflict-free (when two rules clash, Claude may follow either one). When splitting, note that @import only solves organization — imported files still load at startup and save no context; what actually saves context is the path-scoped rules below.

Rules: split instructions into files, activated per path

OfficialOnce the project grows, split instructions into the .claude/rules/ directory: one .md file per topic (say testing.md, api-design.md); subdirectories are discovered recursively too. Rules without a paths: frontmatter load at startup, with the same precedence as .claude/CLAUDE.md; rules with paths: load only when Claude reads a file matching the glob:

---
paths:
  - "src/api/**/*.ts"
---

# API Development Rules

- All API endpoints must include input validation
- Use the standard error response format

OfficialGlobs support multiple patterns and brace expansion (like src/**/*.{ts,tsx}). Personal, universal conventions can go in ~/.claude/rules/, applying to all projects and loading before project rules (project rules win); .claude/rules/ also supports symlinks, handy for sharing one rule set across repos. In a monorepo, if an ancestor directory drags in another team's CLAUDE.md, exclude it by path with the claudeMdExcludes setting (managed-policy files excepted). Note: rules are the same kind of thing as CLAUDE.md — advice Claude reads, not enforced configuration.

The classic misconception: "I wrote it and Claude ignores it"

OfficialThe misconception is treating CLAUDE.md as a config file where writing something guarantees effect. In reality it enters the context as a user message after the system prompt; Claude reads it and tries to follow it, but nothing enforces it. The official troubleshooting order: first run /context and check the Memory files list to confirm the file actually loaded (if it isn't listed, Claude cannot see it at all); then check that the file sits at a level that gets loaded; then make the instructions more specific; finally look for conflicts across multiple files.

Our takeIn practice the most common cause of "wrote it, ignored" is a file that is too long: the important rules drown in noise — the official best-practices doc warns in as many words that a bloated CLAUDE.md makes Claude miss your real instructions. /doctor can suggest trims for a committed CLAUDE.md (needs v2.1.206+). For the full diagnose-and-slim walkthrough, see the tip: Claude ignores your CLAUDE.md — it’s probably too long. And remember the division of labor: anything that must happen at a given moment (like running lint before every commit) should not be a hope written into CLAUDE.md — make it a hook (D8).

Hands-on: doable today

  1. If the project has no CLAUDE.md yet, run /init in a session. Expected: Claude analyzes the codebase and generates a CLAUDE.md with build commands, test practices, and project conventions; if one already exists, it proposes improvements rather than overwriting. Skip to the next step if you already have one.
  2. Run /context and confirm in the Memory files list that your CLAUDE.md (plus CLAUDE.local.md and rules) actually loaded. Expected: you can see the file list; a file that is missing means it sits at a level that isn't loaded.
  3. Use the prompt below to have Claude audit your CLAUDE.md and slim it against the table above:
Audit this project's CLAUDE.md (and .claude/rules/ if present):
1. List anything Claude could infer from the code itself (directory layout, dependency
   lists, architecture overview) and suggest deleting it;
2. List instructions too vague to verify (like "keep the code clean"), each with a
   specific, verifiable rewrite;
3. List contradictory or duplicated instructions;
4. For instructions that only apply to certain directories or file types, suggest moving
   them into .claude/rules/ with a paths: glob.
Output the audit report first; wait for my confirmation before changing any files.
  1. Build a path-scoped rule by hand: mkdir -p .claude/rules, then create .claude/rules/testing.md with content like the below (adjust the globs to your project). Then start a new session, run /context first to confirm it is not in the startup load list (rules with paths load on demand), then have Claude read a matching test file and restate the testing conventions currently in effect. Expected: the rule's content shows up in Claude's answer only after it reads a matching file.
    ---
    paths:
      - "**/*.test.ts"
      - "**/*.test.tsx"
    ---
    
    # Testing Rules
    - Name tests "should [expected result] when [condition]"
    - Mock external dependencies, not internal modules
  2. (Optional) Run /memory to browse where each level's memory files live, and open the auto memory directory to see what Claude has already noted about this project on its own.

How to know you've learned it

  • You can recite CLAUDE.md's four levels and their concatenation order without the diagram, and say which load at startup and which on demand.
  • You can name at least three kinds of content that do not belong in CLAUDE.md, and justify it with D2's context costs.
  • The project has a CLAUDE.md under 200 lines that passed the step-3 audit, plus at least one rules file with paths: whose on-demand loading you verified.
  • Self-test: a monorepo root and the subpackage you own each have a CLAUDE.md, and you start Claude Code in the subpackage directory — which ones load, and in what order? (Answer: both load at startup, root first, subpackage after; a CLAUDE.md in a deeper subdirectory below the subpackage loads on demand only once Claude reads a file in that directory.)