Конспект урока «Hooks» из бесплатного курса Anthropic Claude Code in Action. Оригинал урока: https://anthropic.skilljar.com/claude-code-in-action/486933
Это пересказ на русском, а не перевод. Права на курс принадлежат Anthropic.
Написать правило в CLAUDE.md — значит попросить, а не гарантировать. «Всегда форматируй после правки» Клод обычно выполнит. Обычно. Но в длинном прогоне, за которым ты не следишь, «обычно» не годится.
Hook — это детерминированный код, который выполняется в фиксированной точке цикла. Он превращает правило из «Клод обычно слушается» в «Клод не может это пропустить».
За сессию Claude Code выстреливает около тридцати событий. Знать все не нужно — регулярно пригождается небольшая горстка, и она совпадает с моментами, когда логично вмешаться: сессия стартовала, пришёл промпт, вызвали инструмент, ход завершился.
| Событие | Когда срабатывает и зачем |
|---|---|
| PreToolUse | до вызова инструмента. Основной инструмент принуждения — единственный, кто может остановить действие до того, как оно случилось |
| PostToolUse | после успешного вызова. Сюда обычно вешают автоформат и автолинт |
| Stop | когда Клод хочет завершить ход. Можно отказать: «нет, ты ещё не закончил», если условие не выполнено. Для субагентов есть парное SubagentStop |
| PreCompact / PostCompact | до и после сжатия контекста |
| InstructionsLoaded | при загрузке CLAUDE.md или файла правил. Удобно, чтобы проверять, что реально попало в контекст |
| SessionStart | на старте, подготавливает окружение. Есть источник startup, если нужно только на свежих запусках |
Место, где спотыкаются: чтобы вернуть контекст после сжатия, PostCompact не подходит.
Нужен SessionStart с матчером compact — только его вывод реально попадает обратно в
разговор.
Здесь вся сила, потому что можно заблокировать вызов до исполнения. Ответ Клоду — это
JSON в stdout и выход с нулевым кодом. Ключевое поле permissionDecision, у него три
значения:
allow — пропустить вызов;deny — остановить;ask — вернуть решение пользователю.Формально есть и четвёртое, defer, но оно только для неинтерактивных прогонов с -p,
где вызывающий процесс приостанавливает инструмент и возобновляет позже. Пригождается
редко.
Структура ответа:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "...",
"updatedInput": { "command": "..." }
}
}
Обрати внимание на updatedInput: вызов можно не блокировать, а переписать. Именно так
вычищают секрет из bash-команды, при этом позволяя ей выполниться. Подвох: updatedInput
заменяет объект входных данных целиком, так что поля, которые ты не меняешь, надо вернуть
обратно — иначе они потеряются.
Не всякому хуку нужен JSON. Значимых кодов три:
SessionStart, UserPromptSubmit и UserPromptExpansion
он добавляется в контекст — на этом и держится хук, сохраняющий состояние.Главная ловушка — код 1. Выглядит как ошибка, но не блокирует: команда всё равно выполнится. Хочешь остановить — возвращай 2, а не 1.
Ещё пара тонкостей. Код 2 умеет блокировать даже Stop — так Клоду сообщают, что он не
закончил. А PostToolUse срабатывает уже после выполнения инструмента, так что
блокировать там поздно, хотя текст Клоду вернуть всё ещё можно. Некоторые события
блокировку игнорируют вовсе (Notification, SessionStart) — покажут твой stderr и
поедут дальше.
Практический пример. Ставим на инструмент Bash защиту через PreToolUse. Матчер выбирает
инструмент, необязательное условие сужает до конкретной команды.
Очевидный ход — вернуть deny и остановить опасный вызов. Это правильно. Но менее
известный и более интересный ход — вернуть updatedInput и переписать вызов. Так из
команды вырезают секрет, не отказывая ей в исполнении.
На практике: Клоду поручают выполнить команду с боевым на вид секретом. Хук перехватывает
её, видит шаблон sk_live_ и подменяет его заглушкой ещё до запуска.
Команда выполнилась. Работа сделана. Секрет наружу не ушёл. Вот разница между блокировкой и вычисткой — и хук обеспечивает это каждый раз, без исключений.
Ещё один приём, который стоит настроить. При сжатии длинного разговора теряется много
деталей. Хук SessionStart с матчером compact срабатывает сразу после сжатия — пусть он
печатает короткую сводку о файлах, над которыми ты работал. Сводка возвращается в контекст,
и Клод продолжает с того же места, а не с чистого листа.
Хуки превращают правило, которому Клод обычно следует, в правило, которому он следует
всегда. Не ограничивайся автоформатом: закрывай инструменты через PreToolUse, ставь
ворота на завершение хода через Stop, сохраняй состояние через сжатие. Настройка стоит
некоторых усилий, но окупается в первый же раз, когда хук поймает что-то в прогоне, за
которым ты даже не наблюдал.