The four scopes
| Scope | Path | Committed | Audience |
|---|---|---|---|
| Managed | /etc/claude-code/CLAUDE.md (Linux)/Library/Application Support/ClaudeCode/CLAUDE.md (macOS)C:\ProgramData\ClaudeCode\CLAUDE.md (Windows) |
Deployed by IT | Whole organisation. Cannot be excluded |
| User | ~/.claude/CLAUDE.md |
No | Just you, across every project |
| Project | ./CLAUDE.md or ./.claude/CLAUDE.md |
Yes | Your team |
| Local | ./CLAUDE.local.md |
No, gitignore it | Just you, in this project |
Historical note. Older documentation marked
CLAUDE.local.mdas deprecated in favour of@imports. The current docs list it as a supported scope again, and both/memoryand/contextsurface it. If you find a blog post saying it is dead, the post is out of date, but read the git worktree trap for the one situation where the import approach is genuinely better.
Discovery order
Claude Code walks from the filesystem root down to your working directory.
Files higher in the hierarchy load first; the file closest to where you launched
loads last. Within a single directory, CLAUDE.local.md is appended after
CLAUDE.md.
graph LR
A["/etc/claude-code/<br/>CLAUDE.md<br/><i>managed</i>"] --> B["~/.claude/<br/>CLAUDE.md<br/><i>user</i>"]
B --> C["/work/<br/>CLAUDE.md"]
C --> D["/work/repo/<br/>CLAUDE.md<br/><i>project</i>"]
D --> E["/work/repo/<br/>CLAUDE.local.md<br/><i>local</i>"]
E --> F["/work/repo/services/api/<br/>CLAUDE.md<br/><i>lazy</i>"]
Critical: this is a reading order, not a precedence order. Nothing in this chain overrides anything else in it. See memory files have no precedence engine.
What belongs in each
Personal: ~/.claude/CLAUDE.md
How you like to work, everywhere. Preferences that no project would sensibly contradict.
# Personal defaults
- Show me the plan before any refactor touching more than three files.
- Prefer the standard library. Ask before adding a dependency.
- When comparing options, use a markdown table with a tradeoff column.
- Do not add comments that restate the code. Comment the "why", not the "what".
Keep this file short and keep it about you. The moment it contains a technical convention, it will eventually contradict a repository that does it differently, and you will have created an unresolvable conflict.
Project: ./CLAUDE.md
Facts about this project, for everyone. Prefer things Claude cannot infer by reading the code.
# payments-service
Go 1.23 service handling settlement. Deployed to Kubernetes via ArgoCD.
## Commands
| Task | Command |
|---|---|
| Build | `make build` |
| Unit tests | `make test` |
| Integration tests | `make test-integration` (needs `make dev-env` first) |
| Lint | `golangci-lint run` |
## Layout
- `cmd/` entrypoints
- `internal/` everything else; nothing here is importable externally
- `pkg/ledger/` the double-entry core. Changes here need a design note.
## Non-obvious
- `internal/clock` exists so tests can freeze time. Never call `time.Now()`
directly outside it.
- Migrations are forward-only. There is no down migration by design.
The “Non-obvious” heading is doing the heavy lifting. Anything Claude could work out from a quick read of the repository is wasted context; anything it could not possibly know is worth its weight several times over.
Local: ./CLAUDE.local.md
Your machine, your workflow, uncommitted.
# Local environment
- My Postgres runs on port 55432, not 5432.
- I use `podman`, not `docker`. Substitute in any command you suggest.
- Skip `vendor/` entirely; it is checked in but I never edit it.
Nested: services/billing/CLAUDE.md
Loads only when Claude touches that directory. This is where subsystem detail belongs in a monorepo.
# billing service
Spring Boot 3.3, Java 21. Owns the subscription lifecycle only; invoicing lives
in `services/invoicing`.
- The `BillingClock` bean is injected everywhere. Nothing calls `Instant.now()`.
- Webhooks from the payment provider land in `WebhookController` and are
idempotent by `provider_event_id`. Never make one non-idempotent.
- Integration tests need Testcontainers, so they need a running Docker daemon.
Imports
Any memory file can pull in another with @path:
# Project memory
@./docs/architecture.md
@./docs/api-conventions.md
# Individual preferences
- @~/.claude/my-project-instructions.md
- Both relative and absolute paths work, as does
~. - Imports resolve recursively to a maximum depth of 5.
- Imports inside fenced code blocks are not evaluated, so you can document the syntax without triggering it.
This keeps CLAUDE.md short while still having depth available. It does not reduce context cost, though: imported content is loaded eagerly, same as inline text. For lazy loading you want a skill or a path-scoped rule.
Size discipline
Target under 200 lines per memory file. Adherence measurably degrades on longer files; instructions in the middle of a 600-line CLAUDE.md get followed less reliably than the same instruction in a 60-line file.
When a memory file outgrows that:
flowchart TD
BIG["CLAUDE.md is over 200 lines"] --> Q1{"Is the content<br/>language- or<br/>path-specific?"}
Q1 -->|"Yes"| RULES["Move to .claude/rules/NAME.md<br/>with a paths: glob"]
Q1 -->|"No"| Q2{"Is it a procedure<br/>you invoke, rather<br/>than a standing fact?"}
Q2 -->|"Yes"| SKILL["Move to .claude/skills/NAME/SKILL.md"]
Q2 -->|"No"| Q3{"Does it only apply<br/>to one subdirectory?"}
Q3 -->|"Yes"| NESTED["Move to that directory's<br/>own CLAUDE.md — loads lazily"]
Q3 -->|"No"| IMPORT["Split into docs/*.md<br/>and @import it"]
The git worktree trap
CLAUDE.local.md is gitignored, which means it exists only in the worktree
where you created it. If you routinely git worktree add, every new worktree
starts without your local memory.
The documented workaround: keep the content in your home directory and import it
from the committed CLAUDE.md:
# Individual Preferences
- @~/.claude/my-project-instructions.md
The import silently no-ops for teammates who don’t have that file, so it is safe to commit.
Editing memory
| Method | Use for |
|---|---|
/init |
Bootstrapping a project CLAUDE.md by having Claude analyse the repo |
/memory |
Opening any discovered memory file in your editor |
# prefix in a prompt |
Quickly appending a line; Claude asks which file to write it to |
The # shortcut is how most memory accumulates in practice. Prune it
periodically: memory files rot, and a stale instruction is worse than a missing
one because it actively misleads. A useful habit is to re-read ./CLAUDE.md
whenever you finish a large refactor, since that is exactly when half of it
stops being true.
Excluding inherited memory
In a monorepo, an ancestor directory’s CLAUDE.md may be irrelevant or actively
wrong for your subproject. Exclude it by glob in .claude/settings.local.json:
{
"claudeMdExcludes": ["../../CLAUDE.md", "**/legacy/CLAUDE.md"]
}
This is the only way to make a memory file lose to another one. Everything else is additive. Note that managed policy memory cannot be excluded.
Two situations where it earns its place:
- A vendored subrepository that ships its own
CLAUDE.mddescribing a build you do not use. - A monorepo root whose instructions are written for the primary language and are actively misleading inside a small polyglot corner of the tree.
If you find yourself reaching for claudeMdExcludes often, the real problem is
usually that shared instructions were written as commands rather than as
conditionals. Rewriting Always write tests first as Write tests first unless the subproject's own CLAUDE.md says otherwise removes the need to exclude
anything.