Start Debugging

Как написать 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: как инструкции, которые надо взвесить, а не как директиву, которую нельзя переопределить.

Из этого следуют три конкретных вывода:

  1. Чем больше текста, тем хуже соблюдение. Чем длиннее файл, тем сильнее размывается каждое отдельное правило. Официальная документация рекомендует “целиться меньше чем в 200 строк на файл CLAUDE.md. Более длинные файлы потребляют больше контекста и снижают соблюдение.”
  2. Расплывчатые правила округляются. “Форматируйте код правильно” модель интерпретирует так же, как и вы: сделать что-то разумное. “Используйте отступ в 2 пробела, без завершающей точки с запятой, кроме как после imports” — это проверяемая инструкция, которой модель действительно может следовать.
  3. Конфликтующие правила разрешаются произвольно. Если ваш корневой 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 полезны для трёх конкретных вещей:

  1. Переиспользование одних и тех же инструкций между двумя репозиториями без копипасты. Делайте symlink или импортируйте общий файл из ~/shared/team-conventions.md.
  2. Переопределения для отдельного разработчика, которые не должны попадать в commit. @~/.claude/my-project-instructions.md позволяет хранить личные предпочтения в домашней папке, пока все получают командный CLAUDE.md из git.
  3. Мост к 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 “на всякий случай”. Автоматическая память тоже загружается в каждой сессии. Второе: когда автоматическая память накапливает паттерн, который должна знать вся команда, продвигайте его: откройте MEMORY.md, скопируйте запись в CLAUDE.md, и /memory позволит вам удалить оригинал. Продвижение — это момент, когда “Claude заметил это про меня” становится “мы как команда это решили”.

Подробнее о разделении пост о планировании рутин Claude Code рассказывает, что переживает автономный запуск без человека в петле, что является полезным стресс-тестом того, действительно ли ваш CLAUDE.md самодостаточен.

Настройка соблюдения

Когда файл стал коротким и конкретным, можно выжать из него больше соблюдения тремя приёмами, на которых сходятся документация и полевые отчёты:

  1. Используйте акценты сдержанно. Официальная рекомендация — “настраивать инструкции, добавляя акценты (например, IMPORTANT или YOU MUST), чтобы повысить соблюдение.” Сдержанно — ключевое слово. Если всё IMPORTANT, то ничто не важно. Резервируйте акцент для правила, нарушение которого реально сломает сборку или поднимет дежурного.
  2. Сначала глагол, потом ограничение. “Запускайте pnpm typecheck после каждой правки кода в src/” соблюдается надёжнее, чем “Проверка типов должна выполняться регулярно.” Первое — действие; второе — настроение.
  3. Совмещайте правило с режимом отказа. “Не вызывайте 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 -->), не платя стоимостью токенов.

Связанное

Источники

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Назад