Home › Articles

Why your long CLAUDE.md gets ignored, and how to fix it

Updated 2026-10-18

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:

First, check what actually loaded

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.mdCut 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 defaultsStandard language conventions, "write clean code"
Testing instructions and the preferred test runnerDetailed API docs (link to them instead)
Environment quirks and gotchasFile-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 sessionCLAUDE.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 sometimesA skill in .claude/skills/<name>/SKILL.md
Something that must happen every time, no exceptionsA hook
Your personal preference for this repoCLAUDE.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:

# 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.

  1. In the prompt: "…then run the tests and fix any failures."
  2. In CLAUDE.md: a short, concrete "Definition of done" with exact commands, like the example above. This is still guidance, not enforcement.
  3. 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 /goal condition 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:

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

  1. Run /context and note every memory file that loads at launch.
  2. Count lines: wc -l CLAUDE.md CLAUDE.local.md .claude/CLAUDE.md 2>/dev/null. Aim for under 200 per file.
  3. Ask "would removing this cause a mistake?" for every line. Cut what fails.
  4. Move procedures into skills, file-specific rules into .claude/rules/ with paths, and personal notes into CLAUDE.local.md.
  5. Rewrite vague lines as checkable ones with exact commands and paths.
  6. Run /doctor prompt-audit to find conflicts and stale instructions.
  7. Add the definition of done and the Stop hook.
  8. 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

Official docs, checked 2026-10-18.

Kit updates, every 2 weeks

One short email every two weeks covering tool format changes (Cursor, Claude Code, Codex, Windsurf, Copilot), new rules and kit updates. No spam.

We store only your email, with our email provider Resend. See the privacy policy.