All sections

03Foundations

Memory files

The CLAUDE.md family. Four scopes, a discovery order that is not a precedence order, imports capped at depth five, and the one setting that lets a memory file actually lose to another.

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.md as deprecated in favour of @ imports. The current docs list it as a supported scope again, and both /memory and /context surface 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.md describing 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.