В сессии Claude Code срабатывает 30 событий хуков. Ответить могут только 3.

Полный список событий хуков в Claude Code: когда срабатывает каждое, какие 15 умеют блокировать и правило stdout, которое молча съедает вывод большинства хуков. Практический справочник, собранный на хуках, работающих в продакшене в тысячах сессий агентов.

При подключении хуков к Claude Code раз за разом всплывают две поломки, и они совершенно не похожи друг на друга.

Первая: вы добавляете хук, и ничего не происходит. Ни ошибки, ни предупреждения, ни строки в логе. Хук просто никогда не запускается.

Вторая: хук явно отрабатывает, его побочные эффекты видны на диске, но сообщение, которое он печатает для агента, не доходит. Агент ведёт себя так, будто хук ничего не сказал.

У обеих одна и та же причина. Система хуков шире и неоднороднее, чем горстка событий, которую разбирает большинство статей, а правила, определяющие, кто может говорить с агентом, вовсе не те, о которых вы бы догадались. Это тот справочник, которого нам самим не хватало. Мы строим AgentsRoom поверх этих хуков, и всё, что ниже, либо процитировано из официальной документации, либо измерено в продакшене.

Событий 30, а не шесть

Большинство руководств разбирает PreToolUse, PostToolUse, UserPromptSubmit, Stop, Notification и SubagentStop. Эти шесть реальны, и на них приходится основная часть полезной работы. Они же составляют пятую часть от того, что существует.

Полный список, сгруппированный по тому, за чем каждое событие наблюдает:

ГруппаСобытия
СессияSessionStart, SessionEnd, Setup
ПромптUserPromptSubmit, UserPromptExpansion
ИнструментыPreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch
РазрешенияPermissionRequest, PermissionDenied
ХодStop, StopFailure
Субагенты и задачиSubagentStart, SubagentStop, TaskCreated, TaskCompleted, TeammateIdle
КонтекстPreCompact, PostCompact, InstructionsLoaded
ОкружениеFileChanged, CwdChanged, ConfigChange
WorktreesWorktreeCreate, WorktreeRemove
ИнтерфейсNotification, MessageDisplay
Elicitation в MCPElicitation, ElicitationResult

Хронологическая схема 30 событий хуков Claude Code в порядке их срабатывания в течение сессии агента, от SessionStart через UserPromptSubmit, PreToolUse, PostToolUse и Stop до SessionEnd, с указанием событий, способных заблокировать агента.

Порядок срабатывания событий в одной сессии. Блок инструмента повторяется на каждый вызов инструмента, а весь блок промпта повторяется на каждый ход.

Некоторые из этих событий меняют взгляд на систему. PostToolUseFailure существует, поэтому ветка «сработал ли инструмент» вынесена в отдельное событие, а не выводится из payload. PostToolBatch срабатывает один раз после того, как разрешится пачка параллельных вызовов инструментов: это подходящее место, чтобы запустить линтер один раз, а не по разу на каждую правку. InstructionsLoaded срабатывает при чтении CLAUDE.md, что даёт точку подключения для проверки, действительно ли агент загрузил те правила, которые вы считаете загруженными.

Правило stdout, которое съедает вывод большинства хуков

Это самое полезное, что есть на этой странице.

При коде выхода 0 Claude Code разбирает stdout в поисках полей JSON. Но показывается ли этот stdout агенту вообще, зависит от события, и список исключений короткий. Из документации:

Для большинства событий stdout пишется в отладочный лог, но не показывается в транскрипте. Исключения: UserPromptSubmit, UserPromptExpansion и SessionStart, где stdout добавляется как контекст, который Claude видит и может использовать.

Три события из тридцати. Если вы делаете echo "warning: this migration is destructive" из хука PostToolUse и ждёте, что агент это прочитает, он не прочитает никогда. Ваш текст ушёл в отладочный лог.

Есть ровно два способа показать агенту текст из любого другого события:

  1. Выйти с кодом 2 и писать в stderr. При выходе с кодом 2 Claude Code игнорирует stdout и любой JSON в нём, а stderr отдаёт агенту как сообщение об ошибке.
  2. Выйти с кодом 0 и напечатать объект JSON, несущий hookSpecificOutput.additionalContext.

Обратите внимание на асимметрию в первом пункте. Выход 0: важен stdout, stderr не важен. Выход 2: важен stderr, а stdout отбрасывается целиком. Путаница между этими двумя случаями и есть причина, по которой хук может выглядеть совершенно правильным и всё равно молчать.

Любой другой код выхода считается неблокирующей ошибкой. В транскрипте появляется уведомление <hook name> hook error с первой строкой stderr, выполнение продолжается, а полный stderr попадает в отладочный лог.

Схема кодов выхода хуков Claude Code: выход 0 отправляет JSON из stdout агенту только на трёх событиях, выход 2 блокирует действие и отправляет агенту stderr, любой другой код выхода считается неблокирующей ошибкой и пишется в отладочный лог.

Какой канал доходит до агента при каждом коде выхода. Пунктирный путь: тот самый, который все считают существующим, но которого нет.

Блокировать умеет ровно половина

Пятнадцать событий останавливают действие при выходе с кодом 2. Пятнадцать его игнорируют и продолжают.

Могут блокировать: PreToolUse, PermissionRequest, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange, PostToolBatch, PreCompact, Elicitation, ElicitationResult, WorktreeCreate.

Не могут блокировать: PostToolUse, PostToolUseFailure, PermissionDenied, StopFailure, Notification, SubagentStart, SessionStart, Setup, SessionEnd, CwdChanged, FileChanged, PostCompact, WorktreeRemove, InstructionsLoaded, MessageDisplay.

Практическое следствие: предохранитель ставится на PreToolUse и никогда на PostToolUse. PostToolUse срабатывает после того, как инструмент отработал успешно. Выход с кодом 2 там не отменит запись, он лишь напечатает ошибку, пока ущерб уже лежит на диске. Если вы хотите остановить rm -rf, у вас есть ровно одно место, где это делается.

То, что PostToolBatch блокирует, а PostToolUse нет, заслуживает второго взгляда. Это значит, что проверка на уровне пачки всё ещё может остановить ход после того, как параллельные правки применились: ближе к вето после записи система ничего не предлагает.

Matcher работает по точному совпадению, пока вдруг не перестаёт

Поле matcher меняет стратегию вычисления в зависимости от собственных символов, и ничто не подсказывает вам, по какому пути оно пошло.

MatcherКак вычисляется
"*", "" или отсутствуетподходит ко всему
Только буквы, цифры, _, -, пробелы, ,, |точная строка или список точных строк, разделённых | или ,
Что-либо ещёрегулярное выражение JavaScript без якорей

Ловушка именно в отсутствии якорей. В документации прямо сказано, что регулярное выражение проверяется через RegExp.prototype.test, который срабатывает при совпадении в любом месте значения. Поэтому Edit.* подходит и к Edit, и к NotebookEdit. Если вы имели в виду один инструмент, пишите ^Edit$.

Два зависящих от версии поведения, которые стоит знать, прежде чем отлаживать не то:

  • Запятые как разделители и терпимость к пробелам требуют Claude Code v2.1.191 или новее.
  • Дефис вошёл в набор символов точного совпадения в v2.1.195. До этого matcher вида code-reviewer считался регулярным выражением без якорей и потому срабатывал ещё и на senior-code-reviewer.

В AgentsRoom мы ограничиваем собственный хук атрибуции файлов через Write|Edit|MultiEdit|NotebookEdit: он остаётся на ветке точной строки и подходит к этим четырём инструментам и ни к чему больше. У хуков жизненного цикла, которые мы устанавливаем, matcher отсутствует вовсе, потому что они касаются нас всегда.

Хуки можно объявить в шести местах, и все они складываются

Первый порыв: поискать порядок приоритетов. Его нет, и в этом самое интересное.

РасположениеОбласть действия
~/.claude/settings.jsonвсе ваши проекты, локально на вашей машине
.claude/settings.jsonодин проект, можно закоммитить
.claude/settings.local.jsonодин проект, Claude Code добавляет его в gitignore
Managed policy settingsвся организация, под контролем администратора
hooks/hooks.json плагинапока плагин включён
Frontmatter скилла или агентапока компонент активен

Из документации:

Записи хуков сливаются между уровнями настроек, а не заменяют друг друга: пользовательские, проектные и локальные настройки добавляют свои хуки, не удаляя управляемые, а настройка disableAllHooks не может отключить управляемые хуки извне управляемых настроек.

То есть проектный хук никогда не перекрывает глобальный, он ложится сверху. Шесть источников, все складываются. Форматтер на PostToolUse, объявленный в ваших пользовательских настройках и ещё раз в проекте, отработает дважды на каждую правку, и единственный симптом: ощущение, что всё стало медленнее.

Схема шести мест в настройках Claude Code, где можно объявить хуки: все они складываются в один общий набор хуков, а не перекрывают друг друга.

Шесть источников, один слитый набор. Ничто здесь ничего не перекрывает.

Это же объясняет, почему инструменту, который ставит хук в чужой проект, стоит выбирать .claude/settings.local.json. Он ограничен проектом, Claude Code добавляет его в gitignore, и он загружается без единого флага CLI. Именно туда AgentsRoom пишет свои записи, чтобы закоммиченный пользователем .claude/settings.json никогда не трогался, а его коллеги никогда не наследовали путь, привязанный к конкретной машине.

Чему нас научили хуки в продакшене

AgentsRoom ставит хуки в каждый проект, который открывает, чтобы детерминированно отслеживать статус агентов и приписывать изменённые файлы нужному агенту. Некоторые вещи вылезают только на таком масштабе.

Неизвестные имена событий молча игнорируются. В документации этого нет, а мы на это опираемся. Когда мы добавляем новое событие жизненного цикла в наш установщик, пользователи со старым CLI получают settings.local.json с именем события, о котором их бинарник никогда не слышал. Ничего не ломается, ничего не предупреждает, запись просто пропускается. Именно это позволяет безопасно выпускать установщик раньше релиза CLI. И именно из-за этого, что неизбежно, опечатка даёт полную тишину вместо ошибки.

agent_id показывает, что вы находитесь внутри субагента. Поле присутствует только тогда, когда хук срабатывает внутри вызова субагента. Это важнее, чем кажется: Stop срабатывает, когда свой ход заканчивает субагент, а не только главный агент. Наивное правило «пометить сессию завершённой на Stop» помечает всю сессию законченной при первом же возврате любого субагента. Мы пропускаем события конца хода, несущие agent_id, ровно по этой причине.

Не читайте transcript_path для текущего хода. В документации предупреждают, что транскрипт пишется асинхронно и может отставать от диалога в памяти, поэтому самых свежих сообщений там может ещё не быть в момент срабатывания вашего хука. Stop и SubagentStop получают last_assistant_message именно затем, чтобы вам никогда не приходилось гоняться за файлом.

Хуки: единственный надёжный сигнал статуса. До хуков мы разбирали вывод PTY, чтобы понять, думает агент, ждёт или закончил. Это ломается в тот момент, когда CLI начинает рисовать через альтернативный экранный буфер терминала, что и делает /tui fullscreen. Хуки срабатывают одинаково при любом рендерере. Если вы строите что-то, что наблюдает за агентом снаружи, стройте именно на этом слое, а разбор вывода терминала остаётся в лучшем случае запасным вариантом.

async: true ничего не стоит. Команда хука может объявить async: true, и агент её не ждёт. Наш хук делает POST на локальный эндпоинт с потолком в 2 секунды и возвращает управление; задержка хода агента не меняется, даже когда принимающее приложение закрыто. Если ваш хук только наблюдает и никогда ничего не решает, сделайте его асинхронным и перестаньте за него платить.

Никогда не позволяйте хуку писать мусор в терминал. Наш скрипт проглатывает все исключения, в том числе на верхнем уровне. Необработанный traceback из хука не просто тихо падает, он печатает стек вызовов Python в терминальную сессию пользователя посреди его работы.

Таймауты

Значения по умолчанию щедрые, с тремя исключениями, которые щедрыми не назовёшь:

Тип хукаТаймаут по умолчанию
command, http, mcp_tool600 с
prompt30 с
agent60 с
UserPromptSubmit (command, http, mcp_tool)30 с
MessageDisplay (command, http, mcp_tool)10 с
SessionEnd1,5 с на все хуки вместе, поднимается под более длинный timeout отдельного хука, максимум до 60 с

Бюджет SessionEnd удивляет чаще всего. Это общий бюджет, а не лимит на каждый хук, поэтому три хука очистки делят между собой 1,5 секунды, если вы явно его не поднимете.

Коротко

  • Событий существует 30. Известны шесть.
  • stdout доходит до агента только на UserPromptSubmit, UserPromptExpansion и SessionStart. Во всех остальных случаях выходите с кодом 2 и пишите в stderr либо передавайте additionalContext в JSON.
  • 15 событий блокируют при выходе с кодом 2, 15 его игнорируют. Предохранители ставятся на PreToolUse.
  • Matcher остаётся точной строкой, пока специальный символ не превратит его в регулярное выражение без якорей.
  • Шесть источников настроек складываются. Ничто ничего не перекрывает.
  • Имя события с опечаткой отказывает в абсолютной тишине.

Если вы хотите видеть, как эти события срабатывают, а не рассуждать о них, мы построили именно это: AgentsRoom показывает каждое срабатывание хука по агенту, по проекту, по запуску, на десятках параллельных агентов и в сессиях субагентов. Хуки, которые вы настроили в своих собственных настройках, продолжают работать ровно так, как написаны, потому что AgentsRoom запускает настоящий CLI.

Читать далее

Скачать AgentsRoom

Запускай ИИ-агентов (Claude, Codex, Antigravity CLI, OpenCode, Aider, Grok Build, Mistral Vibe, Kimi Code) на всех проектах из одного окна.

БесплатноСкачать AgentsRoom

Приложение-компаньон: следите за агентами на ходу

Используйте Claude, Codex, Antigravity CLI или другого поставщика AI.

Установить расширение
Chrome Web Store

Отправляйте баги и запросы прямо в ваш публичный бэклог.

Взгляд на AgentsRoom в действии.

Мульти-проекты
Мульти-провайдер
Мульти-агенты
Статус онлайн
Diff и коммиты
Мобильное приложение
Live-превью
Команды агентов
Тесты в браузере
Разработка от backlog
Библиотека промптов
Библиотека навыков
Все функции