4. Project Memory (CLAUDE.md)¶
Video map
Section 2 — Creating Project Memory
The problem: every session starts blank¶
When you start a new Claude Code session it knows nothing about your project: not the folder layout, not your libraries, not how you like code written, not which command runs the tests. Either you re-explain every time or Claude re-discovers it by reading files — which costs time and money.
The fix is a plain-text file Claude reads at the start of every session: CLAUDE.md.
What goes in it¶
Think of it as the onboarding note you would give a new teammate on day one. Good entries are things that are true across tasks and not obvious from the code:
| Section | Example |
|---|---|
| Commands | npm test runs the suite; run it after every change |
| Layout | validate.js = rules, store.js = data, app.js = HTTP only |
| Conventions | Status codes: 201 create, 204 delete, 422 validation |
| Rules and "don'ts" | Do not add dependencies; never edit data.json |
| Gotchas | API tests start the server on port 0 |
Here is the real file from the Shelf project — every line earns its place:
# Shelf — project memory for Claude
Shelf is a small reading-list REST API with a tiny web page. Node 20+, ES modules, **zero runtime dependencies**.
## Commands
- `npm test` — runs the whole suite (node:test). Run it after every change.
- `npm start` — API on :3000; configuration comes from environment variables (see `.env.example`)
- `npm run mcp` — stdio MCP server exposing the store as tools
- `node e2e/smoke.e2e.mjs` — browser end-to-end test (needs Playwright; not part of `npm test`)
## Layout
- `src/config.js` — the ONLY place that reads `process.env`; validates and fails fast
- `src/validate.js` — all input validation; return `{ ok, value | errors }`, never throw. Shared with the browser
- `src/store.js` — the only code that touches data; no HTTP knowledge; delete is a soft delete
- `src/auth.js` — API keys -> `{ owner, role }`; roles are `member` and `admin`
- `src/security.js` — escapeHtml, card rendering, CSP headers, RateLimiter
- `src/jobs.js` — in-process job queue (retry with backoff, dead-letter list)
- `src/ai.js` — AI helpers; the model call is injected as a function so tests never hit the network
- `src/mailer.js` — outbound mail interface, inbound webhook signature + parsing
- `src/app.js` — routing + HTTP mapping only; no business rules here
- `web/` — static client; `ui.js` is pure functions (unit-testable), `app.js` touches the DOM
- `test/` — one test file per area; API tests start the server on port 0
- `project/` — scope, requirements, MVP, tech-stack decisions, plan
## Conventions
- Status codes: 201 create, 204 delete, 400 malformed JSON, 401 no/unknown key, 403 wrong role, 404 missing OR not yours, 422 validation, 429 rate limited, 501 AI not configured
- Never return 403 for another user's book: return 404 so ids do not leak
- Validation errors are arrays of short human-readable strings
- Every dynamic value that becomes HTML goes through `escapeHtml`
- Business rules live in `validate.js` or `store.js`, never in `app.js`
- New behaviour needs a test first; do not change existing tests to make them pass
- AI output is untrusted: validate it and fall back to a safe default; never fail the user's request because the AI failed
## Subagents
- `reviewer` — read-only review of the current diff against these conventions
- `security-auditor` — fresh-eyes security audit (read-only)
- `e2e-writer` — writes and runs browser tests; use it for any E2E work
## Do not
- Add dependencies without asking
- Edit `data.json` (runtime data, git-ignored)
- Commit `.env` or anything that looks like a real key
Create one with /init¶
Run /init and Claude explores your project and writes a first draft. Two important truths about it:
- The output differs every time (models are non-deterministic). Do not treat the draft as sacred.
- You must review and trim it. A draft written by an agent often includes obvious things and misses your real rules.
Then commit the file so teammates (and CI) share it.
Let Claude update it for you
When Claude makes the same mistake twice, say: "Add a rule to CLAUDE.md so you do not do that again." Teaching the file is faster than repeating yourself in every prompt.
Keep it small — it is loaded every time¶
CLAUDE.md is sent with every request. That makes it powerful and expensive in the same breath:
- Short, specific instructions beat long essays — models follow a handful of clear rules better than fifty vague ones.
- Delete outdated lines. A stale rule is worse than no rule.
- Move task-specific playbooks into skills (chapter 7), which load only when needed.
Where memory files can live
- Project root
./CLAUDE.md— shared with the team, checked into git. The most common choice. - Your home folder
~/.claude/CLAUDE.md— your personal preferences across all projects. - Sub-folders — a
CLAUDE.mdinsideserver/applies when Claude works in that part of the code.
The exact lookup rules can evolve; check the official docs. You can also reference other files from a memory file.
Memory vs. instruction vs. enforcement¶
A common confusion: CLAUDE.md is guidance, not a lock. Claude usually follows it, but nothing forces it to.
| Need | Use |
|---|---|
| "Please prefer X" | CLAUDE.md |
| "Always run tests after editing" (must happen) | A hook (chapter 9) |
| "Never read or run Y" | A permission deny rule (chapter 9) |
Worked example — memory in action¶
- Delete
CLAUDE.mdfrom a copy of Shelf and start Claude. Ask: "Add aGET /books/:id/existsroute." Notice what it guesses (maybe it edits tests, maybe it adds a dependency, maybe it puts logic in the wrong file). - Restore
CLAUDE.mdand ask the same thing in a fresh session. Compare: it should write a test first, keep rules out ofapp.js, and runnpm test. - Note which lines of
CLAUDE.mdchanged its behaviour. Those are the high-value lines. Keep those; trim the rest.
This experiment is the best way to feel why memory matters.
Commit with Claude¶
Once a change is reviewed, you can also ask Claude to make the commit:
commit
Claude inspects the diff, writes a descriptive message, and runs git commit. This is slower than typing
the git commands yourself because it must work out what changed (it uses tokens), but the messages are
consistent and well written. A commit after every reviewed step is your safety net (chapter 8).
Common mistakes¶
- Pasting the entire architecture into it. It balloons the context for every prompt.
- Writing wishes instead of rules ("write good code"). Rules must be checkable: "Run
npm testafter every change." - Never pruning. Review the file monthly; remove what is outdated.
- Assuming it is enforced. It is a strong hint. Use hooks/permissions for must-haves.
- Committing secrets into it. The file is shared; never put keys or passwords in it.
Check yourself¶
- I can explain why memory exists and what it costs.
- I ran
/initand then edited the result down to what is true and non-obvious. - I committed
CLAUDE.md. - I know which rules belong in
CLAUDE.md, which need a hook, and which need a deny rule.