CLAUDE.md That Actually Works: Context Rules

You opened a Claude Code session this morning, asked it to add a feature, and watched it reinvent a utility you already have, import a library you banned six months ago, and write tests in a framework you migrated off of last quarter. Your CLAUDE.md is either three lines telling it to "write clean code," or it's a 2,000-line manifesto the model is skimming like a teenager skims a EULA. Either way, the same mistakes keep showing up across sessions.
This is fixable. Not with a longer file — with a sharper one.
Why most CLAUDE.md files fail
The two failure modes are predictable.
The thin file. Three bullets: "use TypeScript, write tests, follow existing patterns." This tells the model nothing it can't infer from the repo in 30 seconds. It produces zero behavior change. You're paying the context tokens for decoration.
The kitchen-sink file. Every convention, every gotcha, every team norm from the last four years, including the section about the deprecated legacy_v2/ folder that nobody touches. The model doesn't ignore this — but it weighs everything equally. Important rules ("never write to production DB from migrations") sit next to trivia ("we prefer 2-space indent"). Signal-to-noise collapses.
The third, subtler failure: instructions that describe the codebase instead of constraining the model. "Our auth lives in services/auth/" is documentation. "When touching auth, read services/auth/README.md first and never bypass requireSession()" is an instruction. Claude Code can grep your repo. It cannot infer your intent.
A working CLAUDE.md does three things:
- Tells the model what to read before acting in specific areas.
- Tells the model what never to do (hard constraints).
- Tells the model how to verify its work before declaring done.
Everything else is filler.
The structure that holds up across sessions
Here's the skeleton I use for repos above ~50k lines. It maps cleanly to how the model actually processes instructions.
# Project: <name>
## What this repo is
One paragraph. What it does, who uses it, what stack.
## Hard rules (never violate)
- Bulleted, imperative, short.
- Each rule is a "do not" or a "always."
## Before you start work
- Commands to run for context (e.g., `pnpm typecheck`, `make schema`)
- Files to read for specific areas (mapped below)
## Area map
| If you're touching... | Read first | Owner notes |
|---|---|---|
| `services/billing/` | `services/billing/CONTRIBUTING.md` | Stripe webhooks are idempotent — don't add retries |
| `apps/web/` | `apps/web/README.md` | App Router only, no Pages Router |
## How to verify your work
- Exact commands, in order
- What "done" looks like (tests pass + typecheck + lint clean)
## Known traps
- Specific bugs the model has made before. Name them.
The "Hard rules" and "Known traps" sections are where you encode the lessons from previous sessions where the model went off the rails. Treat CLAUDE.md like a postmortem log, not a style guide.
Hard rules: write them like a linter
The model responds best to hard rules when they read like a linter would — declarative, scoped, with a clear failure condition. Compare:
Weak: "Be careful with database migrations."
Strong: "Never edit a migration file once it's been merged to main. Create a new migration instead. If you're unsure whether a migration is merged, run git log --all <file>."
The strong version is enforceable — by you, in review, and by the model itself when it second-guesses an action. Every hard rule should pass this test: could a junior engineer execute it without asking a follow-up question?
A practical set for a typical SaaS repo:
## Hard rules
- Never commit secrets. If you find one, stop and tell the user.
- Never run `pnpm install <new-package>` without asking. Use what's in `package.json`.
- Never edit files in `generated/`, `*.pb.go`, or `prisma/migrations/`. They are output, not source.
- Never bypass `requireSession()` in `services/auth/`. If you need an unauthenticated route, add it to `PUBLIC_ROUTES` in `auth/config.ts`.
- Never write tests using `jest`. We use `vitest`. There are leftover `jest` references in `legacy/` — ignore them.
- Never assume environment variables exist. Read `env.schema.ts` and update it if you add one.
- When unsure about a destructive action (delete, force-push, schema change), stop and ask.
Each line is enforceable and non-negotiable. There's no "prefer" or "try to." Hard rules are hard.
Soft rules belong in the area map, not at the top
Soft rules — coding style, naming conventions, preferred libraries — are context-dependent. Most repos have multiple subsystems with different conventions (the API written three years ago vs. the new edge worker). Putting "use functional style" at the top of CLAUDE.md is wrong for half the repo.
Move soft rules into per-area files. Reference them in the area map.
## Area map
| Path | Read first |
|---|---|
| `apps/web/` | `apps/web/CLAUDE.md` |
| `services/api/` | `services/api/CLAUDE.md` |
| `packages/ui/` | `packages/ui/CLAUDE.md` |
| `infra/` | `infra/CLAUDE.md` — and ping #infra before opening a PR |
Then in apps/web/CLAUDE.md:
# apps/web conventions
- React Server Components by default. Use `'use client'` only when you need state, effects, or browser APIs.
- Data fetching: server components call services directly. Client components use `swr`.
- Styling: Tailwind only. No CSS modules, no styled-components.
- Forms: `react-hook-form` + `zod`. See `apps/web/components/forms/Example.tsx`.
Claude Code will load the relevant file when working in that directory. You get specificity without bloating the root. This also scales — adding a new service means adding one row to the area map and one local file.
Tell the model what to read, not what to know
The biggest lever in a CLAUDE.md is explicit read instructions. The model is good at reading files. It's bad at guessing which files matter.
Compare two approaches to documenting your billing system:
Approach A — describe it inline:
## Billing
We use Stripe. Subscriptions are managed in `services/billing/subscriptions.ts`.
Webhooks come into `apps/api/routes/stripe.ts`. We store customer IDs in the
users table. There's a sync job that runs hourly. Don't forget to handle the
`invoice.payment_failed` event...
This is a wall of facts the model has to hold in context, and it'll be stale in two weeks.
Approach B — point to the source of truth:
## Billing
Before touching anything under `services/billing/` or `apps/api/routes/stripe.ts`:
1. Read `services/billing/ARCHITECTURE.md` (current state of the system).
2. Read `services/billing/WEBHOOKS.md` (event handling rules).
3. Check `services/billing/__tests__/` for the contract the implementation must satisfy.
Hard constraint: never call Stripe's API directly from a route handler. Always
go through `services/billing/client.ts` so retries and idempotency are handled.
Approach B costs you less context, stays fresh automatically (because the linked docs live next to the code), and gives the model a clear procedure. The constraint at the bottom is the part you actually want encoded — it's the lesson from the incident where someone hit Stripe with raw fetch() and got rate-limited.
How to verify: closing the loop
Most CLAUDE.md files forget the verification step. The model finishes, says "I've implemented X," and you discover later that types are broken or tests don't run. The fix is explicit.
## Before declaring work done
Run, in order:
```bash
pnpm typecheck
pnpm lint
pnpm test --run
All three must pass. If any fail:
- Fix the failure (do not skip, do not mark as
xtest/skip). - If the failure is unrelated to your change, stop and report it.
For changes touching the database schema, also run:
pnpm db:generate
pnpm db:migrate:dry
The dry-run output must be reviewed before applying. Never run db:migrate without explicit user confirmation.
Two things matter here. First, the model now has a checklist it can execute and report against. Second, the failure modes are pre-handled — "don't skip the test" closes a loophole where the model will sometimes mark a flaky test as skipped to make CI green.
## Known traps: the postmortem log
This is the section that earns its keep over time. Every time Claude Code makes the same mistake twice across sessions, add it here.
```markdown
## Known traps
- The model has repeatedly tried to use `axios` in this repo. We use `undici`. There's no `axios` in `package.json` — don't add it.
- `utils/date.ts` has both `formatDate` and `formatDateLocal`. They are not interchangeable. Read the JSDoc.
- The `User` type in `packages/types` is the canonical one. There's a duplicate in `apps/web/types/user.ts` that's deprecated — do not import from there.
- Migrations are run via `pnpm db:migrate`, not `prisma migrate dev`. We have a custom wrapper that handles our multi-tenant schema split.
- The CI uses Node 20. If you see code using Node 22-only APIs (e.g., `process.loadEnvFile`), reject it.
Each entry is a specific, recurring failure with a specific fix. This section will be the most-read part of your CLAUDE.md by the model, because it's where the highest-density per-token guidance lives.
What to measure: is your CLAUDE.md actually working?
You can tell if a CLAUDE.md is doing its job by tracking three signals across sessions:
| Signal | What it tells you |
|---|---|
| Number of times the model asks "should I use X or Y?" for a choice already documented | The rule isn't in the file, or it's buried |
| Number of PRs where you correct a convention violation in review | Either the rule is missing, or the model isn't reading the area-specific file |
| Number of times the model runs the wrong verification command | Your "before declaring work done" section is unclear or missing |
Treat each instance as a bug in CLAUDE.md, not a bug in the model. Add the rule, sharpen the wording, or move it somewhere the model is more likely to encounter it. Over six to eight weeks of active development, a well-maintained CLAUDE.md converges on something compact and effective — usually 200-400 lines for the root, with per-area files that grow with their subsystems.
A few practical maintenance habits:
- Review
CLAUDE.mdin the same PR that introduces a new convention. If you migrate fromjesttovitest, theCLAUDE.mdupdate is part of the migration PR, not a follow-up. - Date your "Known traps." When a trap is no longer relevant (the duplicate type got deleted, the deprecated folder got removed), remove it. Stale rules are noise.
- Don't let it become a style guide. If a rule could be enforced by Prettier, ESLint, or a typecheck, enforce it there.
CLAUDE.mdis for things tools can't catch: intent, ordering, "ask before doing."
How BizFlowAI approaches this
Every Claude Code engagement we run for a client starts with a CLAUDE.md audit. We open the file, then open the repo, and look for the gap: what does the model actually need to know that isn't documented, and what's documented but never gets followed? Most of the time the file has been added to four times and pruned zero times. The first deliverable is usually a 60% shorter root file plus three or four per-area files, scoped to where the model actually does work.
We also wire in the verification loop — the "before declaring done" commands, the dry-run gates for destructive operations, the pre-commit hook that fails if CLAUDE.md references a file that no longer exists. The goal isn't a perfect document; it's a document that gets better every time the model makes a mistake, with a clear owner and a clear update path. That's the difference between a CLAUDE.md that decays into noise and one that compounds into real leverage.
A starter template you can copy
Here's a compact root CLAUDE.md you can adapt. Strip what doesn't apply, add what does.
# Project: <name>
## What this is
<one paragraph: product, stack, who uses it>
## Hard rules
- Never commit secrets. Stop and report if you find one.
- Never install new dependencies without asking.
- Never edit generated files (list them here).
- Never bypass <auth function> in <auth dir>.
- For destructive actions (delete, force-push, schema change), stop and confirm.
## Before you start
- Run `<typecheck command>` to see the current state.
- For the area you're touching, read the file listed in the area map.
## Area map
| Path | Read first | Notes |
|---|---|---|
| `<path>` | `<path>/CLAUDE.md` | <gotcha> |
## Verifying your work
Run, in order:
1. `<typecheck>`
2. `<lint>`
3. `<test>`
All must pass. Do not skip failing tests.
## Known traps
- <specific recurring mistake> — <specific fix>
Two hundred lines, max. If you're past that at the root, you're describing the codebase instead of constraining the model. Push detail down into per-area files, and let the area map do the routing.
The test of a good CLAUDE.md isn't how thorough it reads. It's whether, three weeks from now, Claude Code stops making the mistakes that annoyed you this week. Track that, iterate on the file, and treat it like the production artifact it is.
Frequently asked questions
What is a CLAUDE.md file and why does it matter?
CLAUDE.md is a configuration file Claude Code reads at the start of a session to learn project-specific rules, conventions, and constraints. It matters because without it the model reinvents utilities, uses banned libraries, or violates team conventions. A good CLAUDE.md tells the model what files to read before acting, what never to do, and how to verify its work. It acts as a persistent memory across sessions for repo-specific context.
What are the most common mistakes in a CLAUDE.md file?
The two biggest mistakes are thin files with vague advice like 'write clean code' and kitchen-sink files that list every convention with equal weight. Thin files give the model nothing it can't infer from the repo itself. Kitchen-sink files destroy signal-to-noise because critical rules sit next to trivia. A third mistake is describing the codebase instead of constraining the model — documentation belongs in READMEs, while CLAUDE.md should contain instructions.
How should I structure hard rules in CLAUDE.md?
Write hard rules like linter checks: declarative, scoped, and with a clear failure condition. Use 'never' and 'always' instead of 'prefer' or 'try to.' Each rule should be executable by a junior engineer without follow-up questions. Examples include 'Never edit migrations merged to main' or 'Never bypass requireSession() in services/auth/.' Hard rules are non-negotiable and capture lessons from past incidents.
Where should soft rules and coding style conventions live?
Soft rules like naming conventions and preferred libraries belong in per-area CLAUDE.md files referenced from an area map in the root file, not at the top level. Different subsystems often have different conventions, so a global 'use functional style' rule is wrong for half the repo. Claude Code loads the relevant local file when working in that directory, giving specificity without bloating the root file. This pattern also scales as you add new services.
How do I make Claude Code verify its work before declaring done?
Add an explicit verification section listing exact commands to run in order, such as typecheck, lint, and tests. Define what 'done' means — all checks pass without skipping tests or marking them as xtest. For destructive actions like database migrations, require dry-runs and explicit user confirmation before applying. This gives the model a checklist it can execute and report against, closing the loop on common failure modes.
Work with BizFlowAI
If you'd rather have this built for you, that's what we do: production AI automation for solo founders and small teams — agents, integrations, and document pipelines that actually ship.
Book a free discovery call — 30 minutes, we map the highest-ROI automation in your workflow. No pitch deck, just engineering.
More guides like this on the BizFlowAI blog.
Frequently asked questions
What is a CLAUDE.md file and why does it matter?
CLAUDE.md is a configuration file Claude Code reads at the start of a session to learn project-specific rules, conventions, and constraints. It matters because without it the model reinvents utilities, uses banned libraries, or violates team conventions. A good CLAUDE.md tells the model what files to read before acting, what never to do, and how to verify its work. It acts as a persistent memory across sessions for repo-specific context.
What are the most common mistakes in a CLAUDE.md file?
The two biggest mistakes are thin files with vague advice like 'write clean code' and kitchen-sink files that list every convention with equal weight. Thin files give the model nothing it can't infer from the repo itself. Kitchen-sink files destroy signal-to-noise because critical rules sit next to trivia. A third mistake is describing the codebase instead of constraining the model — documentation belongs in READMEs, while CLAUDE.md should contain instructions.
How should I structure hard rules in CLAUDE.md?
Write hard rules like linter checks: declarative, scoped, and with a clear failure condition. Use 'never' and 'always' instead of 'prefer' or 'try to.' Each rule should be executable by a junior engineer without follow-up questions. Examples include 'Never edit migrations merged to main' or 'Never bypass requireSession() in services/auth/.' Hard rules are non-negotiable and capture lessons from past incidents.
Where should soft rules and coding style conventions live?
Soft rules like naming conventions and preferred libraries belong in per-area CLAUDE.md files referenced from an area map in the root file, not at the top level. Different subsystems often have different conventions, so a global 'use functional style' rule is wrong for half the repo. Claude Code loads the relevant local file when working in that directory, giving specificity without bloating the root file. This pattern also scales as you add new services.
How do I make Claude Code verify its work before declaring done?
Add an explicit verification section listing exact commands to run in order, such as typecheck, lint, and tests. Define what 'done' means — all checks pass without skipping tests or marking them as xtest. For destructive actions like database migrations, require dry-runs and explicit user confirmation before applying. This gives the model a checklist it can execute and report against, closing the loop on common failure modes.