Writing a CLAUDE.md the Model Actually Follows
Most project instruction files are wishlists. The ones that work are short, specific, and say what not to do.
A project instructions file — CLAUDE.md, a rules file, whatever your tool calls it — is the highest-leverage ten minutes in an AI-assisted workflow. Almost everyone either skips it or writes it badly, and badly is closer to skipping than people think.
Why most of them fail
The typical file reads like this:
# Project Guidelines
- Write clean, maintainable code
- Follow best practices
- Use TypeScript
- Make sure the code is well-tested
- Consider performance and accessibilityEvery line is true and none of it is actionable. "Clean code" isn't a decision procedure. "Use TypeScript" is already evident from the file extensions. Nothing here changes a single output, because none of it distinguishes your project from any other project.
If a line would be true of every project on GitHub, it's taking up space without doing work.
What belongs in it
1. Decisions that aren't inferable from the code
- Static export only. No API routes, no server actions, no database.
If a feature seems to need a backend, propose a static alternative instead.
- All dates are stored UTC, displayed in the user's local timezone.
Never construct a Date without an explicit timezone.
- Money is stored in paise as an integer. Never use a float for currency.These are the ones that pay. They're constraints a reader can't derive by looking at three files, and violating them produces subtly broken code that passes review.
2. Negative constraints — say what not to do
- No new dependencies without asking. We have date-fns; don't add dayjs.
- No Redux, no CSS-in-JS, no UI component library. Build the components.
- Never use localStorage. Everything is React state.
- Don't write tests unless asked.Negative constraints work disproportionately well. Positive instructions compete with everything else the model might do; a prohibition removes a branch entirely. Most of my file is prohibitions.
3. Where things go
- Feature code lives in src/features/<feature>/. Features never import
from each other — promote shared code to src/shared/.
- Domain-agnostic UI primitives only in src/shared/ui/.
A StatusBadge that knows about invoices belongs in the invoices feature.Without this you get correct code in the wrong place, which is the most annoying kind of review comment to write repeatedly.
4. The commands, exactly
- Dev: npm run dev (port 5173)
- Test one file: npm test -- path/to/file.test.ts
- Lint + typecheck before declaring done: npm run lint && npm run typecheckSmall thing, saves a wasted turn every session where it guesses yarn test and gets an error.
What to leave out
- Anything the code already says. Your dependencies are in
package.json. Your structure is visible. Don't restate it. - Generic advice. "Write readable code." It's already trying.
- A full architecture essay. Long files get skimmed — by models and by humans. Aim for under 100 lines.
- Anything that will go stale. A list of every route will be wrong in a month, and a confidently wrong instruction is worse than none.
Write it from your review comments
The best source material is the feedback you've actually given. Look at your last twenty code review comments. The ones you've written more than once are exactly what belongs in the file — that repetition is the signal that the convention isn't discoverable from the code.
This is also why the file should be committed and shared. It's not AI configuration; it's your team's conventions written down for the first time. I've had the file be more useful to new human hires than to any model — nobody had ever written down why we don't use localStorage, so nobody could follow the rule.
Iterate on it
When the output is wrong in a way you've corrected before, that's a missing line. Add it. My files grow by one or two lines a week for the first month, then stabilise — which is roughly how long it takes to enumerate the things you knew but had never said out loud.