03 / 06 · Продвинутый

🧠 Как учить Claude на своих ошибках

Claude Code сбрасывает память между сессиями. Каждая новая сессия начинается с чистого листа. Единственный способ «обучить» его — зафиксировать правило в CLAUDE.md или в файлах спецификаций, которые он прочитает при старте. Эта страница — система превращения каждой ошибки в постоянное правило.

⚠️
Главный принцип Ошибка без зафиксированного правила — это ошибка, которая повторится. Запись правила в CLAUDE.md занимает 30 секунд. Исправление повторной ошибки — 30 минут и испорченное настроение.

🔄 Система capture → rule

Каждая ошибка проходит через 5-шаговый цикл. Без этого цикла знание теряется после закрытия терминала.

💥
Ошибка Claude
Неверный паттерн, забытый контекст, повторяющийся баг
🔍
Анализ причины
Почему это произошло? Отсутствующий контекст или неверный паттерн?
✍️
Формулировка правила
«Запрещено X. Правильно: Y.» — конкретно, однозначно
📁
Куда записать
CLAUDE.md, ADR, docs/rules/ — в зависимости от типа
Верификация
В следующей сессии Claude соблюдает правило?

4 типа ошибок и куда их фиксировать

Тип ошибки Пример Куда записать Приоритет
Неверный паттерн кода Eloquent-запрос в Controller, $request->all() Раздел «Запрещённые паттерны» в CLAUDE.md HIGH
Неверная архитектура Создал монолит вместо сервисов, нарушил слои ADR в docs/adr/ HIGH
Забытый бизнес-контекст Не знает что прод на PostgreSQL 16, а не MySQL Раздел «Проект» и «Стэк» в CLAUDE.md MEDIUM
Повторяющийся технический баг N+1 в конкретном модуле, Race condition в очереди docs/rules/YYYY-MM-DD-bug-name.md MEDIUM
💡
Лимит CLAUDE.md — 150 строк CLAUDE.md должен оставаться коротким — Сlaude обрезает его при превышении ~25 KB / 200 строк без предупреждения. Для длинных правил используй @docs/rules/ — файл-ссылки загружаются при старте сессии через @include синтаксис.

📄 Шаблон файла правил (Rules file)

Каждое правило живёт в отдельном файле docs/rules/YYYY-MM-DD-lesson-name.md. Этот формат стандартизирован — Claude понимает его структуру и может извлекать правило из любого раздела.

docs/rules/YYYY-MM-DD-lesson-name.md
# Урок: [название ошибки] **Date:** 2026-05-10 **Severity:** high | medium | low **Контекст:** [где возникло — модуль, фича, команда] ## Что произошло [конкретное описание ошибки — что Claude сделал неправильно] ## Почему это проблема [последствия — безопасность, производительность, архитектурный долг] ## Правило [формулировка для CLAUDE.md — одно предложение, активный залог] ## Антипаттерн (не делать) ```php // НЕ ТАК ``` ## Правильный паттерн ```php // ТАК ``` ## Верификация [как проверить что правило соблюдается в будущем]

Пример 1: N+1 в Laravel

HIGH docs/rules/2026-05-01-no-n-plus-one-queries.md
# Урок: N+1 запросы в Eloquent без eager loading **Date:** 2026-05-01 **Severity:** high **Контекст:** OrderController@index — список заказов с пользователями ## Что произошло Claude сгенерировал метод index() который итерирует по заказам и вызывает $order->user->name внутри цикла. При 100 заказах — 101 SQL-запрос. ## Почему это проблема На проде с 10 000+ заказов страница падала по timeout. Debugbar показал 8 742 запроса на одну страницу. ## Правило Запрещены Eloquent-запросы внутри foreach без предварительного with(). Всегда использовать eager loading для связей. ## Антипаттерн (не делать) ```php // ❌ N+1: каждый $order->user делает отдельный SELECT $orders = Order::all(); foreach ($orders as $order) { echo $order->user->name; // N запросов для N заказов } ``` ## Правильный паттерн ```php // ✅ Eager loading: 2 запроса всего $orders = Order::with(['user', 'items'])->paginate(50); foreach ($orders as $order) { echo $order->user->name; // уже загружено } ``` ## Верификация Laravel Debugbar: убедиться что queries count ≤ 5 на страницу списка.

Пример 2: Eloquent в Controller вместо Service

HIGH docs/rules/2026-05-03-no-eloquent-in-controller.md
# Урок: Бизнес-логика и запросы прямо в Controller **Date:** 2026-05-03 **Severity:** high **Контекст:** UserController — регистрация пользователя ## Что произошло Claude написал 80 строк бизнес-логики в store() метода Controller: User::create(), email-верификация, создание Profile, отправка уведомлений. Всё в одном методе без Service-слоя. ## Почему это проблема Нарушает Single Responsibility. Невозможно протестировать изолированно. Дублирование при появлении API-эндпоинта и консольной команды. ## Правило Controller содержит только: валидацию входных данных, вызов Service, формирование ответа. Все Eloquent-запросы и бизнес-логика — в Service. ## Антипаттерн (не делать) ```php // ❌ Controller знает про БД и бизнес-логику class UserController { public function store(Request $request) { $user = User::create($request->validated()); $user->profile()->create(['bio' => '']); Mail::to($user)->send(new WelcomeMail($user)); // ещё 40 строк... } } ``` ## Правильный паттерн ```php // ✅ Controller — тонкий роутер class UserController { public function store(StoreUserRequest $request, UserService $service) { $user = $service->register($request->validated()); return UserResource::make($user); } } // Вся логика — в Service class UserService { public function register(array $data): User { /* ... */ } } ```

Пример 3: API-ключи в тесте

HIGH docs/rules/2026-05-10-never-store-api-keys.md
# Урок: Реальный API-ключ зафиксирован в тест-файле **Date:** 2026-05-10 **Severity:** high **Контекст:** PaymentServiceTest — интеграционный тест со Stripe ## Что произошло Claude вставил реальный Stripe secret key прямо в тест-файл чтобы «быстро проверить». Ключ попал в git history. Stripe прислал уведомление через 4 минуты. ## Почему это проблема Secret в git history — навсегда. Даже после удаления файла ключ остаётся в истории коммитов. Требует ротации ключа, аудита транзакций. ## Правило Запрещены любые реальные секреты в коде. В тестах использовать env('STRIPE_TEST_KEY') или фиктивные ключи вида sk_test_fake_xxx. ## Антипаттерн (не делать) ```php // ❌ Никогда — реальный ключ в коде $stripe = new StripeClient('sk_live_abc123realkey...'); ``` ## Правильный паттерн ```php // ✅ Всегда через env() $stripe = new StripeClient(env('STRIPE_SECRET_KEY')); // В .env.testing: // STRIPE_SECRET_KEY=sk_test_fake_for_testing_only ``` ## Верификация Hook secret-scanner блокирует коммиты с паттернами sk_live_*, Bearer eyJ*.

🪝 Hooks для автоматического захвата

Ручной ритуал работает, но Hook-и делают процесс проактивным: скрипт сам предлагает создать правило когда видит ошибку, и сам блокирует повторение известных нарушений.

📥 capture-lesson.cjs
PostToolUse
Запускается после каждого вызова Bash. Анализирует вывод команды. Если видит слова error, failed, exception — создаёт черновик rule-файла в docs/rules/ для заполнения вручную.

Регистрация в settings.json

{ "hooks": { "PostToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "node .claude/hooks/capture-lesson.cjs" } ] } ] } }

Скрипт capture-lesson.cjs

// .claude/hooks/capture-lesson.cjs // PostToolUse: Bash — автозахват ошибок в rule-файлы 'use strict'; const fs = require('fs'); const path = require('path'); // Читаем stdin (Claude передаёт JSON с результатом инструмента) const raw = fs.readFileSync(0, 'utf8'); let input; try { input = JSON.parse(raw); } catch { process.exit(0); } const output = (input.tool_response?.output || input.tool_response || '').toString(); const cmd = (input.tool_input?.command || '').toString(); // Паттерны признаков ошибок const errorPatterns = [ /error/i, /failed/i, /exception/i, /fatal/i, /ошибка/i, /SQLSTATE/, /undefined variable/i, /Call to undefined/i, ]; const hasError = errorPatterns.some(p => p.test(output)); if (!hasError) { process.exit(0); } // Определяем директорию проекта и создаём rule-файл const rulesDir = path.join(process.env.PROJECT_ROOT || process.cwd(), 'docs/rules'); if (!fs.existsSync(rulesDir)) { fs.mkdirSync(rulesDir, { recursive: true }); } const date = new Date().toISOString().split('T')[0]; const fname = `${date}-auto-captured-${Date.now() % 1000}.md`; const fpath = path.join(rulesDir, fname); const excerpt = output.slice(0, 600).replace(/`/g, "'"); const content = [ `# Автозахват: ${date}`, '', `**Команда:** \`${cmd.slice(0, 120)}\``, '', '## Что произошло', '```', excerpt, '```', '', '## Почему это проблема', '[Заполни: последствия для проекта]', '', '## Правило', '[Заполни: одно предложение — запрещено X, правильно Y]', '', '## Антипаттерн', '```\n[код который нельзя]\n```', '', '## Правильный паттерн', '```\n[правильный код]\n```', ].join('\n'); fs.writeFileSync(fpath, content, 'utf8'); // Уведомляем Claude (через stderr — он это читает) process.stderr.write( `[LESSON CAPTURED] Обнаружена ошибка. Черновик правила создан:\n` + ` ${fpath}\n` + `Заполни разделы "Правило" и "Паттерны", затем добавь ссылку в CLAUDE.md\n` ); process.exit(0); // exit 0 — не блокируем работу
🛡️ check-rules.cjs
PreToolUse
Запускается перед каждым Edit/Write. Сканирует все файлы в docs/rules/ и проверяет, не нарушает ли предлагаемый Claude код известные правила. При совпадении — блокирует и выводит причину.

Регистрация в settings.json

{ "hooks": { "PreToolUse": [ { "matcher": "Edit|Write|MultiEdit", "hooks": [ { "type": "command", "command": "node .claude/hooks/check-rules.cjs" } ] } ] } }

Скрипт check-rules.cjs

// .claude/hooks/check-rules.cjs // PreToolUse: Edit|Write|MultiEdit — проверка против известных правил 'use strict'; const fs = require('fs'); const path = require('path'); const raw = fs.readFileSync(0, 'utf8'); let input; try { input = JSON.parse(raw); } catch { process.exit(0); } const newContent = ( input.tool_input?.new_string || input.tool_input?.content || '' ).toLowerCase(); if (!newContent) { process.exit(0); } // Загружаем все правила из docs/rules/ const rulesDir = path.join(process.cwd(), 'docs/rules'); if (!fs.existsSync(rulesDir)) { process.exit(0); } // Встроенные жёсткие правила (регулярки → объяснение) const hardRules = [ { pattern: /\$request->all\(\)/, reason: 'Mass assignment: $request->all() без $fillable — уязвимость (docs/rules/2026-05-10-never-store-api-keys.md)' }, { pattern: /sk_live_[a-z0-9]/i, reason: 'Реальный Stripe ключ в коде. Используй env("STRIPE_SECRET_KEY")' }, { pattern: /bearer [a-z0-9_\-\.]{20,}/i, reason: 'Возможный токен в коде. Используй env() или config()' }, ]; for (const rule of hardRules) { if (rule.pattern.test(newContent)) { process.stderr.write(`[RULE VIOLATION] ${rule.reason}\n`); process.exit(2); // BLOCK } } // Парсим заголовки rule-файлов и ищем совпадение по ключевым словам const ruleFiles = fs.readdirSync(rulesDir).filter(f => f.endsWith('.md')); for (const rf of ruleFiles) { const body = fs.readFileSync(path.join(rulesDir, rf), 'utf8'); const ruleMatch = body.match(/## Антипаттерн[^\n]*\n```[^\n]*\n([\s\S]*?)```/); if (!ruleMatch) continue; const antipattern = ruleMatch[1].toLowerCase().trim(); const keywords = antipattern.split(/\W+/).filter(w => w.length > 5); const hits = keywords.filter(kw => newContent.includes(kw)); if (hits.length >= 3) { const title = (body.match(/^# (.+)/m) || [])[1] || rf; process.stderr.write( `[RULE WARNING] Возможное нарушение правила: "${title}"\n` + `Файл: docs/rules/${rf}\n` ); // exit 1 — предупреждение, не блокируем process.exit(1); } } process.exit(0);
Exit codes напоминание: exit 0 — OK. exit 1 — предупреждение в stderr, Claude видит но продолжает. exit 2 — полный блок, Claude отменяет действие и читает stderr как объяснение.

📋 Интеграция с CLAUDE.md

Правила в docs/rules/ работают только если Claude их читает. Для этого используется синтаксис @file — Claude загружает файл по ссылке при старте сессии, как будто его содержимое написано прямо в CLAUDE.md.

CLAUDE.md — раздел "Обученные правила"
## Обученные правила (из ошибок) @docs/rules/2026-05-01-no-n-plus-one-queries.md @docs/rules/2026-05-03-no-eloquent-in-controller.md @docs/rules/2026-05-10-never-store-api-keys.md ## Запрещённые паттерны (краткие — из rules-файлов) - Запрещены Eloquent-запросы без with() при связях внутри цикла - Запрещён $request->all() без явного $fillable - Запрещён any в TypeScript без явного обоснования - Запрещены секреты и ключи в коде — только через env()
⚠️
@file синтаксис — ограничения @file работает только если файл существует. При переименовании или удалении ссылка тихо игнорируется. Всегда держи краткую версию правила прямо в CLAUDE.md как fallback — файл rules для деталей, CLAUDE.md для памяти.

Рекомендуемая структура папок

project/ ├── CLAUDE.md # корень: <150 строк + @links ├── .claude/ │ ├── settings.json # hooks регистрация │ └── hooks/ │ ├── capture-lesson.cjs # PostToolUse: автозахват │ ├── check-rules.cjs # PreToolUse: проверка правил │ └── dangerous-command-guard.cjs # PreToolUse: защита от DDL └── docs/ ├── rules/ # обученные правила (rule-файлы) │ ├── 2026-05-01-no-n-plus-one-queries.md │ ├── 2026-05-03-no-eloquent-in-controller.md │ └── 2026-05-10-never-store-api-keys.md └── adr/ # Architecture Decision Records └── 001-service-layer.md

📅 Ежедневный ритуал

5 минут в конце рабочего дня — и следующая сессия начнётся умнее. Этот ритуал особенно важен после длинных сессий с много правок.

Конец рабочего дня

  • Открыть git diff HEAD~1 или git log --oneline -10 — просмотреть что изменилось за сессию
  • Найти места где Claude ошибся или пришлось его поправить вручную
  • Для каждой правки: «Почему он это сделал? Какого контекста ему не хватало?»
  • Сформулировать правило в одном предложении («Запрещено X. Правильно: Y.»)
  • Добавить в CLAUDE.md (кратко) и/или создать docs/rules/ файл (подробно)
  • Проверить что hook capture-lesson.cjs создал черновики — заполнить их

Начало следующей сессии

  • Запустить claude — Claude автоматически прочитает CLAUDE.md и @docs/rules/
  • Дать первую задачу, которая похожа на вчерашнюю ошибку
  • Проверить: Claude соблюдает правило или снова нарушает?
  • Если нарушает — переформулировать правило более жёстко или добавить пример
  • Если соблюдает — правило усвоено, можно переходить к задаче
  • Раз в неделю: аудит docs/rules/ — удалить устаревшие, объединить дубли
💡
Ритуал работает даже без hooks Если нет времени настраивать hooks — просто файл. 3 строки в CLAUDE.md в конце дня дают 80% эффекта. Hooks — это 20% дополнительной автоматизации поверх дисциплины.

💬 Встроенный промпт для извлечения правил

Используй этот промпт в конце сессии — попроси Claude самого проанализировать где он ошибся и предложить правила. Это занимает 2–3 минуты и даёт готовые формулировки.

Проанализируй нашу сессию и выдели:

1. Все случаи когда ты допустил ошибку или я тебя поправил
2. Все случаи когда твоя первая реализация оказалась неверной
3. Паттерны и архитектурные решения которые специфичны для этого проекта

Для каждого случая предложи правило в точном формате:
«Запрещено: [антипаттерн]. Правильно: [паттерн]. Причина: [1 предложение].»

Затем предложи итоговый блок для добавления в CLAUDE.md раздел «Запрещённые паттерны».

Пример вывода Claude на этот промпт

## Анализ сессии 2026-05-10 **Случай 1:** UserController.store() — я написал запросы прямо в контроллере. Вы поправили, вынесли в UserService. Правило: Запрещено писать Eloquent-запросы в Controller. Правильно: Вся бизнес-логика через Service-классы. Причина: SRP и тестируемость. **Случай 2:** OrderResource — я вернул все поля включая internal_notes. Вы убрали поле из API-ответа. Правило: Запрещено возвращать служебные поля в API без явного allow-list. Правильно: Resource-классы должны явно перечислять разрешённые поля. Причина: утечка данных. **Блок для CLAUDE.md:** ``` ## Запрещённые паттерны - Запрещены Eloquent-запросы в Controller — только через Service - Запрещён возврат служебных полей в API без явного allow-list в Resource ```
Совет: запускай промпт перед /clear Перед очисткой контекста (/clear) или завершением сессии — сначала запусти промпт для извлечения правил. После /clear Claude забудет сессию, но правила останутся в файлах.

🦊 RuFlo Memory для автозахвата

Если в проекте используется MCP-сервер RuFlo, обучающие моменты можно хранить в персистентной векторной памяти — они доступны между сессиями и поддерживают семантический поиск.

Сохранение урока в RuFlo Memory

// Вызов через MCP-инструмент (можно попросить Claude выполнить) mcp__ruflo__memory_store({ key: "lesson:2026-05-10:no-mass-assignment", content: "Запрещён $request->all() без $fillable. " + "Причина: Mass Assignment уязвимость. " + "Правильно: $request->validated() + explicit $fillable в модели.", tags: ["laravel", "security", "rule", "mass-assignment"] })

Поиск правил при старте задачи

// Поиск по тегам и семантике перед началом работы mcp__ruflo__memory_search({ query: "laravel security rules", tags: ["rule"], limit: 10 })

Когда использовать RuFlo Memory vs docs/rules/

Критерийdocs/rules/ файлыRuFlo Memory
Работает без MCP ✅ да — просто markdown ❌ нет — требует ruflo сервер
Версионирование в git ✅ автоматически ❌ отдельный storage
Семантический поиск ❌ только grep ✅ векторный поиск
Доступ между проектами ❌ только в папке проекта ✅ глобально через MCP
Читается Claude автоматически ✅ через @file в CLAUDE.md ❌ нужен явный вызов memory_search
💡
Рекомендация: оба метода вместе docs/rules/ — основной механизм (надёжный, в git, автозагрузка). RuFlo Memory — дополнение для кросс-проектного поиска и исторических паттернов. Не выбирать один — использовать оба.

📊 Частые ошибки и готовые правила

Скопируй нужные правила прямо в раздел «Запрещённые паттерны» своего CLAUDE.md. Это стартовый набор для Laravel + Vue + Python-проектов.

Ошибка Claude Готовое правило для CLAUDE.md Стек
Eloquent-запрос в Controller Запрещены Eloquent-запросы в Controller — только через Service/Repository Laravel
$request->all() без $fillable Запрещён $request->all() — использовать $request->validated() Laravel
N+1: Eloquent без with() Запрещены Eloquent-запросы внутри foreach без предварительного eager loading Laravel
any в TypeScript Запрещён тип any в TypeScript без явного // eslint-disable-next-line с обоснованием Vue/TS
Options API вместо Composition API Запрещён Options API — проект использует Composition API + <script setup lang="ts"> Vue 3
jQuery в Vue-проекте Запрещён jQuery — использовать Vue реактивность и встроенные директивы Vue 3
Бизнес-логика в API-роутере FastAPI Запрещена бизнес-логика в роутерах FastAPI — только в Service-слое (services/) Python
Сырой SQL в Django без ORM Запрещён connection.execute(raw_sql) без явного обоснования — использовать ORM Django
Секреты в коде или тестах Запрещены любые ключи, токены, пароли в коде — только через env() или os.getenv() Все
git --force без подтверждения Запрещён git push --force — только с явного подтверждения пользователя Git
migrate:fresh на проде Запрещён artisan migrate:fresh и db:wipe в любом окружении кроме local Laravel
JWT токен в логах Запрещён вывод JWT и Bearer токенов в логах — маскировать через *** Все
Docker volume rm без проверки Запрещён docker volume rm без явного docker volume inspect перед этим Docker
ETL: UPDATE без WHERE Запрещён UPDATE table SET без WHERE — всегда требовать явное условие SQL/ETL
print() в продакшен-коде Python Запрещён print() в prod-коде — только через logging.getLogger() Python

🎯 Итог: минимальный стартовый набор

Если нет времени настроить всё сразу — начни с этих трёх шагов. Они дают 80% пользы:

1 Добавь раздел «Запрещённые паттерны»

Возьми 5–10 правил из таблицы выше, адаптируй под свой проект, вставь в CLAUDE.md. Это займёт 10 минут и сразу даст результат.

2 Создай папку docs/rules/

После каждой сессии где Claude ошибся — создавай один файл по шаблону. Добавляй ссылку @docs/rules/файл.md в CLAUDE.md.

3 Промпт для извлечения в конце сессии

Перед /clear — запусти промпт из секции 6. Claude сам предложит формулировки. Это дисциплина, не техника.

+ Затем: подключи hooks

Когда шаги 1–3 войдут в привычку — добавь capture-lesson.cjs и check-rules.cjs для автоматизации. Это усилит дисциплину, но не заменит её.

Продвинутые темы Claude Code

📋 Правила CLAUDE.md → 🪝 Hooks → ✅ Чек-листы →