CLAUDE.md: Project Memory for Claude Code

You told Claude Code last week that this project uses PowerShell 7, that tests run with Invoke-Pester, and never to touch web.config. Today it is asking again. Every session starts with an empty context window, so anything you want it to know every time has to be in a file it reads at startup. That file is CLAUDE.md.

This post covers where the file lives and in what order the copies load, what belongs in it and what does not, the /init shortcut, the private CLAUDE.local.md, splitting a long file into .claude/rules/, and the auto memory Claude writes for itself. Everything here is from the current Claude Code docs, linked at the end. If Claude Code is not installed yet, start with Install Claude Code on Windows, Native and in WSL.

Where CLAUDE.md Lives

There are 4 places, and Claude Code loads them broadest first, so the project file is the last thing it reads:

  • Managed policy: C:\Program Files\ClaudeCode\CLAUDE.md on Windows, pushed by IT. Most people never see one.
  • User: ~\.claude\CLAUDE.md, which on Windows is C:\Users\<you>\.claude\CLAUDE.md. Your own preferences, every project.
  • Project: .\CLAUDE.md or .\.claude\CLAUDE.md in the repo. Commit it, the team shares it.
  • Local: .\CLAUDE.local.md next to the project file. Yours alone, add it to .gitignore.

Claude Code also walks up from the folder you launched it in and loads every CLAUDE.md and CLAUDE.local.md it finds on the way to the drive root, root first. Files in subdirectories below you are not loaded at launch; each one loads the first time Claude reads, writes or edits a file in that subdirectory. All of them are concatenated into context, none overrides another.

# the 2 you will create by hand, if they do not exist yet
Test-Path "$env:USERPROFILE\.claude\CLAUDE.md"
Test-Path .\CLAUDE.md

To see which files actually loaded, run /context all inside a session and read the list under Memory files. If a file is not in that list, Claude cannot see it, whatever the file says.

Start With /init

In a project with no CLAUDE.md, type /init. Claude reads the codebase and writes a starting file with the build and test commands and conventions it can find. If a file already exists it proposes improvements instead of overwriting. Read what it wrote and cut the parts that are obvious from the code; the docs are blunt that directory layouts and dependency lists are things Claude can work out for itself.

/init            write a starting CLAUDE.md from the codebase
/memory          list and open every memory file, user and project
/context all     confirm which files loaded this session
/doctor prompt-audit   find stale or contradictory instructions

/init also reads other tools’ instruction files if they are there, including .cursorrules and .github/copilot-instructions.md, and folds the relevant parts in. And if the repo already has an AGENTS.md and no CLAUDE.md, Claude Code reads the AGENTS.md as its project instructions on its own, from version 2.1.277.

What to Write

The docs give a simple test for when something belongs in the file: Claude made the same mistake twice, a review caught something it should have known, or you typed the same correction you typed last session. Keep it to facts that matter every session, and write them so they can be checked:

# my-project

## Commands
- Build: `dotnet build .\src\MyProject.sln`
- Test: `Invoke-Pester .\tests` (PowerShell 7, run from the repo root)

## Rules
- Target PowerShell 7.4. No Windows PowerShell 5.1 syntax.
- Never edit `web.config` or anything under `.\infra\`.
- Write files with `.tmp` then rename, never in place.

## Layout
- API handlers live in `src/api/handlers/`
- Scripts that touch production are in `ops/` and need a `-WhatIf` switch

3 things from the docs decide whether the file works. Keep it under 200 lines; longer files cost context and the instructions get followed less. Be concrete: “use 2-space indentation” works, “format code properly” does not. Do not contradict yourself across the user file, the project file and any rules files, because when 2 instructions clash Claude may follow either one.

And the line to remember before you write “never run X” in here: CLAUDE.md is context, not enforcement. Claude reads it and tries to follow it. To actually block a command or a file, use a permission rule or a hook, which Claude Code enforces whatever the model decides. Claude Code Permissions: Allow and Deny Rules in settings.json covers the rules and Claude Code Hooks: Run a Check After Every Edit covers hooks.

Pull In Other Files With @

A CLAUDE.md can import another file with an @ path. The imported file is expanded into context next to the file that imports it, relative paths resolve from the importing file, and imports can nest 4 deep:

See @README for the project overview and @package.json for the npm scripts.

# Workflow
- git rules: @docs/git-instructions.md
- my own notes: @~/.claude/my-project-notes.md

Imports organise a long file but do not shrink it; imported files load with their parent. A path mentioned inside backticks is left alone, so `@README` is just text. And the first time a project file imports something outside the working directory, such as the home directory import above, Claude Code asks you to approve it once. One Windows trap from the docs: a path with spaces needs a backslash before each space, and a quoted path is not imported at all.

CLAUDE.local.md for Your Own Notes

Anything that is true for you and not for the team goes in CLAUDE.local.md at the project root: the URL of your staging box, the test database you use, the scratch folder on D:. It loads after CLAUDE.md and is treated the same way. Add it to .gitignore yourself; Claude Code does not do that for this file. If you work in several git worktrees of the same repo, a gitignored local file only exists in the worktree you created it in, so the docs suggest importing a file from your home directory instead.

Split a Long File Into .claude/rules/

When the project file grows past 200 lines, move topics into .claude/rules/, one Markdown file per subject. Files with no frontmatter load at launch with the same priority as the project CLAUDE.md. Files with a paths list only load when Claude touches a matching file, which is the one way to make a rule cost nothing until it is needed:

---
paths:
  - "**/*.ps1"
  - "**/*.psm1"
---

# PowerShell rules

- `Set-StrictMode -Version Latest` at the top of every script
- Parameters use `[CmdletBinding()]` and `[Parameter(Mandatory)]`
- No `Write-Host` in modules, use `Write-Verbose` or return objects

Patterns are relative to the folder that holds .claude. ~\.claude\rules\ works the same way for personal rules across every project. The docs note that a rules file reached through a symlink to a network path, a \\server\share UNC path, is not followed.

Auto Memory: What Claude Writes for Itself

Separately from anything you write, Claude keeps notes of its own. When you correct it, or confirm an approach, it can save that as a memory, and you see Saved 1 memory in the interface when it does. The notes live per project under your profile:

~/.claude/projects/<project>/memory/
    MEMORY.md            the index, first 200 lines (or 25 KB) load every session
    feedback_testing.md  one topic file per memory, read on demand

It is on by default in local sessions. /memory has the toggle and opens the files, which are plain Markdown you can edit or delete. To turn it off for one project, set "autoMemoryEnabled": false in that project’s .claude/settings.json. 2 details worth knowing: memory is shared across all worktrees of the same repo, and the memory folder is excluded from the transcript cleanup that deletes transcripts after 30 days, so it stays until something edits it.

If you ask Claude to remember something in chat, it goes to auto memory. If you want it in CLAUDE.md instead, say so: “add this to CLAUDE.md”.

Other Considerations

HTML comments are stripped. A <!-- note to the next person --> in CLAUDE.md never reaches Claude and costs no tokens, so leave maintainer notes that way. Comments inside code blocks are kept.

The project file survives /compact. After compaction Claude re-reads the project-root CLAUDE.md from disk. Anything you only said in the conversation does not survive, which is the other reason to write it down.

Monorepo noise. If a parent folder’s CLAUDE.md keeps loading for a project it has nothing to do with, the claudeMdExcludes setting takes a list of path globs to skip. Put it in .claude/settings.local.json so it stays on your machine.

Symlinking CLAUDE.md to AGENTS.md is a bad idea on Windows. Creating the link needs admin rights or Developer Mode, and Git checks a committed symlink out as a text file. Use a 1-line CLAUDE.md containing @AGENTS.md instead, which the docs recommend for exactly this case.

Frequently Asked Questions

Does Claude Code need CLAUDE.md to work?

No. It runs fine without one. The file exists so you stop repeating yourself; the first good candidate for it is the thing you have explained twice.

Where does Claude Code look for CLAUDE.md on Windows?

Your user file is C:\Users\<you>\.claude\CLAUDE.md. Project files are CLAUDE.md or .claude\CLAUDE.md in the folder you start from and every folder above it. /context all shows what loaded.

Claude is ignoring an instruction in the file. Why?

Check it loaded with /context all, then check the instruction is specific and not contradicted elsewhere. If it is a hard rule rather than guidance, it belongs in permissions.deny or a hook, which are enforced.

Can I keep notes in CLAUDE.md that only I see?

Yes, in CLAUDE.local.md at the project root, gitignored. It loads with the shared file and is treated the same way.

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

You write CLAUDE.md; Claude writes auto memory from your corrections. Both load every session. Rules and commands go in the file; preferences Claude picked up from you end up in memory, and you can edit both.


Related

If SQL Server is part of your day job, my current work lives over at sqldba.blog: production DBA scripts, an error library and the SSMS guide.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Looking for SQL Server?

This site started as a SQL Server blog in 2017 and the archive is still here. The new SQL Server writing, and all the scripts, moved to a site of their own.

sqldba.blogScripts, error library, wait types, SSMS
Browse by topic