У сесії Claude Code спрацьовує 30 подій хуків. Відповісти можуть лише 3.
Повний перелік подій хуків Claude Code: коли спрацьовує кожна з них, які 15 можуть заблокувати дію, і правило stdout, яке мовчки поглинає вивід більшості хуків. Польова довідка, зібрана з роботи хуків у продакшені на тисячах сесій агентів.
Дві поломки трапляються знову і знову, коли хуки під’єднують до Claude Code, і між собою вони не мають нічого спільного.
Перша: ви додаєте хук, і нічого не відбувається. Ні помилки, ні попередження, ні рядка в журналі. Хук просто ніколи не виконується.
Друга: хук очевидно виконується, його побічні ефекти видно на диску, але повідомлення, яке він друкує для агента, так і не доходить. Агент поводиться так, ніби хук нічого не сказав.
Обидві мають одне джерело. Система хуків більша й менш однорідна, ніж та жменя подій, яку описує більшість статей, а правила про те, кому дозволено говорити з агентом, зовсім не такі, як можна припустити. Це та довідка, якої нам самим бракувало. Ми будуємо AgentsRoom поверх цих хуків, і все, що нижче, або процитовано з офіційної довідки, або виміряно в продакшені.
Подій тридцять, а не шість
Більшість посібників описують 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 |
| Worktrees | WorktreeCreate, WorktreeRemove |
| Інтерфейс | Notification, MessageDisplay |
| Elicitation MCP | Elicitation, ElicitationResult |

Порядок спрацювання подій в одній сесії. Блок інструмента повторюється на кожен виклик інструмента, а весь блок промпту повторюється на кожен хід.
Кілька з цих подій змінюють те, як ви думаєте про систему. PostToolUseFailure існує, тож гілка «чи спрацював інструмент» є подією, а не тим, що ви виводите з payload. PostToolBatch спрацьовує один раз після того, як завершується пакет паралельних викликів інструментів: це правильне місце, щоб запустити linter один раз, а не по разу на кожну правку. InstructionsLoaded спрацьовує, коли читається CLAUDE.md, і це дає точку, де можна перевірити, що агент справді завантажив ті правила, які ви вважаєте завантаженими.
Правило stdout, яке поглинає вивід більшості хуків
Це найкорисніше, що є на цій сторінці.
При коді виходу 0 Claude Code розбирає stdout у пошуках полів вихідного JSON. Але чи буде цей stdout колись показаний агентові, залежить від події, і список винятків короткий. З довідки:
Для більшості подій stdout пишеться в журнал налагодження, але не показується в транскрипті. Винятки:
UserPromptSubmit,UserPromptExpansionіSessionStart, де stdout додається як контекст, який Claude бачить і може використати.
Три події з тридцяти. Якщо ви робите echo "warning: this migration is destructive" з хука PostToolUse і чекаєте, що агент це прочитає, він не прочитає ніколи. Ваш текст пішов у журнал налагодження.
Є рівно два способи показати текст агентові з будь-якої іншої події:
- Вийти з кодом 2 і записати в stderr. При виході з кодом 2 Claude Code ігнорує stdout і будь-який JSON у ньому, а stderr повертає агентові як повідомлення про помилку.
- Вийти з кодом 0 і надрукувати об’єкт JSON, який несе
hookSpecificOutput.additionalContext.
Зверніть увагу на асиметрію в першому пункті. Вихід 0 означає, що важить stdout, а stderr не важить. Вихід 2 означає, що важить stderr, а stdout відкидається повністю. Переплутати ці два напрямки: ось чому хук може виглядати цілком правильним і все одно мовчати.
Будь-який інший код виходу є неблокуючою помилкою. У транскрипті з’являється повідомлення <hook name> hook error з першим рядком stderr, виконання триває, а повний 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/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_tool | 600 с |
prompt | 30 с |
agent | 60 с |
UserPromptSubmit (command, http, mcp_tool) | 30 с |
MessageDisplay (command, http, mcp_tool) | 10 с |
SessionEnd | 1,5 с спільно на всі хуки, піднімається до довшого timeout окремого хука, аж до 60 с |
Бюджет SessionEnd є тим, що людей дивує. Це спільний бюджет, а не ліміт на кожен хук, тож три хуки прибирання ділять між собою 1,5 секунди, якщо ви явно його не піднімете.
Коротка версія
- Існує 30 подій. Відомі шість.
- stdout доходить до агента лише на
UserPromptSubmit,UserPromptExpansionіSessionStart. У всіх інших випадках використовуйте вихід з кодом 2 і stderr, абоadditionalContextу JSON. - 15 подій блокують при виході з кодом 2, 15 його ігнорують. Запобіжники ставлять на
PreToolUse. - Matcher залишається точним рядком, доки спеціальний символ не перетворить його на неприв’язаний регулярний вираз.
- Шість джерел налаштувань зливаються додаванням. Ніщо нічого не перекриває.
- Помилка в назві події призводить до цілковито мовчазного провалу.
Якщо ви хочете бачити, як ці події спрацьовують, а не міркувати про них, то саме це ми й побудували: AgentsRoom показує кожне спрацювання хука по агенту, по проєкту, по запуску, для десятків паралельних агентів і сесій субагентів. Хуки, які ви налаштували у власних settings, працюють рівно так, як написані, бо запускається справжній CLI.
Часті запитання
Скільки подій хуків має Claude Code?
Тридцять. Офіційна довідка перелічує SessionStart, Setup, UserPromptSubmit, UserPromptExpansion, PreToolUse, PermissionRequest, PermissionDenied, PostToolUse, PostToolUseFailure, PostToolBatch, Notification, MessageDisplay, SubagentStart, SubagentStop, TaskCreated, TaskCompleted, Stop, StopFailure, TeammateIdle, InstructionsLoaded, ConfigChange, CwdChanged, FileChanged, WorktreeCreate, WorktreeRemove, PreCompact, PostCompact, Elicitation, ElicitationResult і SessionEnd. Рівно 15 з них можуть заблокувати дію при виході з кодом 2.
Чому мій хук виконується, але агент так і не бачить його виводу?
Тому що stdout додається до контексту агента лише на трьох подіях: UserPromptSubmit, UserPromptExpansion і SessionStart. На всіх інших подіях stdout при виході з кодом 0 потрапляє в журнал налагодження і більше нікуди. Щоб показати текст агентові з будь-якої іншої події, треба або вийти з кодом 2 і записати в stderr, або вийти з кодом 0 і надрукувати об’єкт JSON, який несе additionalContext.
Чому мій хук взагалі ніколи не спрацьовує?
Найчастіша причина: помилка в назві події. Claude Code ігнорує записи хуків, назву події яких він не розпізнає, і робить це мовчки: без помилки, без попередження, без рядка в журналі. Друга за поширеністю причина: matcher, який пішов шляхом регулярного виразу там, де ви очікували точний рядок.
Як працює matcher хука: за точним збігом чи як регулярний вираз?
Це залежить від символів у самому matcher. Якщо він містить лише літери, цифри, підкреслення, дефіси, пробіли, коми та вертикальні риски, він трактується як точний рядок або список точних рядків. Щойно з’являється будь-який інший символ, він стає неприв’язаним регулярним виразом JavaScript, тому Edit.* збігається і з NotebookEdit. Прив’язуйте вираз через ^ і $, коли потрібен збіг по всьому рядку.
Який файл налаштувань перемагає, коли хуки визначені одразу в кількох?
Жоден. Записи хуків зливаються між рівнями налаштувань замість того, щоб заміняти одні одних, тож користувацькі, проєктні та локальні налаштування додають кожні свої хуки. Саме тому проєктний хук ніколи не перекриває глобальний, він накладається згори.
Завантажити AgentsRoom
Запускайте всіх своїх AI-агентів на всіх своїх проєктах з одного вікна.
Додаток-компаньйон: контролюйте своїх агентів на ходу
Використовуйте свого: Claude, Codex, Antigravity CLI або іншого AI-провайдера.
Надсилайте баги та запити прямо у свій публічний беклог.
Читати далі
Десять агентів запустили ту саму перевірку типів водночас. Розв'язком виявилася тека.
Сімнадцять кодових агентів в одному чекауті, десять процесів tsc паралельно, load average 37 і 87 МБ вільної пам'яті. Перевірка типів на дев'яносто секунд забрала 7 хв 36. Ось виміри, пояснення того, чому машина не рахувала, і маленьке спільне блокування, яке все виправило. Копіюється в будь-який репозиторій.
Читати статтюАгент рев’ю не повинен мати змоги писати. Ось як ми це забезпечуємо, CLI за CLI.
У запуску з 17 вузлів релізний агент відредагував тест, щоб червоний набір став зеленим, а потім два агенти рев’ю написали одне й те саме виправлення і зіткнулися. У промпті було сказано: лише рев’ю. Це не втрималося. Розбір інциденту, чому письмова інструкція не може нести це правило, і точні прапорці, які змушують Claude Code, Codex, Grok, Antigravity та OpenCode відмовлятися писати.
Читати статтюЯк масштабувати AI-кодувальні агенти в команді розробників
Один розробник з кодувальним агентом - це історія продуктивності. П'ять розробників з двадцятьма агентами - це проблема координації. Ось що ламається першим, коли команда масштабується, і налаштування, яке тримається: зафіксовані файли контексту, чітка власність файлів, огляд за радіусом вибуху і витрати, які ви дійсно можете побачити.
Читати статтю