cool-solution — dev.blog
Technologies

CLAUDE.md: give Claude Code a persistent project memory

The most tedious part of working with a terminal assistant is repeating myself: every session restarts from an empty context and, unless I tell it, Claude Code doesn't know I use yarn not npm, where the handlers live or which command runs the tests. The fix is a plain text file, CLAUDE.md, which the assistant reads at the start of every session and keeps as context for the whole job. It's the place where I write once what I'd otherwise rewrite every day. The rule I keep repeating is a single one: in it goes what I'd re-explain to a new teammate. In this article I look at what it is exactly, where I put the files in the hierarchy, how I write instructions that get followed and why, in the end, it stays context and not an absolute constraint.

$ claude "refactor user.service to async/await" reading src/services/user.service.ts... proposing 12 edits across 3 files ✓ tests still pass (47 / 47) applying patch...AI PAIRsupervised

🧠 What CLAUDE.md is and why I write it

A CLAUDE.md is a markdown file with persistent instructions for a project. I write it in plain text and Claude Code reads it at the start of every session, before I even type the first prompt. I don't rebuild the context by hand every time: build commands, conventions, directory layout and rules like «always do it this way» sit there and apply right away.

The question I use to decide what to put in it is always the same: would I re-explain this to a new teammate? If yes, it goes in the file. It ends up there when Claude makes the same mistake a second time, when a code review catches something it should have known about this repo, or when I notice I'm typing the same correction as last session.

What I don't put in it are long procedures or rules that only matter for one corner of the code: for those I use a skill or a scoped rule, which loads only when needed. The CLAUDE.md stays the place for the facts I want in every session, not the project manual.

A lean project CLAUDE.md
# Project — rules for Claude Code

## Commands
- Build: `yarn build`   # never npm
- Test:  `yarn test`

## Conventions
- 2-space indentation
- API handlers live in `src/api/handlers/`
- Early return, no else after a return

Official docs · Claude Code memory

A few concrete facts: commands, conventions and layout. Not a manual.

🗂️ Where the files live: the memory hierarchy

CLAUDE.md files can live in several places, each with a different scope. Claude Code loads them from the broadest to the most specific, so a project instruction lands after a personal one and, on the same topic, the one closest to where I launch the command wins.

The four levels, from broadest to narrowest: the managed policy owned by the organization, my user preferences in ~/.claude/CLAUDE.md, the project one in ./CLAUDE.md (or ./.claude/CLAUDE.md) shared with the team via Git, and the local one in ./CLAUDE.local.md, which I keep in .gitignore for my own things in this repo.

A detail I exploit in monorepos: Claude walks up the directory tree from where I am and concatenates every CLAUDE.md it finds, while the ones in subdirectories load on demand, only when it touches files in there. Instructions don't override each other: they add up.

The order in which I load instructions
  1. 01
    Managed policyOrganization-wide instructions, one per machine, not excludable by the individual.
  2. 02
    User~/.claude/CLAUDE.md: my preferences, valid across all projects.
  3. 03
    Project./CLAUDE.md or ./.claude/CLAUDE.md: shared with the team via version control.
  4. 04
    Local./CLAUDE.local.md: private preferences for this repo, kept in .gitignore.

From the broadest scope to the most specific: the last one read weighs more.

✍️ Writing instructions Claude actually follows

Since the CLAUDE.md enters the context at every launch, how I write the instructions matters as much as what I write. Three things make the difference. The first is size: I aim to stay under 200 lines, because a long file consumes context and, paradoxically, gets followed worse.

The second is specificity. «Format the code properly» isn't verifiable; «Use 2-space indentation» is. I always prefer the instruction a reviewer could tick off: «Run yarn test before committing» instead of «test your changes». The third is consistency: if two rules contradict each other, Claude picks one arbitrarily, so every now and then I re-read and prune the old or conflicting ones.

When the file grows too big, instead of bloating it I move the per-area instructions into .claude/rules/, one file per topic attached to precise paths via a glob in the frontmatter. That way the rules about components load only when I touch the components, and the rest doesn't take up context in every session.

Vague or verifiable instructions

Vague — Claude guesses

  • Format the code properly
  • Keep the files tidy
  • Test your changes
  • Handle errors the right way

Concrete — Claude can tick it off

  • Use 2-space indentation
  • API handlers live in src/api/handlers/
  • Run yarn test before committing
  • Use the project's standard error format

The same rule, written two ways: on the right Claude can check itself.

🔗 Imports with @path and the bridge to AGENTS.md

A CLAUDE.md can pull in other files with the @path/to/file syntax: at launch they're expanded into context alongside the file that references them. I use them to avoid duplicating what already lives elsewhere — a @README for the overview, a @package.json for the scripts — with relative or absolute paths and a maximum depth of four hops.

Two things I've noted down. If I want to mention a path without importing it, I wrap it in backticks: outside backticks @README imports the file, inside it stays text. And the first time a project file imports something outside the working directory — say from my home — Claude Code asks for confirmation with a dialog: it's the protection from imports someone else might commit to a shared repo.

The bridge to AGENTS.md is handy too: Claude Code reads CLAUDE.md, not AGENTS.md, so if a repo already uses the latter I create a CLAUDE.md that imports it with @AGENTS.md and add the Claude-specific instructions below. One source of truth, read by every tool.

Imports and reusing AGENTS.md
See @README for the overview and @package.json for the scripts.

# Common rules (shared with other agents)
@AGENTS.md

## Claude Code only
- Use plan mode for changes under `src/billing/`
- Mention `@docs/api.md` in backticks: stays text, not imported
A @path imports; in backticks it stays text. @AGENTS.md reuses the common rules.

🩺 /init, /memory and /context: generate and verify

I never start from a blank page: /init analyzes the codebase and generates a starting CLAUDE.md with build commands, test instructions and conventions it discovers on its own. If the file already exists, instead of overwriting it, it suggests improvements. From there I refine by hand with the things Claude couldn't guess from the code.

To understand what's really active I use two distinct commands. /memory lists the memory files — CLAUDE.md, CLAUDE.local.md and the others — across the various levels and opens them in the editor. /context instead shows me what was actually loaded in this session, under the Memory files entry: if a file isn't there, Claude isn't seeing it, and it's the first thing I check when a rule seems ignored.

Then there's auto memory, complementary: on by default, it's Claude that takes notes on its own — commands, debugging insights, preferences — under ~/.claude/projects/, while the CLAUDE.md stays the part I write. I inspect and prune it from the same /memory.

Generate and verify the memory
# generate a starting CLAUDE.md from the codebase
/init

# lists and opens the memory files (all levels)
/memory

# shows what is ACTUALLY loaded in this session
/context      # -> "Memory files" entry
I generate with /init, I check what's loaded with /context.

⚠️ It's context, not a constraint: the limit to know

The most costly misunderstanding is treating the CLAUDE.md as configuration that enforces behavior. It isn't: its instructions arrive as a message after the system prompt, Claude reads them and tries to follow them, but there's no guarantee of strict compliance, especially if they're vague or in conflict. The more specific and concise they are, the more they're respected.

When a rule must fire at a precise point — before every commit, after every edit — I don't leave it to the CLAUDE.md: I write it as a hook, which runs a shell command at a lifecycle event and applies regardless of what Claude decides. The CLAUDE.md guides, the hook enforces: two different tools, kept separate.

One last thing: the project-root CLAUDE.md survives */compact* — after compaction Claude re-reads it from disk and re-injects it. The ones in subdirectories don't: they reload the next time I touch a file in there. That's why the always-on rules live in the root file.

CLAUDE.md or hook: guide or enforce

CLAUDE.md — guides

  • Context read at session start
  • Conventions, structure, preferences
  • Followed, but with no strict guarantee
  • I update it when I repeat a correction

Hook — enforces

  • A shell command at a precise event
  • Fires before the commit or after an edit
  • Applies regardless of what Claude decides
  • For the constraints that can't be skipped

Two different tools: one steers the behavior, the other forces it.

✅ The flow I follow for a memory that stays useful

Put together, the CLAUDE.md isn't a document I write once and forget, but a living file I prune as the project changes. The mini-flow I follow, in order:

  • Start from */init* for a baseline, then cut everything Claude can infer on its own from the code.
  • Write verifiable instructions, under 200 lines, and move into .claude/rules/ what only matters for one area of the code.
  • Import instead of copying: an @AGENTS.md or a @docs/… keeps one source of truth, with no duplicates that go stale.
  • Verify with */context* when a rule seems ignored: if it isn't among the Memory files, the problem is where I put it.
  • Promote to a hook what can't be skipped, and leave to the CLAUDE.md what only needs to guide.

Frequently asked questions about CLAUDE.md Claude Code

Where do I put CLAUDE.md, at project or user level?

In the project (./CLAUDE.md or ./.claude/CLAUDE.md) goes what anyone working on the repo needs — commands, conventions, architecture — and it's shared with the team via Git. In the user one (~/.claude/CLAUDE.md) my own preferences, valid across all projects. The private things for a single repo go instead in ./CLAUDE.local.md, which I keep in .gitignore.

Why doesn't Claude follow a rule in my CLAUDE.md?

First thing: I run /context and check the file is under Memory files; if it isn't, Claude isn't loading it and it's a placement problem. If it is, usually the rule is too vague or conflicts with another, and I make it more concrete. If it must fire no matter what, I turn it into a hook.

What's the difference between CLAUDE.md and auto memory?

The CLAUDE.md I write: it's instructions and rules. The auto memory Claude writes, saving learnings and useful patterns across sessions on its own under ~/.claude/projects/. They're complementary and both load at launch; I govern the first, the second I inspect and prune from /memory.

Does CLAUDE.md survive /compact?

The project-root one does: after a /compact Claude re-reads it from disk and re-injects it into the session. The CLAUDE.md files in subdirectories aren't re-injected automatically, they reload the next time Claude opens a file in that folder. That's why I keep the always-on rules in the root file.

Let's talk

If this topic is relevant to you, write to me: comparing notes on code and AI is always time well spent.

All articles

More articles from the blog

claude-code-pretooluse-hook-proteggere-env-segreti-2026.mdJuly 24, 2026claude-cowork-record-a-skill-2026.mdJuly 24, 2026claude-code-custom-skills-tutorial-2026.mdJuly 21, 2026