Ты пишешь в CLAUDE.md: «никогда не трогай package-lock.json». Claude слушается. Почти всегда. Но длинная сессия, сжатый контекст, срочная правка зависимостей — и однажды файл всё-таки меняется. Модель не нарушила правило назло: строчка в памяти для неё контекст, а не приказ.
Если правило должно выполняться всегда, ему место не в памяти, а в хуке. Вот как это устроено и как сделать первый хук, который не подведёт.
Хук — это твоя команда, а не просьба к модели
Хук — это команда, которую Claude Code сам запускает в заданный момент сессии, что бы ни решил Claude. Модель не может его «забыть» или «не заметить»: он стоит на пути у самого инструмента.
Каждый хук привязан к событию. Главные события такие:
| Событие | Когда срабатывает |
|---|---|
SessionStart | при старте сессии |
UserPromptSubmit | когда ты отправил запрос |
PreToolUse | перед каждым вызовом инструмента |
PostToolUse | после успешного вызова инструмента |
Stop | когда Claude закончил отвечать |
Notification | когда Claude Code нужно твоё внимание |
Из них только PreToolUse умеет остановить действие до того, как оно случилось. С него и начнём.
Охранник на PreToolUse
Перед вызовом инструмента Claude Code запускает твой скрипт и подаёт ему на вход JSON с описанием события — например, какой файл собираются править. Дальше всё решает код выхода:
- 0 — всё в порядке, действие идёт дальше;
- 2 — вызов заблокирован, а то, что скрипт напечатал в stderr, уходит Claude как причина.
Claude видит объяснение и меняет план, вместо того чтобы молча упереться в ошибку. Важная деталь: PreToolUse срабатывает до проверки режима разрешений. Поэтому блок держится в любом режиме, даже в Auto.
Простой охранник может выглядеть так — сохрани его в .claude/hooks/protect-files.sh и сделай исполняемым (chmod +x):
#!/bin/bash
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in
*/package-lock.json|*/.env)
echo "Blocked: $file защищён, не правь его" >&2
exit 2 ;;
esac
exit 0
И зарегистрируй в .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-files.sh", "args": [] }
]
}
]
}
}
Что здесь происходит:
matcherвыбирает инструменты:Edit|Write— только правки и запись файлов;${CLAUDE_PROJECT_DIR}подставляет путь к проекту, чтобы хук находился, откуда бы ты ни запустил Claude;- с
"args": []скрипт запускается напрямую, без оболочки, и путь не нужно брать в кавычки.
Внутри сессии команда /hooks покажет, какие хуки подхватились.
Проверь хук без Claude
Хук, который нельзя проверить отдельно, нельзя и доверить. К счастью, проверка занимает одну строку: подай скрипту тот же JSON, который прислал бы Claude Code, и посмотри код выхода.
echo "{\"tool_input\":{\"file_path\":\"$PWD/package-lock.json\"}}" | .claude/hooks/protect-files.sh; echo "exit $?"
Видишь exit 2 — значит, хук сработает и в сессии. Видишь exit 0 на защищённом файле — ищи ошибку в скрипте до того, как она молча сломает тебе каждую сессию.
Три рецепта, которые окупаются сразу
Блокировать — не единственное, что умеют хуки.
Прибраться после правки — PostToolUse. Повесь его на Edit|Write, и после каждой правки файла запустится форматтер или линтер. Целый класс просьб «а теперь поправь отступы» исчезает.
Позвать тебя — Notification. Ты ушёл за чаем, а Claude давно ждёт подтверждения. Хук на Notification покажет системное уведомление или проиграет звук. Такой хук — про тебя, а не про проект: на Mac и Linux уведомления делаются по-разному. Поэтому регистрируй его в своём ~/.claude/settings.json, а не в настройках репозитория.
Проверить в конце — Stop. Stop срабатывает, когда Claude закончил отвечать. Это лучшее место, чтобы одним махом проверить всё рабочее дерево: не изменились ли защищённые файлы, проходят ли быстрые тесты.
Быстрое — в PostToolUse, тяжёлое — в Stop
Хук выполняется на каждом событии. Полный прогон тестов в PostToolUse замедлит каждую правку. Туда клади быстрое: форматирование, проверку одного файла. Тяжёлые проверки ставь на Stop — один раз на ответ.
Граница, о которой забывают
Охранник с matcher: "Edit|Write" видит только инструменты редактирования. Если Claude изменит файл командой оболочки — echo X >> .env, sed -i, rm, — хук ничего не заметит: команды идут через инструмент Bash.
Закрыть эту дыру можно двумя способами:
- отдельный хук с
matcher: "Bash", который разбирает команду; - хук на
Stop, который в конце ответа проверяет рабочее дерево. Ему всё равно, как сделано изменение, — он смотрит на результат.
Для действительно важных файлов разумно иметь оба: охранник на PreToolUse останавливает очевидную правку сразу, а проверка на Stop ловит всё остальное.
Хуки — это код с твоими правами
Хуки выполняются с правами твоего пользователя: в интерактивной сессии — после того как ты доверил папку, а в claude -p — без всякого вопроса. Прежде чем доверять чужому репозиторию или запускать в нём Claude из скрипта, открой его .claude/settings.json и посмотри, какие хуки там прописаны.
Частые вопросы
Чем хук отличается от правила в CLAUDE.md?
CLAUDE.md — это контекст, который модель учитывает, но может упустить. Хук — команда, которую Claude Code запускает сам на заданном событии, всегда. Всё, что должно выполняться без исключений, оформляй хуком.
Как хук PreToolUse блокирует действие?
Скрипт хука завершается с кодом выхода 2. Вызов инструмента отменяется, а текст из stderr уходит Claude как причина. Код 0 пропускает действие дальше.
Работает ли блокировка в режиме Auto?
Да. PreToolUse срабатывает до проверки режима разрешений, поэтому блок держится в любом режиме.
Почему хук на Edit|Write не заметил изменение .env?
Скорее всего, файл поменяли командой оболочки, например echo X >> .env. Такие команды идут через инструмент Bash. Для них нужен отдельный хук с matcher Bash или хук на Stop, который проверяет рабочее дерево.
Где потренироваться
Второй уровень курса «Claude Code: уровень профи» начинается именно с этого: урок «Хуки: правило, которое нельзя забыть» и урок «Рецепты хуков», оба с тренажёром в браузере. Если хочешь начать с самых основ, загляни в наш ранний разбор «Hooks в Claude Code», а все события и флаги в одном месте собраны в шпаргалке по Claude Code.