Up to date as of April 2026.
Every CLAUDE.md I’ve written has gone through the same arc: start comprehensive, watch Claude ignore half of it, strip it down to what actually matters, feel annoyed about the time spent on the rest. The one on this project is 113 lines and I still think it’s too long. My global one is 29, and that one I’d defend.
High-priority context, not config
For the first few months I treated CLAUDE.md like a notes file — everything I knew about the project, dumped in, on the theory that more context couldn’t hurt. It can. Too vague, too long, too much generic advice, and the model ignores half of it.
What it actually is: a Markdown file Claude loads at the start of every session. Not a config file, not a hard lock — just high-priority context. Claude reads it and tries to follow it, but only if the instructions are concrete enough to survive alongside everything else in the session.
Shorter and sharper beats comprehensive. If I care about a rule every session, it goes here — anything softer than that belongs in auto memory.
Where to place it
| File | Scope | Who sees it |
|---|---|---|
./CLAUDE.md or ./.claude/CLAUDE.md |
Project | Team via git |
./CLAUDE.local.md |
Project (local) | Only you (gitignored) |
~/.claude/CLAUDE.md |
All projects | Only you |
CLAUDE.md loads up the directory tree from the current folder. If you launch Claude in foo/bar/ — both foo/bar/CLAUDE.md and foo/CLAUDE.md will be loaded.
That loading behavior is why small local files beat one giant universal file.
A working skeleton
The best CLAUDE.md files are boring in a good way: practical, specific, easy to scan.
# Project Overview
Short description of what this project is and why.
One or two sentences.
# Tech Stack
- Runtime: Bun (not Node.js)
- ORM: Drizzle
- Linter: Biome (not ESLint/Prettier)
- Testing: Vitest
# Project Structure
src/
├── api/ # API handlers
├── db/ # database schema and migrations
└── utils/ # shared utilities
# Commands
- `bun run dev` — start dev server
- `bun test` — tests
- `bun run lint` — linting
- `bun run build` — production build
# Coding Conventions
- 2-space indentation
- Name files kebab-case
- Prefer named exports over default
- Strict typing required, no `any`
# Workflow
- Create a feature branch before making changes
- Run tests before committing
- Don't commit directly to main
- Commit messages in English
That’s the shape, not a template to copy. The only parts I’d call mandatory are Commands and the non-obvious stack facts — those are what Claude gets wrong without help. The rest earns its place or gets cut.
Writing principles
Be specific — actually specific
❌ "Format code properly"
✅ "Use 2-space indentation, no semicolons"
❌ "Test your changes"
✅ "Run `bun test` before every commit"
❌ "Keep files organized"
✅ "API handlers live in src/api/handlers/"
❌ "Write good commit messages"
✅ "Commit messages: type(scope): description — e.g. feat(auth): add JWT refresh"
Under 200 lines
Longer means two problems at once: worse adherence and more context spent on the file itself. If it keeps growing, split it into .claude/rules/ or use imports.
No contradictions
If two rules conflict, Claude picks one. It won’t tell you which. Review the file periodically.
Use headings and bullets
Claude scans structure the same way a person does. Walls of text get treated like walls of text.
What to write, what to skip
This is the part I got wrong the longest. I kept writing down everything I knew about the project, when what Claude needs at session start is a much smaller subset.
Write:
- Build, test, and lint commands
- Non-obvious decisions (“we use Bun, not Node”)
- Project structure — what lives where
- Project-specific coding conventions
- Architectural decisions and why they were made
- Things Claude can’t discover from the code alone
Skip:
- What Claude will figure out by reading the code
- General best practices (Claude already knows them)
- Details that change frequently
- Personal preferences (those go in
~/.claude/CLAUDE.md)
The hardest thing to stop including: general best practices you’d write in any onboarding doc. Claude doesn’t need to be told “write clean code” or “handle errors properly”. It needs to know this project uses Bun, not Node — because that’s the thing it actually gets wrong without context.
Imports for modularity
Use the @path syntax to avoid duplicating content:
# Git Workflow
@docs/git-workflow.md
# Personal preferences (won't end up in git)
@~/.claude/my-preferences.md
Imports are expanded and loaded into context at startup, nesting up to 5 levels. The trap is that an import pulls the entire file — @package.json costs you every dependency string every session, to save typing four commands.
I don’t use imports here. One file at 113 lines is below the point where splitting starts to pay for itself.
.claude/rules/ — modular rules
For larger projects instead of one monolithic file:
.claude/rules/
├── testing.md # testing rules
├── api-design.md # API rules
├── security.md # security requirements
└── frontend.md # frontend rules
Path-specific rules
A paths: block in the frontmatter means the rule loads only when Claude touches matching files, so it costs nothing the rest of the time:
---
paths:
- "src/api/**/*.ts"
---
# API Rules
- Input validation is mandatory
- Standard error format: { error, code, message }
- Document all endpoints with JSDoc
Globs are the usual ones — **/*.ts, src/**/*, src/**/*.{ts,tsx}.
I’ve never needed this on a project this size. It starts earning its keep when parts of a repo have genuinely different rules and you’re tired of Claude applying frontend conventions to database migrations.
Global CLAUDE.md for personal preferences
~/.claude/CLAUDE.md — loaded in all projects. A good place for personal preferences you don’t need to share with the team:
# My Working Style
- Explain what you're about to do before doing it
- Prefer small atomic commits
- If a task is ambiguous — ask before starting
- Show full stack trace on errors
# Code Preferences
- Prefer functional style over OOP
- Explicit types everywhere, no any
- Comments only for non-obvious logic
# Workflow
- Always run the linter after changes
- Commit messages in English using conventional commits format
Mine is 29 lines and most of it isn’t preferences at all — it’s environment facts. macOS on a BSD userland, so sed -i needs an argument. Homebrew lives in /opt/homebrew. Use fd and rg, not find and grep. Those get followed every time, because they’re things Claude gets wrong on this machine unless told. The style preferences underneath them land maybe half the time.
Auto memory
Auto memory is the supporting layer: Claude writes its own notes while working — build commands, debugging patterns, preferences it inferred. It works when it stays small and boring. Think “index of things worth remembering”, not “journal of everything that happened”.
On startup Claude Code loads MEMORY.md up to the first ~200 lines (or ~25KB), which is the real argument for keeping it an index rather than a dumping ground.
I keep it mostly off for shared projects and on for personal ones. For solo work the notes are genuinely useful — build quirks, things you mentioned once and would rather not repeat. In shared setups it gets weird: memory files accumulate noise from different contexts and you end up with a “memory” that’s half-outdated.
Stored in:
~/.claude/projects/<project>/memory/
├── MEMORY.md # index, loaded every session
├── debugging.md # debugging patterns
└── ...
To view, edit, or switch it off:
/memory
Tell Claude “remember that…” → saves to auto memory. Tell Claude “add this to CLAUDE.md” → writes to the file.
Worth knowing before you rely on it: memory is local and doesn’t sync between machines, and all worktrees of the same repo share one memory folder.
Generating the first version
If you don’t have a CLAUDE.md yet, don’t overthink version one.
/init # Claude will analyze the codebase and generate CLAUDE.md
After /init, edit aggressively: cut the fluff, keep the commands, add the non-obvious rules Claude could not infer from the repo. The generated file is a starting point, not a destination — treat it like a draft from a new teammate who just read the README. Useful but unfiltered.
After /compact
This is the practical test for whether a rule belongs in CLAUDE.md.
CLAUDE.md survives compaction because Claude re-reads it from disk. If an instruction “disappeared” after /compact, that usually means it lived only in chat and never became project memory.
Debugging
If Claude isn’t following your CLAUDE.md:
/memory # check that the file is actually loaded
If the file isn’t in the list, Claude can’t see it. Check the location before rewriting the content.
For detailed logging of which files load and when — use the InstructionsLoaded hook in settings.json.
AGENTS.md compatibility
If your repo already has an AGENTS.md for other AI tools — import it into CLAUDE.md:
@AGENTS.md
## Claude-specific
- Use plan mode for changes in src/billing/