Tutorials
Ren Okabe14 min read66 views

Claude Code Memory: CLAUDE.md vs Auto Memory, and What Actually Loads (2026)

Claude Code has two memory systems and only one of them is capped. A precise 2026 walkthrough of CLAUDE.md load order, the auto memory directory, the 200-line-or-25KB index limit, and a runnable linter that measures your MEMORY.md the way Claude Code measures it.

Updated on August 22, 2026

An open lined notebook with the word Notes handwritten and underlined, a fountain pen resting on the page and reading glasses behind it, August 2026
An open lined notebook with the word Notes handwritten and underlined, a fountain pen resting on the page and reading glasses behind it, August 2026
On this page

Quick answer

As of August 2026, Claude Code has two separate memory systems, and only one of them is capped. CLAUDE.md files are instructions you write; auto memory is notes Claude writes itself into ~/.claude/projects/PROJECT/memory/. Both load at the start of every session. The 200-line-or-25KB limit applies only to the MEMORY.md index, not to your CLAUDE.md (loaded in full up to 4 MiB) and not to the topic files beside it (read on demand, uncapped). Neither system is enforcement: both are delivered as context, so if you need an action blocked every time, use a hook instead.

That distinction is where most of the confusion in this topic comes from, so the rest of this tutorial walks the two systems separately, then gives you a linter that measures your own index the way Claude Code measures it.

Anthropic logo Every behavioural claim below is sourced to Anthropic's current published documentation at code.claude.com/docs/en/memory (retrieved August 22, 2026) and version-stamped where the behaviour changed. Where a widely-cited third-party guide disagrees with the docs, I say so and show the discrepancy rather than picking one quietly.

The two systems, side by side

Scroll to see more

CLAUDE.md filesAuto memory
Who writes itYouClaude
What it holdsInstructions and rulesLearnings and patterns
ScopeProject, user, or organizationPer repository, shared across worktrees
Loaded intoEvery sessionEvery session (first 200 lines or 25KB of the index)
Good forBuild commands, conventions, project layoutYour preferences, corrections you gave Claude, context not derivable from the code

The documentation is explicit that both are "loaded at the start of every conversation" and that "Claude treats them as context, not enforced configuration."

Where CLAUDE.md files live, and the order they load

Locations, from broadest scope to most specific. Load order matters because everything is concatenated rather than overridden, so the file nearest your working directory is read last.

Scroll to see more

ScopeLocation
Managed policymacOS /Library/Application Support/ClaudeCode/CLAUDE.md, Linux and WSL /etc/claude-code/CLAUDE.md, Windows C:\Program Files\ClaudeCode\CLAUDE.md
User~/.claude/CLAUDE.md
Project./CLAUDE.md or ./.claude/CLAUDE.md
Local (gitignore this)./CLAUDE.local.md

Two resolution rules that are easy to get wrong:

  1. Ancestor directories load at launch; subdirectories do not. Run Claude Code in foo/bar/ and it loads foo/bar/CLAUDE.md and foo/CLAUDE.md at startup. A CLAUDE.md sitting in a subdirectory below your working directory is only pulled in when Claude actually reads a file in that directory.
  2. Within a directory, CLAUDE.local.md is appended after CLAUDE.md, so your personal notes are the last thing Claude reads at that level.

Managed policy files cannot be excluded by individual settings. Everything else can, via claudeMdExcludes, which takes glob patterns matched against absolute paths and merges across settings layers:

json
{
  "claudeMdExcludes": [
    "**/monorepo/CLAUDE.md",
    "/home/user/monorepo/other-team/.claude/rules/**"
  ]
}

Imports, and the depth limit

CLAUDE.md can pull in other files with @path/to/file syntax. Relative paths resolve against the file containing the import, not the working directory, and imports recurse to a maximum depth of four hops.

text
See @README for project overview and @package.json for available npm commands.

# Additional Instructions
- git workflow @docs/git-instructions.md

Import parsing skips code spans and fenced blocks, so wrapping a path in backticks keeps it literal. Note that importing does not save context: imported files are expanded and loaded at launch, so a 400-line file split into four imports is still 400 lines in your context window.

Cursor logo GitHub logo If your repo already carries config for other agents, /init reads Cursor rules (.cursor/rules/ or .cursorrules) and Copilot rules (.github/copilot-instructions.md) and folds the relevant parts into the generated CLAUDE.md. With CLAUDE_CODE_NEW_INIT=1 set it also reads AGENTS.md, .devin/rules/, .windsurf/rules/ and .clinerules. Claude Code itself reads CLAUDE.md, not AGENTS.md, so if you keep the latter, import it:

markdown
@AGENTS.md

## Claude Code

Use plan mode for changes under `src/billing/`.

Where auto memory lives

Auto memory is a directory, not a file:

text
~/.claude/projects/PROJECT/memory/
├── MEMORY.md           # index, one line per memory, loaded every session
├── user_role.md        # one memory
├── feedback_testing.md # one memory
└── ...

Claude records what kind of memory each file holds in a type frontmatter field, and there are four kinds: user (your role, expertise, working preferences), feedback (corrections you gave it, approaches you confirmed), project (ongoing work, deadlines, decisions not derivable from code or git history), and reference (where to find things outside the project). It deliberately skips anything derivable from the codebase, and anything your CLAUDE.md already says.

That PROJECT segment (written as a placeholder in the docs) is derived from the git repository. This is the single most misreported detail in this topic, so state it precisely: all worktrees and subdirectories of the same repo share one auto memory directory. Auto memory is also machine-local and is not synced across machines or cloud environments.

Since v2.1.234 you can override the directory name with CLAUDE_CODE_PROJECT_DIR_NAME alongside CLAUDE_CONFIG_DIR, which makes every project launched under that config directory share one memory directory. To relocate the directory entirely, set autoMemoryDirectory (absolute path or ~/-prefixed) in any settings scope:

json
{
  "autoMemoryDirectory": "~/my-custom-memory-dir"
}

Turning auto memory on and off

Auto memory is on by default. None of the currently-ranking guides I read mention how to change that, so for completeness there are three levers:

  • Interactively: open /memory and use the auto memory toggle. This writes autoMemoryEnabled to your user settings at ~/.claude/settings.json.
  • Per project: set it in that project's settings.
json
{
  "autoMemoryEnabled": false
}
  • By environment variable: CLAUDE_CODE_DISABLE_AUTO_MEMORY=1.

Since v2.1.214, when Claude writes a memory file that already begins with YAML frontmatter, Claude Code stamps a modified field with an ISO 8601 timestamp, which tells both you and Claude how current the fact is. It never adds frontmatter to a file that has none, so files created without it stay without it.

One retention detail worth knowing: Claude Code deletes old session transcripts after cleanupPeriodDays, but excludes the memory directory from that sweep. Memory files persist until you or Claude edit or delete them.

What actually loads, and what silently does not

This is the part with real consequences, because content past the limit is dropped without an error at load time.

  • MEMORY.md: the first 200 lines or the first 25KB, whichever comes first. Anything past that is not loaded at session start.
  • Topic files (user_role.md and friends): not loaded at startup at all. Claude reads them on demand with its normal file tools when it needs them.
  • CLAUDE.md: loaded in full up to 4 MiB. A file larger than 4 MiB is skipped entirely. The 200-line figure you see quoted for CLAUDE.md is a recommendation for adherence, not a truncation point.

The measurement rule is the trap. Claude Code strips YAML frontmatter and block-level HTML comments before measuring, so those do not count against your budget. Before v2.1.211 it measured the raw file, and frontmatter alone could trip the error on a file that actually fit.

A plain wc -l therefore gives you the wrong number. So here is a linter that gives you the right one.

Step-by-step: audit what your project really loads

Step 1. See what loaded in this session. Run /context and read the list under Memory files. This is the only authoritative answer for the session you are in. If a file is not listed there, Claude cannot see it, and no amount of rewording will help.

Step 2. See what exists. Run /memory. It lists your CLAUDE.md, CLAUDE.local.md and other memory locations across user and project scope, including entries for files that do not exist yet, and it gives you the auto memory toggle plus an option to open the memory folder. Since v2.1.216 a GUI editor opens in a separate window and you can keep using the session; before that, /memory blocked until you closed the file.

Step 3. Measure the index against the limit that actually applies. Save this as memory_budget.py and point it at a file, or at ~/.claude/projects to sweep every project at once.

python
#!/usr/bin/env python3
"""Measure MEMORY.md the way Claude Code measures it.

Claude Code loads the first 200 lines OR the first 25KB of MEMORY.md,
whichever comes first. YAML frontmatter and block-level HTML comments are
stripped before that measurement, so a plain `wc -l` overcounts.
"""
import re
import sys
from pathlib import Path

LINE_LIMIT = 200
BYTE_LIMIT = 25 * 1024

FRONTMATTER = re.compile(r"\A---\r?\n.*?\r?\n---[ \t]*\r?\n?", re.DOTALL)
# [<] and [>] are single-character classes matching exactly "<" and ">". They
# are equivalent to writing the delimiters bare, and verified to strip the same
# bytes; written this way so the pattern survives markdown pipelines that treat
# a literal HTML comment as markup.
BLOCK_COMMENT = re.compile(r"^[ \t]*[<]!--.*?--[>][ \t]*\r?\n?", re.DOTALL | re.MULTILINE)


def loadable(raw: str) -> str:
    """Return only the content Claude Code actually measures and loads."""
    body = FRONTMATTER.sub("", raw, count=1)
    return BLOCK_COMMENT.sub("", body)


def report(path: Path) -> int:
    raw = path.read_text(encoding="utf-8")
    body = loadable(raw)
    lines = body.splitlines()
    n_lines = len(lines)
    n_bytes = len(body.encode("utf-8"))

    print(f"{path}")
    print(f"  raw lines        {len(raw.splitlines())}")
    print(f"  loadable lines   {n_lines} / {LINE_LIMIT}")
    print(f"  loadable bytes   {n_bytes} / {BYTE_LIMIT}")

    truncated_at = None
    if n_lines > LINE_LIMIT:
        truncated_at = f"line {LINE_LIMIT}"
    running = 0
    for i, line in enumerate(lines, start=1):
        running += len((line + "\n").encode("utf-8"))
        if running > BYTE_LIMIT:
            byte_cut = f"line {i} (byte {BYTE_LIMIT})"
            if truncated_at is None or i < LINE_LIMIT:
                truncated_at = byte_cut
            break

    if truncated_at:
        print(f"  OVER LIMIT: content is dropped from {truncated_at} onward")
        return 1
    print(f"  within limits ({LINE_LIMIT - n_lines} lines of headroom)")
    return 0


if __name__ == "__main__":
    target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.home() / ".claude" / "projects"
    if target.is_dir():
        found = sorted(target.glob("*/memory/MEMORY.md"))
        if not found:
            print(f"no MEMORY.md under {target}")
            sys.exit(0)
        sys.exit(max(report(p) for p in found))
    sys.exit(report(target))

Python logo Run against a file with three lines of frontmatter, a two-line HTML comment and 205 entries, this reports 210 raw lines but 205 loadable against the 200 limit, and exits 1. The naive count would have told you 210 and left you trimming the wrong five lines.

text
MEMORY.md
  raw lines        210
  loadable lines   205 / 200
  loadable bytes   9732 / 25600
  OVER LIMIT: content is dropped from line 200 onward

TypeScript logo The same check in TypeScript, if you would rather drop it into an existing Node toolchain. Run with npx tsx memory-budget.ts MEMORY.md.

typescript
#!/usr/bin/env -S npx tsx
// Measure MEMORY.md the way Claude Code measures it.
import { readFileSync } from "node:fs";

const LINE_LIMIT = 200;
const BYTE_LIMIT = 25 * 1024;

const FRONTMATTER = /^---\r?\n[\s\S]*?\r?\n---[ \t]*\r?\n?/;
// [<] and [>] match exactly "<" and ">"; see the note in the Python version.
const BLOCK_COMMENT = /^[ \t]*[<]!--[\s\S]*?--[>][ \t]*\r?\n?/gm;

export function loadable(raw: string): string {
  return raw.replace(FRONTMATTER, "").replace(BLOCK_COMMENT, "");
}

export function report(path: string): number {
  const raw = readFileSync(path, "utf8");
  const body = loadable(raw);
  const lines = body.split("\n");
  if (lines.at(-1) === "") lines.pop();

  const nBytes = Buffer.byteLength(body, "utf8");
  console.log(path);
  console.log(`  raw lines        ${raw.trimEnd().split("\n").length}`);
  console.log(`  loadable lines   ${lines.length} / ${LINE_LIMIT}`);
  console.log(`  loadable bytes   ${nBytes} / ${BYTE_LIMIT}`);

  let cut: string | null = lines.length > LINE_LIMIT ? `line ${LINE_LIMIT}` : null;
  let running = 0;
  for (const [i, line] of lines.entries()) {
    running += Buffer.byteLength(line + "\n", "utf8");
    if (running > BYTE_LIMIT) {
      if (cut === null || i + 1 < LINE_LIMIT) cut = `line ${i + 1} (byte ${BYTE_LIMIT})`;
      break;
    }
  }

  if (cut) {
    console.log(`  OVER LIMIT: content is dropped from ${cut} onward`);
    return 1;
  }
  console.log(`  within limits (${LINE_LIMIT - lines.length} lines of headroom)`);
  return 0;
}

const target = process.argv[2];
if (!target) {
  console.error("usage: memory-budget.ts PATH_TO_MEMORY_MD");
  process.exit(2);
}
process.exit(report(target));

Both implementations produce identical output on the same fixtures, verified August 2026 on Python 3 and Node v24.

One honest caveat on the byte figure. The documentation says "25KB" without disambiguating 25,000 bytes from 25,600. The linter uses 25 * 1024. If your index is sitting within 600 bytes of the boundary, treat the result as approximate and trim anyway, because the failure mode is silent truncation.

Step 4. Fix an over-budget index the way Claude Code asks you to. After a write, Claude Code measures MEMORY.md and, if it is near a limit, reminds Claude to shorten it: one line per entry, detail moved into topic files, stale entries merged or dropped. If it is already over, the write still succeeds but Claude Code returns an error telling Claude to rewrite the index, precisely because everything past the limit is dropped on the next load. Keep the index an index.

Three things the top-ranking guides get wrong

I checked the current organic results for this query against the docs. Three claims in the ranking set are wrong or materially misleading, and one of them is currently being repeated by Google's own AI Overview.

1. Worktrees do not get separate memory directories. A widely-shared Substack explainer (published March 16, 2026) states that "if you are using Git worktrees, each worktree gets its own separate memory directory." The docs say the opposite, twice: the project path is derived from the git repository "so all worktrees and subdirectories within the same repo share one auto memory directory," and "all worktrees and subdirectories within the same git repository share one auto memory directory." This matters operationally: if you assumed per-worktree isolation, your worktrees have been sharing notes all along. As of August 22, 2026 the AI Overview for this query cites that page in its worktrees section, so the inverted claim is propagating.

The genuinely per-worktree file is a different one. A gitignored CLAUDE.local.md exists only in the worktree where you created it, which is why the docs suggest importing from your home directory instead if you want personal instructions everywhere:

text
# Individual Preferences
- @~/.claude/my-project-instructions.md

2. CLAUDE.md is not part of the system prompt. Several guides describe memory as being loaded "into the system prompt." The docs are specific: CLAUDE.md content "is delivered as a user message after the system prompt, not as part of the system prompt itself." That is not pedantry, it is the reason adherence is imperfect and the reason --append-system-prompt exists as a separate mechanism for instructions you genuinely need at system-prompt level.

3. The 200-line cap is not a cap on your notes. A well-researched Milvus writeup frames the system as capping out "at 200 lines of notes." The cap is on the MEMORY.md index. Topic files beside it are not subject to it and are read on demand. Worth flagging that this piece is dated April 3, 2026 and keyed to Claude Code v2.1.88, and it describes two subsystems, "Auto Dream" and "KAIROS", that do not appear anywhere in the current published documentation. It may well be accurate about the build it examined. Treat it as archaeology of a specific version rather than a description of today's behaviour, and note the docs do not document a retrieval mechanism at all, so any claim about how memories are searched is not currently first-party sourced.

Memory is context, not enforcement

This is the framing that saves the most time. Both systems shape behaviour; neither guarantees it. The docs say so plainly, and they name the alternative: "to block an action regardless of what Claude decides, use a PreToolUse hook instead."

So the decision rule is:

  • Guidance that should hold most of the time: CLAUDE.md, or a path-scoped rule under .claude/rules/.
  • A multi-step procedure, or something only relevant to part of the codebase: a skill or a path-scoped rule, so it loads on demand instead of every session.
  • Something that must happen at a fixed point every time, such as before a commit or after each edit: a hook. Hooks run as shell commands at lifecycle events and apply regardless of what Claude decides.

If Claude keeps ignoring an instruction, the order of debugging is: confirm it loaded with /context, check for a contradicting instruction in another CLAUDE.md in the tree (when two rules conflict, Claude may pick one arbitrarily), make the instruction concrete enough to verify, and only then consider moving it to a hook. There is also an InstructionsLoaded hook that logs exactly which instruction files loaded, when, and why, which is the fastest way to debug path-scoped and lazily-loaded rules.

Path-scoped rules, briefly

Rules in .claude/rules/ with a paths frontmatter field load only when Claude touches matching files:

markdown
---
paths:
  - "src/**/*.{ts,tsx}"
  - "tests/**/*.test.ts"
---

# API Development Rules

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

Brace expansion is budgeted: a rule's whole paths list shares a limit of 1,000 expanded patterns and 4 MiB, and any pattern exceeding it is used unexpanded, where its literal braces match nothing. Two version-specific traps: before v2.1.217 a paths value with many brace groups could stall or crash the CLI at startup, and before v2.1.207 a single invalid pattern (an unbalanced [, for instance) made the Read tool fail for every file the rule was evaluated against instead of simply matching nothing.

Subagents and memory

The main conversation's auto memory is not loaded into subagents. The exception is a fork, which inherits the parent conversation and system prompt. A subagent can maintain its own auto memory via the subagent memory field, and that is a separate directory. If you are wiring up delegation, the subagent memory field is the knob to reach for.

Worth separating two things that share a word. This tutorial is about the memory Claude Code keeps about your project. Giving an agent you are building durable recall across sessions is a different problem with different machinery, covered in the framework-free guide to agent long-term memory in Python.

Limitations and open questions

  • Adherence is not measurable from the outside. The docs tell you to be specific and concise and cap CLAUDE.md around 200 lines for adherence, but publish no numbers behind that recommendation. There is no first-party benchmark relating file length to instruction-following rate, so the 200-line guidance is a heuristic, and I have not seen it independently reproduced.
  • The retrieval mechanism is undocumented. Topic files are read "on demand using standard file tools." Whether that means a grep, an index, or a judgement call by the model is not stated, so any advice about wording your memories for retrievability is currently guesswork.
  • The 25KB boundary is ambiguous (see the caveat above) and I could not resolve it from published sources.
  • Auto memory writes are non-deterministic by design. The docs say Claude "doesn't save something every session" and decides based on future usefulness. That makes auto memory hard to test and, as one heavily-upvoted r/ClaudeAI thread points out, occasionally surprising: a remembered preference can cause later sessions to reach for tools you did not want. Audit it with /memory periodically rather than trusting it.
  • What I did not verify: everything here is read from Anthropic's published documentation on August 22, 2026 and reasoned about, plus two linters I wrote and ran. I did not empirically confirm the truncation behaviour by building a 201-line index and observing what the model can recall. If you do, I would like to know what you find.

P.S. The most useful thing in this whole system might be the least advertised: block-level HTML comments in CLAUDE.md are stripped before the content reaches Claude's context, so you can leave notes for human maintainers that cost you zero tokens.

Ren Okabe

Written by

Ren Okabe

Ren builds agent infrastructure and writes copy-paste tutorials for engineers shipping LLM tool-use systems.

Frequently asked questions

What is Claude Code memory?

Claude Code memory is two separate mechanisms that carry knowledge across sessions, because each session starts with a fresh context window. CLAUDE.md files are instructions you write. Auto memory is notes Claude writes itself into ~/.claude/projects/PROJECT/memory/. Both are loaded at the start of every conversation, and both are treated as context rather than enforced configuration.

What is the difference between CLAUDE.md and auto memory?

You write CLAUDE.md; Claude writes auto memory. CLAUDE.md holds instructions and rules (build commands, conventions, project layout) and is scoped to a project, user or organization. Auto memory holds learnings and patterns (your preferences, corrections you gave Claude, context it cannot derive from the code) and is scoped per repository. Only auto memory's MEMORY.md index is subject to the 200-line or 25KB load limit.

Where does Claude Code store its memory?

Auto memory lives in ~/.claude/projects/PROJECT/memory/, containing a MEMORY.md index plus one markdown file per memory. The project path is derived from your git repository. You can relocate the directory with the autoMemoryDirectory setting, which accepts an absolute path or one starting with ~/. CLAUDE.md files live separately, in the managed policy location, ~/.claude/CLAUDE.md, ./CLAUDE.md or ./.claude/CLAUDE.md, and ./CLAUDE.local.md.

Does each git worktree get its own Claude Code memory?

No. All worktrees and subdirectories within the same git repository share one auto memory directory, because the project path is derived from the repository rather than the working directory. Several popular guides state the opposite. The file that genuinely is per-worktree is a gitignored CLAUDE.local.md, which only exists in the worktree where you created it. To share personal instructions across worktrees, import a file from your home directory instead.

How do I turn off Claude Code auto memory?

Auto memory is on by default. Open /memory in a session and use the auto memory toggle, which saves autoMemoryEnabled to ~/.claude/settings.json. To disable it for one project, set autoMemoryEnabled to false in that project's settings. To disable it by environment variable, set CLAUDE_CODE_DISABLE_AUTO_MEMORY=1.

How big can a CLAUDE.md file be?

Claude Code loads a CLAUDE.md of up to 4 MiB in full and skips a file larger than that. The commonly quoted 200-line figure is a recommendation for instruction adherence, not a truncation point, and it is separate from the real 200-line-or-25KB limit that applies only to the MEMORY.md auto memory index. Imports do not reduce context, since imported files are expanded and loaded at launch.

Does Claude Code use a lot of RAM?

This question usually gets conflated with the memory feature, which is a different thing. Claude Code's memory system is about persisting instructions and notes across sessions in markdown files on disk, not about system RAM. The figures that matter for it are the 200-line or 25KB load limit on the MEMORY.md index and the 4 MiB ceiling on a CLAUDE.md file, both of which consume context-window tokens rather than machine memory.

Why is Claude not following my CLAUDE.md?

CLAUDE.md content is delivered as a user message after the system prompt, not as part of the system prompt, so there is no guarantee of strict compliance. Debug in this order: run /context and confirm the file appears under Memory files, check for contradicting instructions in another CLAUDE.md in the directory tree (when two rules conflict Claude may pick one arbitrarily), and make the instruction concrete enough to verify. If it must run at a fixed point every time, write it as a hook instead.

Tutorials

Claude Code Plan Mode: How It Actually Gates Your Edits (2026)

Plan mode is not a read-only state. It is a rule at step 4 of a six-step permission evaluation, which is why allow rules stop applying while you plan and why a session with bypass permissions can edit anyway. The CLI keystrokes, the settings, the Agent SDK equivalent, and a plan-then-execute pipeline in Python and TypeScript. August 2026.

14 min read73
Tutorials

Claude Code Subagents: How to Create, Scope, and Nest Them (2026)

The /agents creation wizard was removed in Claude Code v2.1.198, so almost every guide still ranking for this topic teaches a flow that no longer exists. Here is the current way to write, scope, invoke, and cap Claude Code subagents, with a version number on every claim. August 2026.

15 min read73