Как написать CLAUDE.md, который действительно меняет поведение модели
Руководство 2026 года по файлам CLAUDE.md, которым Claude Code действительно следует: ориентир в 200 строк, когда использовать правила с привязкой к путям в .claude/rules/, иерархия @import и предел в 5 переходов, разница между сообщением пользователя и системным prompt, граница между CLAUDE.md и автоматической памятью, и когда сдаться и написать hook. Привязано к Claude Code 2.1.x и проверено по официальной документации памяти.
CLAUDE.md, который “не работает”, почти всегда означает одно из трёх: он слишком длинный и важные правила тонут, он слишком расплывчатый, чтобы его можно было проверить, или инструкция должна быть hook, потому что CLAUDE.md по замыслу носит рекомендательный характер. Начиная с Claude Code 2.1.x файл загружается в контекст как сообщение пользователя после системного prompt, а не в сам системный prompt, и это неочевидная деталь, которая объясняет значительную часть фрустрации в духе “Claude игнорирует мои правила” на r/ClaudeAI и r/cursor в этом месяце. Поведение модели действительно меняется в ответ на хороший CLAUDE.md, но только если относиться к нему так, как описывает собственная документация по памяти Anthropic: как к контексту, а не к конфигурации.
Короткая версия: целитесь меньше чем в 200 строк, пишите конкретные проверяемые инструкции, выносите тематические правила в .claude/rules/ с frontmatter paths:, выносите переиспользуемые рабочие процессы в skills, и используйте hooks для всего, что обязано выполниться. Используйте @imports для организации, но понимайте, что они не экономят токены. И если вы исправляете одну и ту же ошибку дважды, не закапывайте её глубже в CLAUDE.md, она и так уже проигрывает битву другим вашим правилам.
Этот пост предполагает Claude Code 2.1.59+ (версия с автоматической памятью) и claude-sonnet-4-6 или claude-opus-4-7 в качестве базовой модели. Паттерны работают одинаково в обеих, но Sonnet более чувствителен к раздутым CLAUDE.md, потому что соблюдение правил падает быстрее по мере заполнения контекста.
Почему “я ему сказал” недостаточно
Самое полезное предложение в официальной документации по памяти звучит так: “Содержимое CLAUDE.md доставляется как сообщение пользователя после системного prompt, а не как часть самого системного prompt. Claude читает его и пытается следовать, но строгое соблюдение не гарантировано.” Это объясняет каждый тред типа “я буквально написал NEVER use console.log, и оно всё равно сделало”. Модель видит ваш CLAUDE.md так же, как остальную часть вашего prompt: как инструкции, которые надо взвесить, а не как директиву, которую нельзя переопределить.
Из этого следуют три конкретных вывода:
- Чем больше текста, тем хуже соблюдение. Чем длиннее файл, тем сильнее размывается каждое отдельное правило. Официальная документация рекомендует “целиться меньше чем в 200 строк на файл CLAUDE.md. Более длинные файлы потребляют больше контекста и снижают соблюдение.”
- Расплывчатые правила округляются. “Форматируйте код правильно” модель интерпретирует так же, как и вы: сделать что-то разумное. “Используйте отступ в 2 пробела, без завершающей точки с запятой, кроме как после imports” — это проверяемая инструкция, которой модель действительно может следовать.
- Конфликтующие правила разрешаются произвольно. Если ваш корневой CLAUDE.md говорит “всегда писать тесты”, а вложенный во вложенной папке говорит “пропускать тесты для прототипов”, модель выбирает один из них, не сообщая вам какой.
Если вам действительно нужна неотменяемая директива, у вас два варианта. Первый — --append-system-prompt, который помещает текст в сам системный prompt. По справочнику CLI его нужно передавать при каждом запуске, что годится для скриптов и CI, но непригодно для интерактивного использования. Второй и почти всегда лучший вариант — hook, к которому мы ещё придём.
Что относится к CLAUDE.md, а что нет
Собственное руководство по лучшим практикам Anthropic даёт лаконичную таблицу включить/исключить, которую я копирую в каждый проект, который веду. Перефразированное и сжатое:
Включать: bash-команды, которые Claude не может угадать из вашего package.json или Cargo.toml; правила стиля кода, отличающиеся от значений по умолчанию языка; test runner, который вы реально хотите использовать; соглашения по веткам и PR; архитектурные решения, которые не очевидны из чтения кода; и подводные камни вроде “тестовому контейнеру postgres нужен POSTGRES_HOST_AUTH_METHOD=trust, иначе миграции зависают.”
Исключать: всё, что Claude может прочитать из tsconfig.json; соглашения framework, которые знает каждый разработчик; пофайловые описания базы кода; историю того, как код пришёл к текущему состоянию; и самоочевидные практики типа “пишите чистый код”. Документ по лучшим практикам прямолинеен: “Раздутые файлы CLAUDE.md заставляют Claude игнорировать ваши настоящие инструкции.” Каждая добавленная строка снижает соотношение сигнал/шум для остального.
CLAUDE.md, переживший такой фильтр, для backend на Next.js + Postgres выглядит так:
# Project: invoice-api
# Claude Code 2.1.x, Node 22, Next.js 15
## Build and test
- Use `pnpm`, never `npm` or `yarn`. The lockfile is committed.
- Run `pnpm test --filter @app/api` for backend tests, NOT the full workspace.
- Migrations: `pnpm db:migrate` only inside the `apps/api` workspace.
## Code style
- Use ESM (`import`/`export`). Default export is forbidden except in
Next.js page/route files where the framework requires it.
- Zod schemas for every external input. No `any`, no `as unknown as T`.
## Architecture
- Database access goes through `apps/api/src/db/repositories/`.
Do not call `db.query` from route handlers.
- All money is `bigint` cents. Never `number`, never decimals.
## Workflow
- After a code change, run `pnpm typecheck` and `pnpm test --filter @app/api`.
- Commit messages: imperative, no scope prefix, max 72 chars on the title.
Это 17 строк, и они закрывают каждую повторяющуюся правку, которую эта команда задокументировала в шаблоне PR. Обратите внимание, чего там нет: никакого “всегда писать чистый код”, никакого “будьте осторожны с безопасностью”, никакого “используйте strict mode TypeScript” (это в tsconfig.json, модель это видит). Каждая строка отвечает на вопрос “приведёт ли её удаление к измеримой ошибке?” словом “да”.
Потолок в 200 строк и .claude/rules/
Как только вы переходите границу в 200 строк, официальная документация по памяти рекомендует разделять тематические инструкции в .claude/rules/ с YAML frontmatter, привязывающим каждый файл к glob:
---
paths:
- "src/api/**/*.ts"
- "src/api/**/*.tsx"
---
# API endpoint conventions
- Every route under `src/api/` exports a `POST`, `GET`, `PUT`, or `DELETE`
function. Never a default export.
- Validate the body with the matching Zod schema in `src/api/schemas/`
before doing anything else. If no schema exists, write one first.
- Return errors with `Response.json({ error }, { status })`. Do not throw.
Правило с paths: загружается в контекст только тогда, когда Claude читает файл, совпадающий с одним из glob. Стоимость десяти файлов правил по 100 строк каждый намного меньше, чем одного CLAUDE.md в 1000 строк, потому что девять из них не находятся в контексте для любой конкретной задачи. Правила без paths: загружаются в каждой сессии с тем же приоритетом, что и .claude/CLAUDE.md, поэтому не складывайте их туда по привычке, если они действительно не применяются к каждому файлу.
Здесь же умирает “расползание области в CLAUDE.md”. Если коллега предлагает добавить двенадцать строк об одном малоизвестном инструменте миграций, ответ “это идёт в .claude/rules/migrations.md с paths: ['db/migrations/**/*.sql']”, а не “потом подрежем”. Никогда не подрежем потом.
Imports, иерархия и предел в 5 переходов
Синтаксис import @path/to/file — это для организации, а не для экономии токенов. Из документации: “Импортируемые файлы разворачиваются и загружаются в контекст при запуске вместе с CLAUDE.md, который на них ссылается.” Если вы разделите CLAUDE.md в 600 строк на корневой файл в 50 строк и @docs/conventions.md в 550 строк, модель всё равно увидит 600 строк.
Imports полезны для трёх конкретных вещей:
- Переиспользование одних и тех же инструкций между двумя репозиториями без копипасты. Делайте symlink или импортируйте общий файл из
~/shared/team-conventions.md. - Переопределения для отдельного разработчика, которые не должны попадать в commit.
@~/.claude/my-project-instructions.mdпозволяет хранить личные предпочтения в домашней папке, пока все получают командный CLAUDE.md из git. - Мост к
AGENTS.md, если в вашем репозитории уже есть такой для других агентов кодирования. Документация явно рекомендует@AGENTS.md, за которым следуют переопределения, специфичные для Claude:
@AGENTS.md
## Claude Code
Use plan mode for changes under `src/billing/`.
Imports разрешаются рекурсивно до пяти переходов вглубь. Дальше import тихо отбрасывается. Если у вас CLAUDE.md, импортирующий файл, который импортирует файл, который импортирует файл, и так четыре раза подряд, вы построили нечто хрупкое: сделайте структуру плоской.
Сама иерархия аддитивная, не переопределяющая. Проектный CLAUDE.md, пользовательский CLAUDE.md (~/.claude/CLAUDE.md) и любой CLAUDE.md, поднимающийся вверх по дереву каталогов от рабочего каталога, конкатенируются. CLAUDE.local.md (в gitignore) загружается после CLAUDE.md на том же уровне, поэтому ваши личные заметки выигрывают в конфликте. В монорепозитории, где вы не хотите видеть в своём контексте файлы CLAUDE.md соседних команд, настройка claudeMdExcludes принимает список glob-шаблонов:
{
"claudeMdExcludes": [
"**/monorepo/CLAUDE.md",
"/home/marius/monorepo/other-team/.claude/rules/**"
]
}
Поместите это в .claude/settings.local.json, чтобы исключение было вашим, а не командным.
CLAUDE.md — это “ваши требования”, автоматическая память — это “что заметил Claude”
В Claude Code 2.1.59 добавили автоматическую память: заметки, которые Claude пишет о себе, основываясь на ваших правках. Она лежит в ~/.claude/projects/<project>/memory/MEMORY.md и загружается так же, как CLAUDE.md, за исключением того, что в начале сессии забираются только первые 200 строк или 25KB MEMORY.md. Остальная часть каталога читается по запросу.
Самый чистый способ думать об этом разделении:
- CLAUDE.md содержит правила, которые вы хотите соблюдать с первого дня. “Запускайте
pnpm test --filter @app/api, а не весь набор.” Вы это написали, вы это закоммитили, ваша команда это видит. - Автоматическая память содержит паттерны, которые заметил Claude. “Пользователь предпочитает
vitestвместоjestи поправил меня, когда я сгенерировалjest.config.js.” Это написал Claude, это для конкретной машины, этого нет в git.
Из этого вытекают два практических правила. Первое: не дублируйте записи автоматической памяти в CLAUDE.md “на всякий случай”. Автоматическая память тоже загружается в каждой сессии. Второе: когда автоматическая память накапливает паттерн, который должна знать вся команда, продвигайте его: откройте MEMORY.md, скопируйте запись в CLAUDE.md, и /memory позволит вам удалить оригинал. Продвижение — это момент, когда “Claude заметил это про меня” становится “мы как команда это решили”.
Подробнее о разделении пост о планировании рутин Claude Code рассказывает, что переживает автономный запуск без человека в петле, что является полезным стресс-тестом того, действительно ли ваш CLAUDE.md самодостаточен.
Настройка соблюдения
Когда файл стал коротким и конкретным, можно выжать из него больше соблюдения тремя приёмами, на которых сходятся документация и полевые отчёты:
- Используйте акценты сдержанно. Официальная рекомендация — “настраивать инструкции, добавляя акценты (например,
IMPORTANTилиYOU MUST), чтобы повысить соблюдение.” Сдержанно — ключевое слово. Если всёIMPORTANT, то ничто не важно. Резервируйте акцент для правила, нарушение которого реально сломает сборку или поднимет дежурного. - Сначала глагол, потом ограничение. “Запускайте
pnpm typecheckпосле каждой правки кода вsrc/” соблюдается надёжнее, чем “Проверка типов должна выполняться регулярно.” Первое — действие; второе — настроение. - Совмещайте правило с режимом отказа. “Не вызывайте
db.queryиз route handlers; пул соединений выдаётся на запрос, а route handlers его утекают. Используйтеrepositories/вместо этого.” Режим отказа — то, что делает правило липким между сессиями.
Если вы исправляете одну и ту же ошибку дважды, а правило уже в CLAUDE.md, правильный шаг — не добавлять ещё одно правило. Это спросить, почему существующее правило не побеждает. Обычно дело в одном из: файл слишком длинный, два правила противоречат друг другу, или инструкция относится к тому типу, которому нужен hook.
Когда сдаться с CLAUDE.md и написать hook
CLAUDE.md носит рекомендательный характер. Hooks детерминированы. Из руководства по hooks они — “скрипты, которые автоматически выполняются в определённых точках рабочего процесса Claude” и “гарантируют, что действие произойдёт”. Если ваше правило относится к категории “должно срабатывать без исключений”, ему не место в CLAUDE.md.
Hook PostToolUse, запускающий Prettier после каждой правки файла, надёжнее строки в CLAUDE.md, гласящей “всегда запускайте Prettier после правок”. То же самое с “блокировать запись в migrations/”, что становится PreToolUse hook с шаблоном запрета. Тот же паттерн делает более широкую историю agent skills в Visual Studio 2026 рабочей на практике: skill — это мягкая инструкция, hook — жёсткие рельсы.
Это также правильный момент подумать о границе между CLAUDE.md и skills. Инструкция CLAUDE.md загружается в каждой сессии и применяется широко. Skill в .claude/skills/SKILL.md загружается по запросу, когда модель решает, что задача релевантна, поэтому глубокое знание рабочего процесса с побочными эффектами (например, рабочий процесс “fix-issue”, открывающий PR) принадлежит туда. Та же логика применима к инструкциям, которые огромны, но имеют значение только для одной части вашего кода: им нужно правило с привязкой к путям, а не CLAUDE.md.
Диагностика того, что реально загружено
Когда модель делает что-то не так, первый шаг — подтвердить, что она реально видит. Запустите /memory внутри сессии Claude Code. Это перечислит каждый CLAUDE.md, CLAUDE.local.md и файл правил, загруженный в данный момент, с путями. Если ожидаемого файла нет в списке, остальная часть разговора неважна: Claude его не видит.
Для правил с привязкой к путям и лениво загружаемых файлов CLAUDE.md из подкаталогов hook InstructionsLoaded срабатывает каждый раз, когда Claude подтягивает инструкции. Подключите его к логгеру, чтобы убедиться, что glob paths: действительно совпал, или чтобы отладить, почему вложенный CLAUDE.md никогда не перезагружается после /compact. Случай с компакцией — известный острый угол: корневой CLAUDE.md проекта переинъецируется после /compact, но вложенные перезагружаются только при следующем чтении файла в этом подкаталоге. Если вы полагаетесь на вложенный CLAUDE.md и инструкции кажутся потерянными в середине сессии, причина в этом.
Другая полезная диагностика: блочные HTML-комментарии (<!-- like this -->) удаляются из CLAUDE.md перед инъекцией. Используйте их для заметок только для людей (строка <!-- last reviewed 2026-04 -->), не платя стоимостью токенов.
Связанное
- Как запланировать повторяющуюся задачу Claude Code, классифицирующую issues GitHub описывает, что нужно CLAUDE.md для автономных запусков.
- Claude Code 2.1.119: запуск из PR с GitLab и Bitbucket для родственного вопроса “где живут мои инструкции в облачной сессии”.
- Visual Studio 2026 Copilot agent skills — ближайший аналог со стороны Microsoft: файлы skill против постоянного контекста.
- Создание MCP-сервера на TypeScript для случая, когда лучшим ответом, чем “больше правил в CLAUDE.md”, является “открыть инструмент агенту”.
Источники
- Официально: Как Claude помнит ваш проект (документация по памяти Claude Code и CLAUDE.md).
- Официально: Лучшие практики для Claude Code.
- Официально: Справочник по hooks и hook
InstructionsLoaded. - Полевые заметки: Writing a good CLAUDE.md (HumanLayer).
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.