Агент получил объёмную задачу и ушёл работать: читает файлы, правит код, гоняет тесты. Ждать у терминала бессмысленно, и вы переключаетесь на почту, документ, соседний репозиторий. Проходит четверть часа, вы возвращаетесь — а Claude всё это время не работал. Он остановился на второй минуте, потому что ждал вашего одобрения на команду, и с тех пор просто стоит.
Обратная ситуация не лучше: задача давно готова, а вы об этом не знаете. Теряется одно и то же — ваше внимание не там, где нужно. Хуки закрывают этот разрыв: пусть Claude Code сам постучится в системное уведомление, когда ему что-то нужно от вас или когда он закончил.
Что уже умеем / что добавим
Вы уже умеете вешать обработчики на события инструментов, писать матчеры, читать входной JSON и возвращать решение кодом выхода или JSON на stdout. Все эти хуки работали внутрь: они влияли на то, что Claude делает.
Теперь развернём хук наружу — к вам. Разберём два события, Notification и Stop, настроим уведомление под macOS, Linux и Windows, научимся сужать хук до конкретного типа и вытаскивать в текст последнюю фразу Claude.
Что вы узнаете в рамках статьи
- Чем событие
Notificationотличается отStopи когда какое выбирать - Как отправить системное уведомление на macOS, Linux и Windows
- Какие бывают типы уведомлений и как сузить хук до одного из них
- Как положить в уведомление осмысленный текст из
last_assistant_message - Почему
Stopхуку нужна защита от зацикливания черезstop_hook_active
Ключевые термины
Notification— событие, которое срабатывает, когда Claude Code отправляет уведомление, то есть когда агент чего-то ждёт от вас.Stop— событие, которое срабатывает, когда Claude закончил отвечать. Получает поляlast_assistant_messageиstop_hook_active.- Тип уведомления — значение матчера события
Notification, напримерpermission_promptилиidle_prompt. stop_hook_active— поле входного JSON, равноеtrue, если Claude уже перезапущен после срабатыванияStop-хука. Признак того, что скрипту пора выйти.
Два события: «ждёт вас» и «закончил»
Разница между Notification и Stop — смысловое ядро этого урока, и путают их постоянно.
Notification срабатывает, когда Claude Code отправляет уведомление. Практически это значит: агент остановился и ждёт вас. Самый частый случай — запрос разрешения на вызов инструмента. Работа стоит, пока вы не ответите.
Stop срабатывает, когда Claude закончил отвечать. Здесь важна оговорка, которая ломает ожидания: Stop срабатывает каждый раз, когда Claude заканчивает ответ, а не только по завершении большой задачи. Спросили «что делает этот файл», получили абзац — Stop сработал. Это событие раз за ход, как UserPromptSubmit на другом конце хода. Ещё деталь: на прерывание пользователем Stop не срабатывает. Нажали Esc посреди работы — уведомления не будет.
Отсюда правило. Нужно знать, что агент вас ждёт, — берите Notification. Нужно знать, что ответ готов, — Stop, но готовьтесь к уведомлениям и на короткие реплики.
Уведомление на трёх платформах
Начните не с конфигурации, а с самой команды: выполните её в терминале и убедитесь, что уведомление видно. Если команда не работает в шелле, из хука она тем более не заработает, а ошибку вы там не увидите.
# macOS
osascript -e 'display notification "Claude Code needs your attention" with title "Claude Code"'
# Linux
notify-send 'Claude Code' 'Claude Code needs your attention'
# Windows
powershell.exe -Command "[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')"
Убедились, что всё видно, — переносите команду в настройки:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
Обратите внимание на экранирование: двойные кавычки внутри JSON-строки записаны как \". Забытый обратный слэш — частая причина того, что файл настроек перестаёт разбираться.
Дальше — две платформенные оговорки, о которые спотыкаются чаще всего.
На macOS osascript шлёт уведомления через приложение Script Editor. Если у Script Editor нет разрешения на уведомления, команда молча падает — ни ошибки, ни уведомления. Лечится один раз: выполните osascript -e 'display notification "test"', затем в System Settings > Notifications найдите Script Editor и включите Allow Notifications.
На Linux notify-send требует демон уведомлений рабочего стола. На headless-серверах и в большинстве контейнеров его нет, и хук работать не будет. Пакет на Debian и Ubuntu называется libnotify-bin.
Матчеры Notification: сузить до нужного случая
Пустой матчер ловит все типы уведомлений. Часто это шумно: вам нужен момент, когда работа реально встала.
Два типа стоит знать наизусть. permission_prompt — Claude ждёт одобрения вызова инструмента, промпт висит около шести секунд. idle_prompt — Claude закончил отвечать около 60 секунд назад, а вы ничего не набрали. Первый означает «я заблокирован», второй — «я жду вашего следующего шага».
Остальные значения: auth_success, elicitation_dialog, elicitation_url_dialog, elicitation_complete, elicitation_response, agent_needs_input, agent_completed.
Сузить хук — значит поставить нужное значение в поле matcher:
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Ждёт одобрения вызова инструмента'"
}
]
}
]
}
}
Теперь уведомление придёт только тогда, когда агент заблокирован на разрешении.
Stop-хук: осмысленный текст и защита от зацикливания
У Stop есть преимущество: во входном JSON приходит last_assistant_message — последняя реплика Claude. Её можно положить в текст уведомления, и тогда вы узнаете не только «готово», но и что именно готово.
И сразу же — обязательная часть. Claude Code перекрывает Stop-хук после восьми блокировок подряд. Чтобы не крутиться в цикле «Claude остановился — хук не дал остановиться — Claude ответил снова», скрипт должен читать поле stop_hook_active и выходить раньше, если оно true:
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0
fi
MSG=$(echo "$INPUT" | jq -r '.last_assistant_message // "Задача завершена"')
osascript -e "display notification \"${MSG:0:200}\" with title \"Claude Code\""
exit 0
Сохраните скрипт как .claude/hooks/notify-stop.sh, сделайте исполняемым через chmod +x и зарегистрируйте:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/notify-stop.sh"
}
]
}
]
}
}
Поля matcher здесь нет, и это не упущение. Stop — одно из событий, которые матчер не поддерживают: значение молча игнорируется. Ошибки вы не увидите, а хук будет срабатывать на все остановки. Фильтровать нужно внутри скрипта.
Проверка
Порядок такой. Сначала команда уведомления в терминале — она должна показать уведомление на экране. Затем правка файла настроек. Затем в сессии выполните /hooks, найдите событие Notification и убедитесь, что ваш хук в списке: там видно матчер, тип, файл-источник и команду. Если хука нет, ошибка в конфигурации, а не в команде. Дальше спровоцируйте событие: попросите Claude выполнить команду, требующую разрешения.
Частые ошибки
- Ставить матчер на
Stop. Событие матчер не поддерживает, значение молча игнорируется, хук срабатывает всегда. Ошибки не будет — будет неожиданное поведение. - Не экранировать кавычки в JSON. Команда
osascriptсодержит двойные кавычки, внутри JSON-строки их пишут как\". Иначе файл настроек не разберётся целиком. - Забыть про
stop_hook_active. Без этой проверкиStopхук, блокирующий остановку, зацикливается, пока Claude Code не перекроет его после восьми срабатываний подряд. - Настраивать хук, не проверив команду в терминале. На macOS без разрешения для Script Editor
osascriptпадает молча, на headless-Linuxnotify-sendне находит демон. Из хука вы этого не увидите.
Наиболее частые вопросы
- Что выбрать для «дай знать, когда закончишь»? —
Stop. Но он срабатывает после каждого ответа, включая односложные, — на активной сессии это много уведомлений. - А если я хочу знать, что агент застрял на разрешении? —
Notificationс матчеромpermission_prompt. Ровно тот случай, когда работа стоит и ждёт вас. - Сработает ли
Stop, если я прерву Claude сам? — Нет. На прерывание пользователем событие не срабатывает. - Почему уведомление не приходит, хотя хук виден в
/hooks? — Скорее всего, дело в команде: разрешение для Script Editor на macOS или отсутствие демона уведомлений на Linux. Проверьте её в терминале отдельно. - Чем
idle_promptотличается отStop? —Stopсрабатывает сразу по завершении ответа,idle_prompt— примерно через минуту, и только если вы ничего не набрали.
Итоги
Вы замкнули петлю обратной связи: агент теперь сам сообщает, что закончил или что ему нужно ваше решение. Вы различаете Notification и Stop, знаете команду уведомления для своей платформы и её подводные камни, умеете сузить хук до permission_prompt и защитить Stop-хук через stop_hook_active.
На этом глава про хуки завершается. Вы начали с карты событий жизненного цикла, научились писать обработчики, блокировать опасные вызовы, автоматизировать рутину вокруг правок файлов и получать сигнал наружу. Хуки перестали быть чем-то из документации и стали инструментом, которым вы настраиваете среду под себя. Дальше — глава 9 про MCP: там мы подключим к Claude Code внешние системы, базы данных, браузер и сервисы по API, чтобы агент работал не только с вашим репозиторием.