What Claude Code Loads From CLAUDE.md at Session Start, and How to Cut It#
Published by Alex Sorokoletov
Claude Code loads the CLAUDE.md files in your working directory and every directory above it, plus every file they @-import, before you type anything. A CLAUDE.md in a subdirectory waits until Claude reads, writes or edits a file in that subdirectory. Moving one 29 KB service-only doc from a root import to a nested CLAUDE.md cut startup memory from 25.3k to 14.8k tokens.
What loads at launch#
| Source | When it loads | How much |
|---|---|---|
~/.claude/CLAUDE.md (user) | Every session | Whole file |
CLAUDE.md and CLAUDE.local.md in the working directory and each directory above it | Every session | Whole file, up to 4 MiB |
Files those CLAUDE.md files @-import (up to four hops deep) | Every session, with the file that imports them | Whole file |
Auto memory MEMORY.md | Every session | First 200 lines or 25 KB |
CLAUDE.md in a subdirectory, and its imports | When Claude reads, writes or edits a file in that subdirectory | Up to a per-file limit (below) |
.claude/rules/*.md with a paths: field | When Claude reads, writes or edits a matching file | Up to the same limit |
| Auto memory topic files | When Claude decides to read one | Whole file |
The memory docs state the trap in one line: imports "help you organize a long file but don't reduce its context cost, because imported files load together with the CLAUDE.md that imports them." Splitting a long CLAUDE.md into @docs/*.md files makes it easier to read and costs exactly as much context.
A cat or head on a single file counts as a read, so a Bash look at worker/src/queue.ts triggers worker/CLAUDE.md the same way the Read tool does.
Measure before you move anything#
/context all lists every loaded memory file with its token count. It also runs headless, so a before/after comparison needs no interactive session:
echo "/context all" | claude -p --model haiku
Take a typical web app repository whose root CLAUDE.md is a short index of @ imports:
<!-- CLAUDE.md -->
- Architecture: @docs/ARCHITECTURE.md
- Scripts: @docs/SCRIPTS.md
- Styling: @docs/STYLING.md
- Testing: @docs/TESTING.md
- Background worker: @docs/WORKER.md
Every session pays for all five files. When docs/WORKER.md describes a queue worker in worker/ and is the largest of them, a CSS fix in src/ carries the whole worker manual. In the repository behind the numbers above, that one file was 10.5k of 25.3k memory-file tokens: more than the root CLAUDE.md and every other import put together.
Move part-of-the-repo docs behind a nested CLAUDE.md#
Keep the doc where it is, so existing links still work, and import it from a CLAUDE.md in the directory it describes:
<!-- worker/CLAUDE.md -->
Claude Code loads this file only when work touches worker/.
@../docs/WORKER.md
The relative path resolves from worker/CLAUDE.md, not from the working directory. A session started inside worker/ sees ../docs/ as outside its working directory, so the first interactive launch there asks once to approve external imports. Approve it, or move the doc into worker/.
In the root CLAUDE.md, replace the import with a plain pointer in backticks, so a session started at the root can still find the file. Text inside backticks is not imported:
- Background worker: `docs/WORKER.md`, loaded on demand through `worker/CLAUDE.md`
Test it with a fact that only the moved file contains, such as a default like QUEUE_MAX_RETRIES. Turn off file tools so the model cannot look it up, and ask from each place a session can start:
echo "Answer only from context already loaded. What is the default of QUEUE_MAX_RETRIES? If not in your context, reply NOT_IN_CONTEXT." \
| claude -p --model haiku --disallowedTools "Read,Bash,Grep,Glob,Agent,WebFetch,WebSearch"
| Session | Before | After |
|---|---|---|
| Started at the repository root | Knows it | NOT_IN_CONTEXT |
Started in worker/ | Knows it | Knows it (launch, ancestor walk) |
Started at the root, then reads one file in worker/ | Knows it | Knows it (on demand) |
The measured result: 25.3k memory-file tokens at a root launch became 14.8k, a 41% cut, and sessions started in the service directory still got the full doc. The same three answers came back on Claude Code v2.1.319.
The size limit on files that load on demand#
Since v2.1.309, a subdirectory CLAUDE.md or path-scoped rule that loads on demand has a per-file limit, and its imports count. Over the limit, Claude gets the start of the file and a note to read the rest, and the later instructions stay out of context until it does.
| Context window | Limit per file |
|---|---|
| 200K | 40,000 characters |
| 1M, Opus 4.6 and Sonnet 4.6 | 200,000 characters |
| 1M, later models | 150,000 characters |
Check with wc -m docs/WORKER.md. A 29 KB doc is about 29,000 characters, under the 40,000 limit with room to grow. A file loaded at launch has no such limit, so a doc that worked as a root import can lose its last sections after the move. Split one that is over the limit into several rules files; each counts on its own.
Pick the right home for each instruction#
- Root CLAUDE.md: rules for every task in the repository, kept under about 200 lines. It is re-read from disk after
/compact. - Nested CLAUDE.md: everything about one directory, such as a worker, a service or a package. It loads again on demand after compaction.
.claude/rules/withpaths:: rules for a file type spread across the tree, such as styling rules forsrc/**/*.tsxor test conventions for**/*.test.ts. Setup, triggers by version and the import gotcha: path-scoped rules in .claude/rules.- Auto memory: facts Claude learned from you. Only the
MEMORY.mdindex loads at launch, so keep it one line per memory. - A lint rule, test or hook: anything that must hold every time. CLAUDE.md is context, not enforcement. "Never import from
src/internal/outsidesrc/" written as prose costs tokens every session and still gets broken; as an ESLintno-restricted-importsrule it costs zero tokens and fails the build.
A monorepo can also skip other teams' ancestor files with claudeMdExcludes in settings.
FAQ#
Does an @import reduce context? No. The imported file loads with the CLAUDE.md that imports it, every session. Only a nested CLAUDE.md or a path-scoped rule defers loading.
When does a subdirectory CLAUDE.md load? When Claude reads, writes or edits a file in that subdirectory, including a cat or head on one file. Starting Claude Code inside the subdirectory loads it at launch.
How do I see which CLAUDE.md files loaded? Run /context all and check the Memory files list, or pipe it to claude -p for a scripted check.
Can a nested CLAUDE.md import a file outside its directory? Yes. @../docs/file.md resolves relative to the CLAUDE.md that holds it. A path outside the working directory needs a one-time approval.
Why does Claude ignore the end of my subdirectory CLAUDE.md? The file, with its imports, is over the per-file limit for files that load on demand: 40,000 characters on a 200K window. Split it into rules files.