Why layers at all?
In level 1 you mapped which surface owns which rule. CLAUDE.md is the simplest of those surfaces: plain markdown that Claude Code reads into context so you don't have to repeat yourself every session. The catch is that there are several of them, and they answer different questions:
- What does the company want everywhere?
- What does this repo need?
- What do I prefer?
- What do I need in this repo only, which nobody else should see?
Think of it like git config: system, global and repo level, each one narrower than the last. One important difference is that CLAUDE.md layers do not override each other. They are concatenated. Claude sees all of them at once, as context to weigh. Nothing here is enforced, so a rule that must never be broken belongs in a hook, which comes later in the course.
The four scopes
| Scope | Location | Who gets it |
|---|---|---|
| Organisation (managed policy) | macOS: /Library/Application Support/ClaudeCode/CLAUDE.md; Linux/WSL: /etc/claude-code/CLAUDE.md; Windows: C:\Program Files\ClaudeCode\CLAUDE.md |
Everyone on a machine IT manages |
| User | ~/.claude/CLAUDE.md |
Only you, in every project |
| Project | ./CLAUDE.md or ./.claude/CLAUDE.md |
Everyone who clones the repo |
| Local | ./CLAUDE.local.md (add it to .gitignore) |
Only you, in this repo |
The files load in that order, broadest first and most specific last. The practical test for placement is: does it travel through git? Only the project file does. A teammate who clones the repo never receives your ~/.claude/CLAUDE.md or your CLAUDE.local.md. If the team relies on a rule that lives in your user file, it only works on your machine.
Walkthrough: build the hierarchy
1. The project file, short and specific. Run /init to get a first draft, then cut it hard.
# CLAUDE.md (repo root, committed)
## Commands
- Install: `pnpm install`
- Test one file: `pnpm vitest run path/to/file.test.ts`
- Lint before every commit: `pnpm lint`
## Conventions
- TypeScript strict mode; no `any` without a comment explaining why.
- API handlers live in `src/api/`, one file per route.
## More detail
- Git workflow: @docs/git-workflow.md
Every line should be something Claude could not work out by reading the code, and something it could get wrong.
2. The @ import. The @docs/git-workflow.md line pulls that file into context alongside CLAUDE.md. What you need to know:
- A relative path resolves from the file containing the import, not from the directory you launched in.
- Imported files can import others, to a maximum depth of four hops.
- An
@pathinside a code span or fenced code block is ignored. That means@anthropic-ai/claude-codein a snippet is safe, but it also means an import you accidentally wrapped in a fence does nothing. - The first time a project imports a file from outside the project, Claude Code asks you to approve it.
Gotcha: imports tidy the file; they do not save context. An imported file loads at launch with its parent. Splitting a 600-line CLAUDE.md into six imports still costs 600 lines.
3. Your personal file.
# ~/.claude/CLAUDE.md (never in any repo)
- Explain the plan in three bullets before editing more than two files.
- I use PowerShell; give shell examples in PowerShell.
4. The local file, for this repo only.
# CLAUDE.local.md (repo root, untracked)
- My dev database is on port 5433, not the default.
- My sandbox URL: http://localhost:4010
echo "CLAUDE.local.md" >> .gitignore # keep it out of version control
Within a directory, CLAUDE.local.md is appended after CLAUDE.md, so your notes are the last thing Claude reads at that level.
What loads when
Claude Code looks in the directory you launch from and every directory above it, and loads those files in full at session start. If you launch in repo/services/billing/, you get repo/services/billing/CLAUDE.md, repo/services/CLAUDE.md and repo/CLAUDE.md, plus any CLAUDE.local.md beside them.
Files below your working directory behave differently. They load on demand, when Claude reads or edits a file in that subdirectory.
repo/ <- launch here
CLAUDE.md loaded at start
services/billing/
CLAUDE.md loaded only once Claude touches a file in billing/
So a billing rule that is missing at the start of a session is not a bug. Claude has not been into that directory yet.
Diagnosing an ignored instruction
Work through these in order:
- Did it load? Run
/memory. It lists the instruction files loaded for this session and lets you open them. If the file is missing, check its location. The usual causes are a subdirectory file that has not been triggered yet, or an import inside a code fence. - Is it vague? "Write clean code" can be read many ways. Rewrite it as something you could check: "Run
pnpm lintbefore every commit." - Does it conflict? If your user file says "use npm" and the project file says "use pnpm", Claude may pick either. Delete one; don't rely on a winner.
- Is the file too long? Aim for under 200 lines per CLAUDE.md. Longer files use more context, and each rule gets less attention.
From experience: most "Claude ignored my rule" reports turn out to be step 2 or 3. The rule loaded fine and was just too vague, or contradicted by another file.
Key takeaways
- There are four scopes: organisation, user, project and local. They are concatenated broadest to most specific, not overridden.
- Only the project CLAUDE.md reaches teammates through git. User and local files stay on your machine.
@pathimports keep the shared file readable but still load at launch. Paths resolve from the importing file, nest to four hops, and are ignored inside code blocks.- Files in ancestor directories load at start; files in subdirectories load when Claude works there.
- To debug, check
/memoryfirst, then fix wording that is vague, conflicting or bloated.