Событий жизненного цикла у hooks много - в документации их двадцать одно, - и держать их в голове списком бессмысленно. Полезнее видеть группы: старт и конец сессии и открытие рабочей области, вызовы инструментов до и после, старт и остановка субагентов, выполнение команд оболочки и обращения к внешним серверам, чтение и правка файлов, отправка запроса и сжатие контекста, ответы и рассуждения агента, а также события подсказок в редакторе. Группировка отвечает не на вопрос, что бывает, а на вопрос, в какой момент вы можете вмешаться.
Группы нужны не для красоты, а для выбора точки подключения. Проверка команды должна стоять до её выполнения, а не после; форматирование - после правки файла; аудит начала работы - на старте сессии; контроль того, что уходит в запрос, - перед его отправкой. Пары до и после существуют именно поэтому: событие до даёт право запретить, событие после - только право отреагировать. Ошибка в выборе даёт обработчик, который срабатывает исправно и при этом бесполезен, потому что момент уже прошёл.
Полезно один раз свести группы событий в таблицу. Ниже такая карта. Она же напоминает про важное ограничение: в облачных агентах поддерживается не весь набор, а пользовательских hooks там нет вовсе. Значит, политика, построенная только на пользовательском уровне, в облаке просто не действует, и это надо учитывать при переносе процесса с локальной машины в автономный запуск.
| Группа | События |
|---|---|
| Сессия | sessionStart, sessionEnd, workspaceOpen |
| Инструменты | preToolUse, postToolUse, postToolUseFailure |
| Субагенты | subagentStart, subagentStop |
| Оболочка и внешние серверы | before/afterShellExecution, before/afterMCPExecution |
| Файлы | beforeReadFile, afterFileEdit |
| Запрос и контекст | beforeSubmitPrompt, preCompact |
| Вывод агента | afterAgentResponse, afterAgentThought, stop |
| Подсказки в редакторе | beforeTabFileRead, afterTabFileEdit |
У каждого обработчика есть параметры, которые стоит задавать осознанно. Команда и её тип, таймаут, ограничение на число повторных срабатываний, режим отказа и фильтр, сужающий область применения. Фильтр экономит больше, чем кажется: обработчик, подключённый к частому событию без сужения, платит своим временем на каждом срабатывании. Ограничение повторов защищает от петли, когда обработчик своим действием снова запускает то же событие: значение по умолчанию невелико, и это разумно - лучше упереться в предел, чем крутиться бесконечно.
Обработчики бывают двух видов, и разница между ними та же, что во всей книге. Команда исполняется одинаково всегда: она получает событие на вход и возвращает решение. Обработчик на основе запроса к модели гибче, потому что понимает смысл, но его решение вероятностно и может отличаться на одинаковом входе. Для строгой политики выбирают команду; обработчик на модели уместен там, где нужна оценка, а не запрет, и где ошибка не критична.
Как это выглядит на конкретной проверке. Команда, стирающая каталог или отправляющая изменения в основную ветку, должна не выполняться, а не разбираться постфактум. Обработчик подключают к событию перед выполнением команды оболочки, он читает строку, сверяет её со своим списком и возвращает отказ. Дальше решает режим отказа, и вот здесь чаще всего ошибаются: по умолчанию сбой самого обработчика не блокирует действие, то есть упавший скрипт молча пропускает то, что должен был остановить. Для проверки, ради которой всё затевалось, это неверно, и режим переключают так, чтобы отказ обработчика означал запрет.
Отдельная тема - совместимость с hooks других агентных инструментов. Cursor умеет отображать часть чужих событий на свои, но соответствие неполное. При переносе это ловушка: отображается лишь часть чужих событий, поэтому обработчик может просто не подключиться, а значения по умолчанию отличаются: предел повторов равен пяти у обработчиков Cursor и не ограничен у чужих. Правильный путь при миграции - проверять каждое событие отдельно: что приходит на вход, что ожидается на выходе и действительно ли блокировка работает.
У hooks есть граница применимости, и она проходит там же, где граница между правилом и механизмом. Правило меняет поведение модели, оставаясь рекомендацией; обработчик исполняется вне модели и потому даёт запрет. Где ошибка стоит данных или денег, нужен обработчик; где речь про стиль и предпочтение, хватает правила. Признак, по которому понимают, что с обработчиками перестарались, тоже есть: работа заметно замедлилась, а часть срабатываний упирается в таймаут. Каждое событие с подключённой командой стоит времени, и на частых событиях эта цена суммируется.
Инженерный вывод простой: hooks дают детерминированность, но требуют инженерии. Точка подключения выбирается по смыслу, таймаут и предел повторов - по здравому смыслу, режим отказа - по цене ошибки, а совместимость проверяется, а не предполагается. Обработчик, написанный за пять минут и не протестированный, чаще мешает, чем защищает.
Типичные провалы предсказуемы. Подключиться не к тому событию и получить проверку после факта. Забыть про предел повторов и устроить петлю. Построить политику на пользовательском уровне и удивиться, что в облаке она не действует. И перенести чужие обработчики по названию событий, не проверив формат и блокирующее поведение.