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 понимает его структуру и может извлекать правило из любого раздела.
# Урок: [название ошибки]
**Date:** 2026-05-10
**Severity:** high | medium | low
**Контекст:** [где возникло — модуль, фича, команда]
## Что произошло
[конкретное описание ошибки — что Claude сделал неправильно]
## Почему это проблема
[последствия — безопасность, производительность, архитектурный долг]
## Правило
[формулировка для
CLAUDE.md — одно предложение, активный залог]
## Антипаттерн (не делать)
```php
// НЕ ТАК
```
## Правильный паттерн
```php
// ТАК
```
## Верификация
[как проверить что правило соблюдается в будущем]
Пример 1: N+1 в Laravel
# Урок: 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
# Урок: Бизнес-логика и запросы прямо в 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-ключи в тесте
# Урок: Реальный 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-и делают процесс проактивным: скрипт сам предлагает создать правило когда видит ошибку, и сам блокирует повторение известных нарушений.
Запускается после каждого вызова 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 — не блокируем работу
Запускается перед каждым 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.
## Обученные правила (из ошибок)
@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