Claude Code · Series · Part 2 of 3
CLAUDE.md: the project memory that Claude reads every time
In Part 1 we installed Claude Code and had a first session. Now the important question: what happens tomorrow? Open a new session and Claude has no idea who you are, what the project is, or the three things you corrected yesterday. This part explains why that happens and how a plain text file solves it.
- A model has no memory between conversations. Everything it “knows” must be in the text it is given.
CLAUDE.mdis a markdown file that Claude Code loads into that text at the start of every session.- It can live in your home folder (personal), the project root (shared), or sub-folders (specific).
- Bigger projects split into a
.claude/folder of rules and skills. /initdrafts one for you;/memoryopens it for editing.- Good files are short, specific and checkable. Bad ones are long, vague or out of date.
1. Why Claude forgets
If you read my earlier post on how AI chat works, this will sound familiar. A language model does not store your conversation. Every time you send a message, the app packs the conversation so far into one block of text, called the context, and hands it to the model. The model can only look at a fixed amount of text at once, the context window.
When you close the session, that block is gone. A new session starts with an empty window. The model is the same, but it is meeting you for the first time.
2. What CLAUDE.md is
It is exactly what the name says: a file called CLAUDE.md, written in markdown (the same simple format as a README). When Claude Code starts, it finds these files and adds their text to the context, ahead of your first message. The model then treats the contents as standing instructions.
Imagine a contractor with perfect skills but no memory, who forgets everything overnight. You would not re-brief them every morning. You would leave a note on the monitor: “Build with this command. Tests live here. We use tabs, not spaces. Never edit the generated folder.” CLAUDE.md is that note, and Claude reads it first every day.
It is different from your README.md. A README is written for humans who might contribute. A CLAUDE.md is written for the assistant, and contains the things you would otherwise keep repeating in chat.
3. All the ways Claude “remembers”
Before going further, one important fact: Claude does not remember anything on its own. The model is the same on day 10 as on day 1, and it forgets when a session ends. Everything that looks like memory is Claude Code putting saved text back into the context window. There are only a few ways that happens:
| Kind of memory | How it works | Lasts how long |
|---|---|---|
| The conversation | Everything said so far is re-sent with each message (the context window). | One session. /clear or closing it empties the window, though the transcript is saved and can be reopened with /resume. |
| CLAUDE.md files | Loaded automatically at the start of every session. | Permanent: they are files on disk. |
| Imports and linked notes | @docs/file.md loads with CLAUDE.md; a feature or handoff note is read when Claude is told to. | Permanent while the file exists. |
| Settings files | Not knowledge, but saved behaviour: permissions, default model. | Permanent. |
| Resumed sessions | claude --continue or --resume reloads an old conversation. | Brings the old text back, which costs tokens. On Pro and Max, resuming a large session after a long break offers to continue from a summary instead. |
| Automatic memory | Claude can write notes for itself in a memory folder under ~/.claude/projects/ (see below). Per the official docs it is on by default in local sessions. A MEMORY.md index is loaded at the start of each session; the first 200 lines or 25 KB of it. | Permanent until you or Claude edit or delete it. Still saved text read back in, not the model learning. |
This is also why plan mode (Part 1) does not remember anything: a plan lives in the conversation, so it is gone with it unless it is saved to a file.
Where Claude Code stores things on your computer
If you open ~/.claude/projects/ in Finder or a file manager, you will find one folder per project. According to the official Manage sessions page, each transcript is saved as ~/.claude/projects/<project>/<session-id>.jsonl, where <project> is your working folder's path with every non-alphanumeric character replaced by -. So /Users/me/work/my-app becomes -Users-me-work-my-app, and a session started in / shows up as a folder called just -. Auto memory lives in a memory folder inside the same project folder, and per the memory docs it is shared by all sub-folders and git worktrees of one repository.
memory folder of notes.| What you see | What it is |
|---|---|
-Users-…-my-app/ | One folder per project path where you ran claude. |
<long-id>.jsonl | A session transcript: every message and tool call of one conversation, one JSON entry per line. This is what --resume and --continue reopen. |
memory/ | Automatic memory for that project: a MEMORY.md index (one line per memory) plus one small topic file per memory. Only the start of the index is loaded each session; Claude reads the topic files on demand. |
- Transcripts can contain everything you pasted or showed Claude, including code, tokens and customer data. Do not share or commit the folder.
- You can open
memory/and edit or delete a note. That is how you correct something Claude remembered wrongly. - Deleting a project folder removes that project's history and memory, and nothing else. You lose
--resumeand the saved notes for it. - Old transcripts are deleted automatically after a retention period, 30 days by default (the
cleanupPeriodDayssetting). Memory files are excluded from that clean-up, so they stay until you or Claude edit or delete them. - Auto memory is machine-local. It is not shared with teammates or other computers, so anything the team needs belongs in CLAUDE.md, which you commit.
What auto memory saves, and how to control it
The official docs describe four kinds of notes Claude may save for itself:
| Type | What it holds |
|---|---|
user | Your role, expertise and working preferences. |
feedback | Corrections you gave and approaches you confirmed. |
project | Ongoing work, deadlines and decisions that cannot be derived from the code or git history. |
reference | Where to find things outside the project, such as a tracker or dashboard. |
- It is selective. Claude skips anything it can derive from the codebase and anything your CLAUDE.md already says, and it does not save something every session.
- You can see it happen. Messages like “Saved 2 memories” or “Recalled 2 memories” appear in the interface.
- You can switch it off. Run
/memoryand use the auto memory toggle, set"autoMemoryEnabled": falsein a project's settings, or set the environment variableCLAUDE_CODE_DISABLE_AUTO_MEMORY=1.
It is still just text. It helps only if the notes are correct and short, and it takes up context like any other file, so review it from time to time just as you would CLAUDE.md. Details: auto memory in the official docs.
What happens if you say “remember this session”?
By itself, nothing is guaranteed. Saying it does not change the model. Whether anything survives depends on whether Claude actually writes a file:
- It edits a file (you see an
Update(CLAUDE.md)orWrite(...)line and a diff). That text loads next session. - It only replies “Okay, I'll remember” and no file changes. Nothing is saved, and a fresh session will not know.
- Auto memory may save something if it is on. Claude decides what is worth remembering, so it is not guaranteed. You will see “Saved N memories” when it happens.
- Vague wording may give you a short note, a long summary or nothing.
To check, look for the file write in the session or run git diff. Part 3 shows a better way, an explicit handoff note.
4. Where the file can live
You can have several, and Claude Code combines them. Each location has a different audience:
| Location | Who it is for | Typical content |
|---|---|---|
~/.claude/CLAUDE.md | You, in every project | Personal preferences: “explain simply”, preferred language, how you like commit messages. |
./CLAUDE.md or ./.claude/CLAUDE.md | Everyone on the project (commit it to git) | Build and test commands, architecture, conventions. |
./CLAUDE.local.md | You, in this project only. Add it to .gitignore. | Your sandbox URLs, preferred test data, personal habits for this repo. |
CLAUDE.md in a sub-folder | Work inside that folder | Rules for one module, such as server/ or app/. Loaded once Claude reads or edits a file in that folder. |
| Managed policy file | Everyone in an organisation | Company standards set by IT, in a system folder (for example /Library/Application Support/ClaudeCode/CLAUDE.md on macOS). |
A good split is: personal taste at the user level, how this project works at the root, and how this one area works in the sub-folder. Commit the root file so your whole team gets the same assistant behaviour.
How they combine, per the official docs: Claude Code loads the CLAUDE.md in your working folder and in every folder above it at launch, and files in sub-folders on demand. They are added together, not overridden, ordered from the filesystem root down, so the instructions closest to where you started are read last. If your repository already has an AGENTS.md for other coding tools, Claude Code can read that when there is no CLAUDE.md, or you can import it from one. See How Claude remembers your project.
5. Creating one with /init
You do not have to start from a blank page. Run /init and Claude reads your project (build files, folders, existing docs) and drafts a CLAUDE.md.
> /init
● Analyzing your codebase…
Read(build.gradle.kts)
Read(README.md)
List(app/src)
● Created CLAUDE.md
Build commands, module layout, and conventions found in the code.
Review it, then trim anything that is not useful.
The draft is usually decent about commands and structure. It cannot know your team's unwritten rules or what has gone wrong before, so that part is yours to add. If a CLAUDE.md already exists, /init suggests improvements instead of overwriting it. To confirm a file really loaded, run /context and look under Memory files.
6. What to put in it
The test for every line is simple: would Claude get this wrong, or waste time finding it, without being told? If not, leave it out.
Specific beats vague. Compare these two lines:
| Vague | Specific |
|---|---|
| Format code properly. | Run ./gradlew ktlintFormat before committing. |
| Keep tests passing. | Run ./gradlew testDebugUnitTest; all tests must pass before you say a task is done. |
| Follow our architecture. | UI calls a ViewModel, the ViewModel calls a Repository. Never touch the database from a screen. |
7. A complete example
Here is a realistic root file for the Android chat app from my earlier post. It is deliberately short.
# Private AI Chat (Android)
Kotlin + Jetpack Compose app that chats with an on-device model
(llama.cpp) or an online model, with fallback between them.
## Commands
- Build debug: ./gradlew assembleDebug
- Unit tests: ./gradlew testDebugUnitTest
- Lint/format: ./gradlew ktlintFormat
- Release bundle: ./gradlew :app:bundleRelease
## Structure
- app/ui/ Compose screens (no business logic)
- app/chat/ ViewModels, builds the prompt
- app/engine/ LlmEngine interface + local / online / rules impls
- app/data/ Room database (chats, memories, settings)
- native/ llama.cpp + JNI bridge (C++)
## Conventions
- Screens never call an engine directly: go through the ViewModel.
- Every engine returns Flow<String> of text pieces.
- All native calls run on the single dedicated native thread.
- Use https only for online models; never log prompts or API keys.
## Gotchas
- The native build must target plain armv8-a, or older phones crash (SIGILL).
- Test release builds on a real device: R8 can strip JNI classes.
## Definition of done
- Unit tests pass and ktlint is clean.
- New user-visible strings are in strings.xml.It is about thirty lines. It answers the questions Claude would otherwise spend time on: how to build, where things live, and the two traps that already cost me a day each.
8. Splitting it up with imports
A CLAUDE.md can pull in other files by writing @ followed by a path. That keeps the main file small while still pointing at the detail.
See @README.md for the project overview.
Architecture details: @docs/architecture.md
Release checklist: @docs/release.mdImported files are expanded and loaded at launch along with the main file, so they count toward its size: imports help you organise a long file but do not make it cheaper. A few rules from the official docs:
- Relative paths resolve from the file that contains the import, not from your working folder.
- Imported files can import others, up to four levels deep.
- Imports inside backticks or code blocks are ignored, so write
`@README`in backticks to mention a path without importing it. - The first time a project imports a file from outside the working folder (for example your home folder), Claude Code asks you to approve it.
Use imports to organise, not to hide an enormous document. In Part 3 we will look at the cost side of this.
9. Growing up: the .claude folder
One CLAUDE.md is enough for a small project. As a project grows, a single file turns into a long list that is expensive to load and hard to maintain. Claude Code lets you spread the knowledge across a .claude/ folder in the project, so each piece has a clear job and loads only when it is useful. A typical layout looks like this (the official reference for all of it is the memory documentation):
The pieces, one by one
| Piece | What it is | Loaded | Guaranteed? |
|---|---|---|---|
CLAUDE.md | Core notes: commands, structure, key conventions. | Every session | No, it is guidance |
rules/*.md | Topic files such as testing, build or CI. Easier to review than one giant file. In recent versions a rule can be limited to certain paths, so it applies only there. | Session start, or when the matching files are in play | No, it is guidance |
skills/<name>/SKILL.md | A reusable recipe for one job, with optional extra files beside it. | Only its short description is always visible; the body loads when the task matches | No, Claude decides when to use it |
settings.json | Team settings: permissions, default model. Committed. | Session start | Yes, enforced by the tool |
settings.local.json | Your personal overrides for this project. Keep it out of git. | Session start | Yes, enforced by the tool |
The most useful distinction is the last column. CLAUDE.md, rules and skills ask Claude to behave a certain way. Settings make it happen. If Claude must never read a file, do not rely on a sentence in CLAUDE.md. Use a deny rule in settings.json.
Rules: split by topic
Move a long section out of CLAUDE.md into its own file and keep one line behind:
# Testing rules
- Unit tests live next to the code in src/test/.
- Name tests: methodName_condition_expectedResult.
- Run ./gradlew testDebugUnitTest before saying a task is done.
- Do not mock the database; use the in-memory one.Each file stays short, has an obvious owner, and a pull request that changes testing rules touches only testing.md. A modules/ sub-folder works well for one rule file per module. Official reference: Organize rules with .claude/rules/.
A rule can also be limited to certain files with a paths list at the top. It then loads only when Claude works on a matching file, so it costs nothing elsewhere:
---
paths:
- "src/api/**/*.ts"
---
# API rules
- Every endpoint validates its input.
- Use the standard error response format.A rule without paths loads every session, like CLAUDE.md.
Skills: a recipe that loads on demand
A skill packages a procedure you would otherwise paste into chat again and again, such as “how we write release notes”. Claude sees only a short description until the task matches, then reads the full instructions. That is the token saving: long how-to text costs nothing on unrelated tasks.
---
name: create-release-notes
description: Write release notes from merged changes. Use when asked to
prepare or draft release notes for a version.
---
1. List merged pull requests since the last tag.
2. Group them: Features, Fixes, Internal.
3. Write one plain-language line per change; no ticket numbers.
4. Follow the layout in reference.md.Extra files beside SKILL.md (like reference.md) are read only if the skill points to them. Official reference: Extend Claude with skills.
A fuller settings.json: a deny list
Here is a more complete project .claude/settings.json with the thing I find most useful on a real project: a list of files Claude must not read. The file names are examples; replace them with your own.
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./secrets.properties)",
"Read(./**/*.keystore)",
"Read(./**/*.pem)",
"Read(./node_modules/**)",
"Read(./package-lock.json)",
"Read(./build/**)",
"Read(./.idea/**)"
]
}
}The deny list does two jobs
- Protects secrets. Keystores, key files and local credential files are blocked by the tool itself, so Claude cannot read them even if asked. This is enforced by Claude Code, not by a sentence in CLAUDE.md.
- Saves tokens. Lock files, dependency folders, build output and IDE folders are huge and tell Claude almost nothing. Without a deny rule, one broad search can pull thousands of lines of
package-lock.jsoninto the context for no benefit.
Treat the list as a guardrail, not a vault. The safest secret is one that is not in the project folder at all.
Exact rule syntax changes between versions, so confirm it against the official settings and permissions pages before copying it.
- How Claude remembers your project: CLAUDE.md locations, imports,
.claude/rules/, path-scoped rules,AGENTS.mdand auto memory. - Extend Claude with skills:
.claude/skills/<name>/SKILL.mdand how skills load. - Settings files and precedence:
settings.json,settings.local.jsonand which one wins. - Configure permissions:
allow,askanddenyrules.
These pages move and grow as Claude Code changes. If a name or option here does not match your version, the docs are right and this post is out of date.
What goes in git, and what does not
- Commit:
CLAUDE.md,rules/,skills/,settings.json. The whole team gets the same assistant. - Do not commit:
settings.local.jsonand your personal~/.claude/folder. Add the local file to.gitignore. - Never store keys, tokens or customer data in any of these files, committed or not.
- Review changes to these files in pull requests like code, because a bad rule or setting affects every future session.
Start small. Begin with one CLAUDE.md. Split out a rules file only when a section gets long, and add a skill when you paste the same instructions a third time.
10. Keeping it up to date
Three easy ways to change the file:
- Type
/memoryto list your CLAUDE.md and memory files, open one in your editor, and toggle auto memory. - Tell Claude in plain words: “Add to CLAUDE.md that we always run ktlint before commits.” It will edit the file for you.
- Edit it like any other file and review the change in a pull request.
> Remember: the debug build needs JAVA_HOME set to JDK 17.
Add it to CLAUDE.md.
● Update(CLAUDE.md)
+ - Needs JDK 17: set JAVA_HOME before running Gradle.
● Added under ## Commands.
Make this a habit: if you correct Claude twice, put the correction in the file. Equally, when a line stops being true, delete it. A wrong note is worse than none, because Claude will follow it with confidence.
Two more tips from the official docs. First, /doctor prompt-audit asks Claude to check your instruction files for outdated or conflicting content and proposes edits (it changes nothing until you agree, and it needs a recent version). Second, block-level HTML comments such as <!-- maintainer notes --> are stripped before CLAUDE.md is sent to Claude, so you can leave notes for people without spending tokens on them.
11. What it cannot do
- It is guidance, not enforcement. Claude usually follows it, but nothing guarantees it. For rules that must never break, use the permission settings from Part 1's settings file.
- It takes up space. The file sits in the context window for the whole session, using room that could hold your code or conversation.
- It is not learning. Claude does not get smarter from your usage. The model is fixed, and a new session on day 10 starts as blank as on day 1. Only what is written in the file carries over, so the file is your memory, not Claude's.
- It is not secret. It is plain text, usually committed to git. Never put credentials in it.
- It can go stale. Nobody updates what nobody owns. Treat it as code: review it, and prune it.
That second point, the cost of space, is where Part 3 begins: how to make the file earn its tokens, and how it helps when you join a new project or start a new feature.
| Term | In plain words |
|---|---|
| Context window | The fixed amount of text the model can read at once. |
| CLAUDE.md | A markdown file loaded into the context at the start of each session. |
| /init | Command that drafts a CLAUDE.md from your project. |
| /memory | Command that opens your memory files for editing. |
| Rules | Topic files in .claude/rules/, split out of CLAUDE.md. |
| Skill | A reusable recipe whose full text loads only when the task matches. |
Import (@path) | Pulls another file's contents into CLAUDE.md. |
| Standing instruction | A rule present at the start of every session. |