# Project memory, done properly

Course: Claude Code: Pro Level — https://zero2vibecode.com/learn/claude-code-pro
Canonical URL: https://zero2vibecode.com/learn/claude-code-pro/5-1-project-memory
Verified against: Claude Code v2.1.306 (2026-10-22)
Last updated: 2026-10-22

A long CLAUDE.md loads in full into every session. An @path import splits it into files, and a rule in .claude/rules with a paths field loads only when Claude reads a matching file. /context all and the InstructionsLoaded hook show what actually loaded.

You already know how to write a CLAUDE.md. But in a big project it grows fast: commands, conventions, rules for the API, for tests, for markup. And **every line** loads into every session — even when you are editing the README.

Two tools help you keep it in order: imports and rules.

## Imports: @path

A line with `@` and a path loads another file along with CLAUDE.md:

```markdown
# The linkcheck project

@AGENTS.md

- npm commands: @package.json
- Architecture: @docs/architecture.md
```

An import organises memory, but it **does not save context**: the imported file always loads, together with the file that imports it.

Two small things that trip people up:

- the path runs up to the first space — do not put a full stop right after it;
- the path is relative to the file that contains the import, not to the folder where you started Claude.

Importing a file outside the working folder, such as `@~/notes.md`, asks for your approval the first time it is used in a project. Decline, and it stays off.

## AGENTS.md and CLAUDE.md

Many projects already keep an AGENTS.md for other agents. Claude Code reads it on its own — but only while there is no `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md` in your folder or above it. **As soon as one of those exists, Claude reads the CLAUDE.md files instead, and AGENTS.md is skipped.**

So the day you add a CLAUDE.md for Claude-specific notes, make `@AGENTS.md` its first line, and the shared rules stay in one place. The other way is `/config` → **Project instructions** → `claude-md-and-agents-md`, which reads both. Note that a personal `CLAUDE.local.md` counts too: creating one in a project that relies on AGENTS.md switches AGENTS.md off for you.

## Rules: .claude/rules

A rule is a Markdown file in the `.claude/rules/` folder. Without a `paths` header, it loads at startup, like an ordinary CLAUDE.md. With the header — only when Claude works with a matching file:

```markdown
---
paths:
  - "test/**/*.js"
---

# Tests

- Use node:test and node:assert/strict; do not add other libraries.
```

Patterns are relative **to the project root**. `*` does not cross a folder boundary, `**/` goes through any number of folders, and braces such as `src/**/*.{ts,tsx}` work too.

The main subtlety: a rule with `paths` applies when Claude **reads, writes or edits a matching file** — with the Read, Write or Edit tool, or with a plain `cat`, `head` or `tail` on that one file in the shell. A command that only lists files, pipes the file into something else or reads several at once does not count. So when you check, ask exactly that: "read test/config.test.js".

## What loads, and when

| Instruction file | When it loads |
|---|---|
| CLAUDE.md at the root, and a rule without `paths` | at session start |
| AGENTS.md, when there is no CLAUDE.md | at session start |
| a file pulled in by an `@path` import | along with the file that imports it |
| a rule with `paths` | when Claude reads, writes or edits a matching file |
| auto memory — notes Claude keeps itself | at startup: the first 200 lines or 25 KB of the MEMORY.md index |

For personal instructions for this project there is `CLAUDE.local.md`. You create it by hand and do not commit it — that is exactly what the lesson on a shared team kit is about.

## Why Claude does not listen

First find out **whether the file loaded**. Only if it did, work on the wording: make it specific and remove rules that contradict it.

The first way is `/context all`. Plain `/context` can collapse the list; `all` expands it. The **Memory files** section shows the files loaded at startup. Rules with `paths` appear there only after a matching file is read.

The second way is the `InstructionsLoaded` hook. It fires every time a CLAUDE.md, a rule or an import loads. Put it in a separate file, for example `.practice/loaded-hook.json`:

```json
{
  "hooks": {
    "InstructionsLoaded": [
      { "hooks": [ { "type": "command", "command": "jq -r '[.load_reason, .file_path] | @tsv' >> \"$CLAUDE_PROJECT_DIR/.practice/loaded.txt\"" } ] }
    ]
  }
}
```

And start a session with it: `claude --settings .practice/loaded-hook.json`. One gap: the hook does not fire for an AGENTS.md that Claude reads directly, only for one that a CLAUDE.md imports. Every line in the log has a reason:

- `session_start` — loaded at startup, like CLAUDE.md;
- `include` — pulled in by an import;
- `path_glob_match` — a rule applied through its pattern.

If there is no `path_glob_match` line for your rule, compare the pattern with the real files. Git helps:

```bash
git ls-files ':(glob)src/api/**/*.js'
```

Empty output means the pattern matches no file. The classic: a rule for `source/**` in a project where the code lives in `src/`.

## Editing mid-session

Claude Code reads the project's CLAUDE.md **once, at startup**. Edit the file, and the change reaches the session after `/clear`, `/compact` or a restart.

And to find instructions that contradict each other or point to deleted files, run `/doctor prompt-audit`. It suggests edits and changes nothing until you ask.

**Careful.** 
CLAUDE.md is context, not a setting Claude is obliged to obey. A rule that must never be broken belongs in a hook, not a line in memory. And do not bloat the file: every line loads into every session, so keep CLAUDE.md short and move long procedures for rare tasks into skills.

**Practice in the terminal.** Create a CLAUDE.md that imports @AGENTS.md. Then look at ls src, and at cat drafts/api-v1.md and cat drafts/api-v2.md — pick the draft whose pattern matches the real folder and copy it to .claude/rules/api.md.
