Key Takeaway
How to write a CLAUDE.md file that works: run /init, cut everything Claude can work out from the code, write concrete commands, conventions and hard rules, stay under 200 lines, move folder-specific rules into path-scoped rules, then confirm with /context. CLAUDE.md is only context, so a must-follow rule needs a hook or a test.
You ran /init, Claude generated a CLAUDE.md, and a few days later the file is over a hundred lines because every time Claude made a mistake you added another sentence. Claude still uses npm when your project uses pnpm, or still edits a file you told it to leave alone. You sprinkle VERY IMPORTANT around, and things get less focused, not more. Almost everyone writing their first CLAUDE.md gets stuck at this point.
This guide takes you from an empty file to a short, concrete, testable CLAUDE.md in six steps. I checked every command and rule against the official Claude Code documentation, and the real example comes from this site's own CLAUDE.md. I deliberately left out the parts that hold personal identifiers.
Want to know first what CLAUDE.md is and how its layers and load order work? Read our free lesson: CLAUDE.md file example: layers and load order. This article is the hands-on side: you write the file step by step.
What you need
Not much, and you can try all of it in a practice repository.
- Claude Code installed and starting with the claude command.
- One git repository to experiment in. A practice repo is better than a work repo.
- About 20 minutes and any text editor.
Every slash command below (/init, /context, /memory, /doctor) is typed inside a claude session, not in a plain terminal.
Step 1: Generate a first draft with /init
Go into your project folder, run claude, then type /init. Claude reads the codebase and creates a CLAUDE.md with the build commands, test instructions and conventions it finds. If a CLAUDE.md already exists, /init suggests improvements instead of overwriting it. Treat the result as a draft, not the final file.
cd belajar-claude-md # your practice repo
claude # start a session
# Inside the session:
/init
# Claude analyses the codebase and writes ./CLAUDE.md.
# If ./CLAUDE.md already exists, it suggests improvements instead.The project file can live at ./CLAUDE.md or ./.claude/CLAUDE.md and should be committed so the whole team shares it. If your repo already has an AGENTS.md for other tools, read the comparison first in AGENTS.md vs CLAUDE.md. For personal notes that must stay out of git, use CLAUDE.local.md at the project root and add it to .gitignore.
Step 2: Cut it down to what Claude cannot guess
This is the step people skip most. The docs suggest one question for every line: would removing it cause Claude to make mistakes? If not, delete it. The table below sums up what earns a place and what does not.
| Include | Exclude |
|---|---|
| Bash commands Claude cannot guess | Anything Claude can figure out by reading the code |
| Code style rules that differ from the defaults | Standard language conventions Claude already knows |
| Testing instructions and the preferred test runner | Detailed API documentation (link to it instead) |
| Architectural decisions specific to your project | File-by-file descriptions of the codebase |
| Gotchas that are not self-evident | Self-evident advice like write clean code |
# Wrong: sounds fine, but none of it can be checked.
This is an important project. Write clean code, follow best practices
and make sure to test your changes properly. Keep files organized.
# Right: each line names a command, a folder or a number.
- Use 2-space indentation
- Run npm test before committing
- API handlers live in src/api/handlers/Notice the difference in the example above. The wrong version sounds reasonable but cannot be checked: when is code clean? The right version names commands, folders and numbers you can verify. The docs give the same pattern: use 2-space indentation beats format code properly.
Step 3: Write commands, conventions and hard rules
The structure I use is four short blocks under headers: Commands, Conventions, Hard rules and Gotchas. The docs say there is no required format, but a file organised with headers and bullets is easier for Claude to follow than dense paragraphs. Here is an example for a made-up project called toko-api.
# toko-api
Backend API for a small online shop (Node.js, Express, PostgreSQL).
## Commands
npm run dev # API server on http://localhost:3000
npm test # unit tests; the API tests need a local Redis
npm run lint # ESLint, fix every error before committing
npx tsc --noEmit # typecheck without building
## Conventions
- Use 2-space indentation
- API handlers live in src/api/handlers/
- Money is stored as integer cents, never as floats
## Hard rules
1. Never edit a migration that is already merged. Add a new one: staging replays them in order.
2. Throw errors through src/api/errors.ts, not raw strings: the client parses its format.
## Gotchas
- npm run build regenerates src/generated/schema.ts. Run npm run schema:build after editing schema.sql.
<!-- maintainers: rule 1 exists because of the staging replay, keep it -->Each hard rule says what breaks if it is violated. That is what makes a rule checkable. Here is how it looks in this site's own CLAUDE.md, the Hard rules section:
## Hard rules — build breaks if violated
1. **`messages/en.json` and `messages/id.json` must have the exact same key tree.** Missing key in either file → runtime error.
2. **No `{`, `}`, or `<` in blog `content.*` JSON values.** The merge script rejects them. Put markup in JSX; put ICU interpolation only in non-blog UI strings.
3. **Every slug in `BLOG_POSTS_STATIC` must have an entry in `BLOG_META` (and vice versa).** `assertBlogParity()` fails the build otherwise.
4. **Every component key read in a blog component must exist in the translation fragment.** The merge script validates this.
5. **Always import `Link`, `useRouter`, `usePathname` from `@/i18n/navigation`, not from `next/link` or `next/navigation`.** Bare Next.js navigation loses the locale prefix.Five rules, one line each. Rules 2 to 4 also name their backstop, the merge script or assertBlogParity() failing the build, so the rule holds even on a day Claude misreads it. One more thing in this file is a gotcha written down once: npm run build fails when lib/blog-sources.json is stale, with the fix command next to it, so Claude does not have to guess the cause.
If Claude keeps skipping one instruction, add emphasis such as IMPORTANT to that line alone. The docs explain that if you emphasise many lines, none of them stands out.
Step 4: Stay under 200 lines and move the rest
The docs target under 200 lines per CLAUDE.md file. Longer files consume more context and reduce adherence. To be honest, this site's own CLAUDE.md runs past 500 lines, and much of it is blog procedure that belongs in a skill. I know that debt, and it is a bad example to copy.
You have three places to move things. A rule that matters for one folder goes into a file like .claude/rules/api.md with paths frontmatter and loads only when Claude touches a matching file. A multi-step procedure goes into a skill, which loads when invoked or when Claude judges it relevant. A note for humans goes into an HTML comment, which is stripped before the file reaches Claude.
---
paths:
- "src/api/**/*.ts"
---
- All API endpoints must include input validation
- Use the shared error format in src/api/errors.ts# Wrong: five @imports still load at launch, so the context cost is unchanged.
@docs/api.md
@docs/git.md
@docs/testing.md
# Right: keep the always-needed facts in CLAUDE.md,
# and move the one-folder rules into .claude/rules/ with paths.Do not split a long file into several @imports to save context. Imported files load at launch just like inline text, so the cost stays the same. If your repo is a monorepo with many folders, also see the Claude Code monorepo workflow.
Step 5: Back every must-follow rule with a safeguard
CLAUDE.md reaches Claude as a user message after the system prompt. Claude reads it and tries to follow it, but the docs are clear that strict compliance is not guaranteed. To block an action whatever Claude decides, use a PreToolUse hook. Hooks run deterministically, while CLAUDE.md instructions are advisory.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}#!/bin/bash
# .claude/hooks/protect-files.sh (run: chmod +x on it)
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
case "$FILE_PATH" in
*.env*)
# stderr becomes Claude's feedback; exit 2 blocks the edit
echo "Blocked: $FILE_PATH is protected" >&2
exit 2
;;
esac
exit 0 # no objection; the normal permission flow still appliesThis site follows the same pattern: every hard rule has a safeguard that fails on its own, such as a failing build or a red test. The line in CLAUDE.md explains the reason to Claude, and the safeguard protects the outcome. The hook goes in .claude/settings.json and its script must be executable.
A project CLAUDE.md is shared with the whole team through git. Do not put secrets, passwords or personal data in it. Keep personal preferences in CLAUDE.local.md, which is gitignored, and reread the file before you commit.
Step 6: Test that Claude really reads it
Start a new session, run /context, and look at the list under Memory files. If a file is missing there, Claude cannot see a word of it. The /memory command also lists your CLAUDE.md and CLAUDE.local.md locations and opens them for editing.
/context
# Read the list under "Memory files".
# Your ./CLAUDE.md must be there. A CLAUDE.md in a subfolder is NOT:
# it loads on demand, and a "Loaded" line appears when Claude first
# reads, writes or edits a file in that folder.
/memory # lists your CLAUDE.md files and opens them for editing
/doctor prompt-audit # stale or conflicting instructions (v2.1.283+)Then test the behaviour: ask Claude to do something one of your rules covers and see whether it complies. The docs suggest treating CLAUDE.md like code: review it when things go wrong, prune it regularly, and test changes by observing whether Claude's behaviour actually shifts. To find stale or conflicting instructions, run /doctor prompt-audit, which needs Claude Code v2.1.283 or later.
Common problems and how to fix them
The six problems that show up most often after the file is written, with the cause and the fix.
| Symptom | Cause | Fix |
|---|---|---|
| Claude ignores a rule you wrote down | The file is too long and the rule is getting lost, or the wording is vague | Prune to under 200 lines and rewrite the rule concretely. Add IMPORTANT to the one line that keeps getting skipped. |
| The file is missing from Memory files in /context | The name or location is wrong, or the session was started from a folder outside that project | Make sure it is named CLAUDE.md at ./CLAUDE.md or ./.claude/CLAUDE.md, restart the session from the project folder, then check /context again. |
| A subfolder CLAUDE.md is not in Memory files | A subfolder file loads on demand, not at launch | Ask Claude to read a file in that folder. A Loaded line with its path appears in the terminal. |
| Claude sometimes uses npm and sometimes pnpm | Two instructions contradict each other. Files are concatenated, not overriding, so Claude may pick either. | Delete one of them, and check the user file, the project file and subfolder files. Run /doctor prompt-audit to help find it. |
| A never touch .env rule still gets broken | CLAUDE.md is context, not enforcement | Add a PreToolUse hook that exits with code 2 to block the action. |
| Personal notes get committed to the repo | The notes were written in the CLAUDE.md that is shared through git | Move them to CLAUDE.local.md and add it to .gitignore. Never put secrets in a committed file. |
If one problem keeps coming back, do not add another line to CLAUDE.md. First ask whether the rule needs a safeguard, belongs in a rule or skill, or should be cut.
Checklist before you commit
- The file is under 200 lines and has no line Claude could work out from the code.
- There are blocks for commands, conventions, hard rules and gotchas, each under a header.
- Every rule is concrete and checkable, with no empty advice.
- No two instructions contradict each other, including with your user file and subfolder files.
- Every rule that must always hold has a hook, permission or test behind it.
- /context lists the file under Memory files, and personal notes live in CLAUDE.local.md.
Go deeper
To understand the managed, user, project and local layers and their load order, take the free lesson CLAUDE.md file example: layers and load order. To see how context is managed in large repositories, read context engineering for AI agents in monorepos.
The rule is simple: write only what Claude cannot guess, make every line checkable, and do not trust CLAUDE.md alone with a rule that must hold. A short, concrete file is read and followed more consistently than a long, complete one.

