Главный антипаттерн: правило в CLAUDE.md
Самая частая ошибка, которую я вижу у людей, начинающих с Claude Code, — они открывают CLAUDE.md и пишут туда что-то вроде «всегда после редактирования .tsx файла запускай prettier» или «каждый раз перед git push проверяй .env на секреты».
Это не работает. Точнее, работает через раз. Claude — это языковая модель, она читает инструкции и может их выполнить, но может и забыть. Особенно в длинных сессиях, где контекста много, а правило одно из тридцати. Через час работы я ловлю себя на том, что prettier не запустился, потому что Claude был занят основной задачей и не вспомнил.
CLAUDE.md — это знания и стиль: как со мной разговаривать, что за проект, какой стек. Если ты хочешь, чтобы что-то выполнялось железно, каждый раз — это работа не для языковой модели, а для harness'а. Для этого есть hooks.
В чём принципиальная разница
Hook — это shell-команда, которую Claude Code запускает автоматически на определённое событие. Не «Claude решает запустить», а harness запускает сам, без участия модели.
Это меняет надёжность на порядок:
- Правило в CLAUDE.md: вероятность выполнения ~80-95% в зависимости от контекста
- Hook: 100%, пока не сломается shell-команда
Hooks живут в ~/.claude/settings.json (или в .claude/settings.json проекта) в разделе hooks. Каждый hook привязан к событию и опционально к матчеру (например, только когда инструмент = Bash, или только когда файл = .py).
Четыре события
В Claude Code есть несколько событий, на которые можно повесить hook. Я использую четыре основных.
PreToolUse — перед инструментом
Срабатывает до того, как Claude вызвал инструмент. Полезно для предупреждений и валидации.
Пример из моего setup — security warning перед опасной bash-командой:
{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "... case \"$CMD\" in *'rm -rf'*|*'git reset --hard'*|*'git push --force'*) echo '⚠️ ВНИМАНИЕ: Деструктивная операция!' ;; esac"
}]
}
Когда модель собирается выполнить rm -rf, git reset --hard или git push --force — в её контекст падает предупреждение до выполнения. Дальше модель видит warning и сама решает: продолжать или переспросить.
Похожий hook у меня стоит на Write и Edit: если файл .env, CLAUDE.md, schema.prisma или docker-compose.yml — выводится напоминание «это конфиг, проверь изменения».
PostToolUse — после инструмента
Срабатывает сразу после успешного выполнения. Это место для форматирования и побочных эффектов.
Мой основной hook сюда — автоформатирование:
{
"matcher": "Edit",
"hooks": [{
"type": "command",
"command": "... case \"$FILE\" in *.py) black --quiet \"$FILE\" ;; *.js|*.ts|*.jsx|*.tsx) npx prettier --write \"$FILE\" ;; esac"
}]
}
После любого Edit файл прогоняется через black (если Python) или prettier (если JS/TS). Я никогда не думаю про форматирование — оно происходит само, и diff в git всегда чистый.
Второй пример — конвертация .excalidraw файлов:
{
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "if echo \"$FILE\" | grep -qE '\\.excalidraw$'; then bash $HOME/.claude/scripts/excalidraw-to-clipboard.sh \"$FILE\"; fi"
}]
}
Как только Claude записал .excalidraw файл, скрипт автоматически рендерит его в PNG и кладёт в буфер обмена. Я просто переключаюсь в Telegram или документ и вставляю — без ручных шагов.
Stop — когда Claude закончил отвечать
Срабатывает в момент, когда модель завершила свой ответ и собирается отдать управление пользователю. Полезно для уведомлений и подведения итогов.
Сюда можно повесить, например:
- Отправку Telegram-уведомления «Claude закончил, можно смотреть»
- Запуск дашборда с метриками проекта
- Сохранение состояния сессии в MEMORY.md
В моём setup на Stop пока ничего тяжёлого нет — Stop у меня используется системой Buddy Evolution (плагин), но идея та же: автоматизировать «что делать каждый раз, когда AI закончил работу».
UserPromptSubmit — когда я отправил сообщение
Срабатывает до того, как мой промт уйдёт в модель. Это место для проверок и логирования:
- Залогировать все мои запросы в файл (для последующего анализа, какие задачи я повторяю)
- Сканировать промт на чувствительные данные (если случайно вставил пароль или токен — предупредить)
- Подгрузить дополнительный контекст автоматически (например, свежий
git status)
Я этим почти не пользуюсь, но видел кейсы, где люди вешают сюда фильтр на чувствительные слова — чтобы AI не получал токены из буфера обмена случайно.
Три моих рабочих hook'а
Соберу в одно место то, что у меня реально стоит и спасает каждый день.
1. Форматирование на PostToolUse: Edit
Любой .py → black. Любой .js/.ts/.jsx/.tsx → prettier. Происходит автоматом после каждого редактирования. Я не помню, когда последний раз думал про форматирование вручную — но и не вижу «дрейфа стиля» в коммитах.
2. Security warning на PreToolUse: Bash
Деструктивные команды (rm -rf, git reset --hard, git push --force, DROP TABLE, docker rm) дают warning в контекст. Это не блокировка — это напоминание. Полезно, потому что в длинной сессии модель может «забыть» правило из CLAUDE.md, но warning в её контексте прямо перед выполнением — это не пропустить.
Аналогично — на запись в .env, CLAUDE.md, schema.prisma, docker-compose.yml. Это файлы, которые трогаются редко, но если трогаются — точно стоит затормозить и проверить.
3. Excalidraw → PNG → буфер обмена
Когда я генерирую диаграмму через excalidraw-visualizer, она ложится как .excalidraw (JSON). Я не хочу руками открывать excalidraw.com, импортировать, экспортировать в PNG, копировать. Поэтому hook делает это сам: записал файл → скрипт сконвертил в PNG → положил в буфер обмена → я вставляю куда нужно.
Это избавляет от 5 кликов на каждой диаграмме. За месяц экономит десятки минут и нервы.
Когда hook хуже, чем skill или agent
Hook — это автоматизация. Но не любая задача в неё хорошо ложится. Три красных флага:
1. Если задача требует решения
Hook — это shell-скрипт. Он не «думает». Если нужно решить «когда стоит запустить prettier, а когда не стоит», это уже не работа для hook — это работа для AI. Hook должен делать одно простое действие без условий уровня «а если бизнес-логика говорит ещё что-то».
Если шаги начинаются с «иногда нужно X, а иногда Y, в зависимости от...» — это skill, не hook.
2. Если задача медленная
Hook вешается синхронно (в большинстве событий). Если он занимает 30 секунд — все 30 секунд Claude Code висит. Я ставлю hook'и на действия в пределах 1-3 секунд: форматирование, проверка regex, копирование файла. Что-то тяжёлое (рендер видео, обращение к LLM) — это уже background job или skill.
В settings.json можно указать timeout, но если ты хочешь, чтобы hook не блокировал — это сигнал, что задача не для hook.
3. Если задача требует контекста разговора
Hook видит только то, что ему передал harness: имя инструмента, параметры, иногда содержимое файла. Он не знает что ты обсуждал с Claude минуту назад. Если для действия нужен контекст разговора («запомнить что мы решили на прошлом шаге») — это subagent или skill, не hook.
Простое правило: hook — это «всегда X на событие Y». Если в формулировке появляются «если», «когда», «в зависимости от» — это не hook.
Как добавить hook
Два пути.
Через skill /update-config — у меня есть skill, который умеет добавлять hooks в settings.json через диалог. Я говорю: «повесь hook на PostToolUse Edit, форматирование .py через black» — skill сам формирует JSON и добавляет в правильное место. Это удобно, когда не хочется лезть в файл руками.
Вручную в settings.json — открыть ~/.claude/settings.json (или .claude/settings.json проекта), найти раздел hooks, добавить новый объект:
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "echo 'edit done' >> /tmp/edits.log"
}
]
}
]
}
После сохранения hook подхватывается в следующей сессии. Старые сессии не перезагружают конфиг.
Что сделать прямо сейчас
- Открой свой
~/.claude/settings.json. Если там нет разделаhooks— это твой первый шаг. - Поймай одну рутину, которую делаешь руками после Claude — форматирование, копирование, уведомление. Заверни в hook.
- Не клади в hook ничего, что требует решения. Только «всегда X».
Через неделю работы с hooks начинаешь замечать, как много мелких ручных действий исчезает из жизни. И в CLAUDE.md больше не пишутся бесполезные «каждый раз делай Y» — все автоматизации живут там, где им положено.