$ sudo teach IT

Агент получил объёмную задачу и ушёл работать: читает файлы, правит код, гоняет тесты. Ждать у терминала бессмысленно, и вы переключаетесь на почту, документ, соседний репозиторий. Проходит четверть часа, вы возвращаетесь — а 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-Linux notify-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, чтобы агент работал не только с вашим репозиторием.

На следующий урок