Why your long CLAUDE.md gets ignored, and how to fix it
In short: Claude Code treats CLAUDE.md as context, not enforced configuration. A long file costs context on every session, buries the rules that matter, and tends to hide contradictions. Keep only the facts every session needs (under 200 lines, per Anthropic's docs). Move procedures into skills and file-specific rules into .claude/rules/. Use @imports to organize, knowing they don't save tokens. Then turn "run the tests before you say you're done" into a check Claude can run, and enforce it with a Stop hook.
Why a long CLAUDE.md stops working
The Claude Code memory docs are explicit: CLAUDE.md content is delivered as a user message after the system prompt, not as part of the system prompt itself. Claude reads it and tries to follow it, "but there's no guarantee of strict compliance, especially for vague or conflicting instructions." Four things make that worse as the file grows:
- Length. The docs say to target under 200 lines per CLAUDE.md file: "Longer files consume more context and reduce adherence." Anthropic's best practices are blunter: "Bloated CLAUDE.md files cause Claude to ignore your actual instructions!"
- Lost rules. Also from the best practices: if Claude keeps doing something you have a rule against, "the file is probably too long and the rule is getting lost."
- Contradictions. Claude Code concatenates every CLAUDE.md it finds, plus your rules files, instead of letting one override another. If two instructions disagree, the docs say Claude "may pick one arbitrarily". Long files hide those conflicts.
- Vague lines and emphasis inflation. "Test your changes" can't be checked. "Run
npm testbefore committing" can. And if you mark many lines IMPORTANT, none of them stands out. The docs suggest emphasis on one line only.
First, check what actually loaded
/contextlists the CLAUDE.md and rules files loaded at launch under Memory files. If a file isn't there, Claude can't see it. CLAUDE.md files in subdirectories don't show up there, because they load on demand when Claude reads or edits a file in that folder.- Length warnings. When a file is over the recommended length, Claude Code warns at startup and in
/status. It also warns when several files that are each fine add up past a combined limit. Each CLAUDE.md, rules file and@pathimport counts as a separate file. /doctorproposes trims for a checked-in CLAUDE.md. It cuts what Claude can derive from the codebase (directory layouts, dependency lists, architecture overviews) and keeps pitfalls, rationale and non-default conventions./doctor prompt-audit(Claude Code v2.1.283 or later) looks for outdated or conflicting instructions across your CLAUDE.md, AGENTS.md, rules and skills. It only reports; nothing changes until you ask Claude to apply the edits.
Fix 1: keep the always-on part short
The docs' rule for CLAUDE.md: "Keep it to facts Claude should hold in every session: build commands, conventions, project layout, 'always do X' rules." The test for every line is Anthropic's: "Would removing this cause Claude to make mistakes?" If not, cut it.
| Keep in CLAUDE.md | Cut or move |
|---|---|
| Commands Claude can't guess (test one file, typecheck, lint) | Anything Claude can learn by reading the code |
| Conventions that differ from the defaults | Standard language conventions, "write clean code" |
| Testing instructions and the preferred test runner | Detailed API docs (link to them instead) |
| Environment quirks and gotchas | File-by-file descriptions, long tutorials, anything that changes often |
Here is a complete project CLAUDE.md in that spirit, about 25 lines:
# acme-api: Node 22, Fastify, Postgres
## Commands
- Typecheck: `npm run typecheck`
- One test file: `npx vitest run path/to/file.test.ts`
- Full suite: `npm test` (needs `docker compose up -d db` first)
- Lint and fix: `npm run lint -- --fix`
## Conventions that differ from defaults
- Money is integer cents (`amountCents`), never floats
- Database access only through `src/db/repo/*`, no raw SQL in route handlers
- New env vars must be added to the schema in `src/env.ts`
## Definition of done
- Run `npm run typecheck` and the tests for every file you changed,
then `npm test`, before you say a task is done
- Report the exact commands you ran and their results.
If you didn't run something, say so
- Never delete, skip or loosen a test or assertion to get a pass
## Workflows (skills, loaded on demand)
- /release, /db-migration, /fix-issue
<!-- Maintainers: release steps live in .claude/skills/release/SKILL.md -->
That last line is free. Claude Code strips block-level HTML comments from CLAUDE.md before injecting it, so you can leave notes for humans without spending context on them.
Fix 2: move procedures into skills
The memory docs say it directly: "If an entry is a multi-step procedure or only matters for one part of the codebase, move it to a skill or a path-scoped rule instead." The skills docs explain why it pays off: "Unlike CLAUDE.md content, a skill's body loads only when it's used, so long reference material costs almost nothing until you need it."
A typical offender is a 20-line "How to release" section that every session pays for. As a skill it looks like this. Save it as .claude/skills/release/SKILL.md:
---
name: release
description: Cuts a new release. Bumps the version, updates CHANGELOG.md, tags and pushes. Use only when the user asks to release or publish a version.
disable-model-invocation: true
---
# Release
1. Check the working tree is clean: `git status --porcelain` must print nothing.
2. Run `npm run typecheck` and `npm test`. If anything fails, stop and report.
3. Ask which bump (patch, minor or major) if the user didn't say.
4. Move the "Unreleased" notes in CHANGELOG.md under the new version heading and commit.
5. Run `npm version <bump>`, then `git push --follow-tags`.
6. Report the new version and every command you ran.
Two details matter here. disable-model-invocation: true means only you can start it with /release, which is what you want for anything with side effects, and Claude Code then keeps even the description out of context until you call it. Without that field, the description sits in the skill listing every session and the body loads when the skill is used. Also keep skill bodies concise: once a skill loads, its content stays in the conversation across later turns. For writing descriptions that make a skill fire when it should, see how to write a SKILL.md that actually triggers.
Rules that only matter for some files belong in .claude/rules/ with paths frontmatter. They load when Claude reads or edits a matching file:
---
paths:
- "src/api/**/*.ts"
---
# API handlers
- Validate every request body with the schema in `src/api/schemas/`
- Return errors with `sendError()` from `src/api/errors.ts`
A rules file without paths loads at launch like CLAUDE.md, so splitting into rules only saves context when you scope them.
| If the line is… | Put it in |
|---|---|
| Needed in every session | CLAUDE.md |
| Only relevant to some files or folders | .claude/rules/*.md with paths, or a CLAUDE.md in that folder |
| A multi-step procedure or reference you need sometimes | A skill in .claude/skills/<name>/SKILL.md |
| Something that must happen every time, no exceptions | A hook |
| Your personal preference for this repo | CLAUDE.local.md (add it to .gitignore) |
Fix 3: use @imports to organize, not to save tokens
CLAUDE.md can pull in other files with @path/to/file. What the docs say about it:
- Relative paths resolve from the file that contains the import, not the working directory. Absolute paths and
~/paths work too. - Imported files can import others, up to four hops deep.
- Imports are expanded and loaded at launch, so they don't reduce context. In the docs' words, imports "help you organize a long file but don't reduce its context cost."
- An
@pathinside backticks or a code block stays literal text, so you can mention a file without importing it. - The first time a project imports a file from outside your working directory, Claude Code asks you to approve it.
# CLAUDE.md
@AGENTS.md
## Claude Code only
- Workflows are skills in .claude/skills: /release, /db-migration
- My personal notes: @~/.claude/acme-api-notes.md
The @AGENTS.md line is the useful one. It keeps one rule file shared with Codex, Cursor and other tools, with Claude-only notes underneath. By default, Claude Code (v2.1.277 or later) reads AGENTS.md on its own only when there's no CLAUDE.md or CLAUDE.local.md in your working directory or above it. Once you have one, import AGENTS.md from it. Details in AGENTS.md vs CLAUDE.md: one source of truth. The imported file counts toward the same budget, so keep it lean too.
Fix 4: make "done" mean the tests passed
Anthropic's best practices put it plainly: "Claude stops when the work looks done. Without a check it can run, 'looks done' is the only signal available." The fix is a check that returns pass or fail (tests, a build, a linter) plus evidence instead of claims: the command Claude ran and what it returned. You can apply it at three levels.
- In the prompt: "…then run the tests and fix any failures."
- In CLAUDE.md: a short, concrete "Definition of done" with exact commands, like the example above. This is still guidance, not enforcement.
- As a deterministic gate: a Stop hook that runs your check and blocks the turn from ending until it passes. The docs also describe a
/goalcondition that a separate evaluator re-checks after every turn, and verification subagents that give a second opinion.
Here is a Stop hook. Register it in .claude/settings.json (Stop hooks take no matcher):
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/require-tests.sh",
"timeout": 300
}
]
}
]
}
}
And the script, .claude/hooks/require-tests.sh (run chmod +x on it; it uses jq):
#!/bin/bash
# Stop hook: don't let Claude finish while the test suite fails.
INPUT=$(cat)
# Already continuing because a Stop hook blocked? Let it stop instead of looping.
# Delete this check for a strict gate.
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0
fi
cd "$CLAUDE_PROJECT_DIR" || exit 0
# No uncommitted changes means nothing to verify (e.g. Claude only answered a question).
if [ -z "$(git status --porcelain)" ]; then
exit 0
fi
if ! OUTPUT=$(npm test 2>&1); then
echo "Tests are failing. Fix the code, not the tests, before you finish:" >&2
echo "$OUTPUT" | tail -n 40 >&2
exit 2
fi
exit 0
How it behaves, per the hooks guide and reference:
- Exit code 2 on a Stop hook prevents Claude from stopping, and Claude receives the stderr text as the reason to continue. Exit 0 lets it stop.
- Stop hooks fire whenever Claude finishes responding, not only when a task is complete. That's why the script skips clean trees, and why the check should be fast. Use your quickest meaningful test command.
stop_hook_activeistruewhen Claude is already continuing because of a Stop hook. With the check in place, Claude is pushed back to work once per stop. Without it, Claude Code still ends the turn after eight consecutive blocks; that count resets whenever Claude calls a tool, andCLAUDE_CODE_STOP_HOOK_BLOCK_CAPchanges the cap.- Run
/hooksto confirm the hook is registered. Test the script by hand first:echo '{"stop_hook_active":false}' | CLAUDE_PROJECT_DIR=$PWD .claude/hooks/require-tests.sh; echo $?.
The same idea covers other "always" rules. A PostToolUse hook on Edit|Write can run your formatter after every edit, and a PreToolUse hook that exits 2 can block edits to protected files. Then those lines can come out of CLAUDE.md entirely. The docs also offer prompt and agent hook types for judgment calls (the agent hook example runs the test suite before Claude stops), but agent hooks are marked experimental, and the docs recommend command hooks for production.
The same principle in Cursor and AGENTS.md
Cursor's rules docs give the same advice: keep rules under 500 lines, split large rules into composable ones, and reference files instead of copying their contents. A rule with alwaysApply: true is applied to every chat session; otherwise the agent gets it through matching globs or decides from its description. So keep always-apply rules for true essentials. Cursor also supports nested AGENTS.md files in subdirectories, which apply when the agent works with files there. For how the formats map onto each other, see Cursor rules vs CLAUDE.md vs AGENTS.md.
A 15-minute cleanup
- Run
/contextand note every memory file that loads at launch. - Count lines:
wc -l CLAUDE.md CLAUDE.local.md .claude/CLAUDE.md 2>/dev/null. Aim for under 200 per file. - Ask "would removing this cause a mistake?" for every line. Cut what fails.
- Move procedures into skills, file-specific rules into
.claude/rules/withpaths, and personal notes intoCLAUDE.local.md. - Rewrite vague lines as checkable ones with exact commands and paths.
- Run
/doctor prompt-auditto find conflicts and stale instructions. - Add the definition of done and the Stop hook.
- Start a fresh session and check that Claude's behavior actually changes. Treat CLAUDE.md like code and prune it when things go wrong.
FAQ
How long should CLAUDE.md be?
Anthropic's Claude Code docs say to target under 200 lines per CLAUDE.md file, because longer files consume more context and reduce adherence. For each line, ask whether removing it would cause Claude to make mistakes. If not, cut it, or move it into a skill or a path-scoped rule.
Do @imports in CLAUDE.md reduce context usage?
No. Imported files are expanded and loaded into context at launch, alongside the CLAUDE.md that imports them. Imports help you organize a long file, but they don't lower its context cost. To load instructions only when needed, use .claude/rules files with paths frontmatter, or skills.
What is the difference between CLAUDE.md, .claude/rules and skills?
CLAUDE.md and rules without paths load at the start of every session. Rules with paths frontmatter load when Claude works with matching files. A skill's description sits in the skill listing, and its full body loads only when you or Claude invoke it. With disable-model-invocation: true, even the description stays out of context until you call the skill.
How do I make Claude Code run tests before it says it's done?
Write a concrete definition of done in CLAUDE.md with the exact test command, and ask Claude to report the commands it ran and their results. For a deterministic gate, add a Stop hook that runs the tests and exits with code 2 when they fail. Claude Code then keeps the conversation going and gives Claude the hook's stderr as the reason to continue.
Why does Claude still ignore a rule after I shortened CLAUDE.md?
Run /context to confirm the file loaded, look for conflicting instructions in other CLAUDE.md files, .claude/rules or ~/.claude, and make the rule specific enough to check. CLAUDE.md is guidance, not enforcement. If something must happen at a fixed point every time, such as after each edit or before Claude finishes, write it as a hook.
Sources
- Claude Code docs: How Claude remembers your project (CLAUDE.md, @imports, .claude/rules)
- Claude Code docs: Best practices
- Claude Code docs: Extend Claude with skills
- Claude Code docs: Automate actions with hooks
- Claude Code docs: Hooks reference (Stop hooks, exit code 2)
- Claude Code docs: Extend Claude Code (CLAUDE.md vs rules vs skills vs hooks)
- Cursor docs: Rules