Stop Hook в Claude Code: как проверить тесты до сообщения «готово»
Настройте короткую проверку завершения Claude Code: тесты, Git-статус или ожидаемый файл, понятный exit code и безопасные логи.
Сообщение «готово» в Claude Code означает, что агент решил завершить ход. Оно не доказывает, что тесты действительно запускались, рабочее дерево чистое или ожидаемый файл появился. Stop Hook позволяет запускать короткую локальную команду перед завершением и вернуть агенту понятный код ошибки.
Хороший hook отвечает на один проверяемый вопрос: «после этого хода есть ли файл dist/app.js и проходит ли быстрая проверка?». Он не заменяет CI, полноценный набор тестов или ручное решение о публикации.
Чем Stop Hook отличается от CLAUDE.md и CI
- Stop Hook запускает команду в момент завершения ответа. Он подходит для быстрой проверки факта:
git diff --check, существования файла или короткого тестового набора. - CLAUDE.md описывает правило для работы агента: что менять нельзя, какую команду запускать и какой результат считать успешным. Сам файл ничего не проверяет.
- CI запускается в отдельной среде после push или pull request и остаётся обязательной границей для команды. Hook не должен заменять эту проверку.
Если проверка меняет внешнюю систему, делает deploy или отправляет сообщение, вынесите её из Stop Hook. При каждом завершении такой side effect будет трудно повторять и откатывать.
Опишите один проверяемый результат
Перед настройкой hook запишите четыре строки:
- Claim: что агент должен утверждать после завершения, например «сборка создана».
- Проверка: команда или файл, которыми это можно подтвердить.
- Успех: exit code
0. - Блокировка Stop: exit code
2и короткая причина в stderr без секретов и полного лога.
Для небольшого Node-проекта контракт может выглядеть так:
Сначала запустите эти команды вручную. Если они занимают несколько минут или требуют сети, сократите их до быстрой локальной проверки, а полный прогон оставьте CI.
Минимальный Stop Hook
Claude Code хранит проектные настройки в .claude/settings.json. Добавьте один Stop hook с ограничением времени и отдельным скриптом:
Скрипт должен быть коротким и предсказуемым:
Сделайте файл исполняемым (chmod +x scripts/check-before-stop.sh) и проверьте его отдельно. В реальном проекте замените npm test и путь к файлу на команды из репозитория. Не добавляйте в вывод переменные окружения, API Key, содержимое .env или полный ответ тестов.
Здесь важен именно код 2. По актуальной документации Claude Code, exit 2 для события Stop запрещает завершение хода, а строка из stderr объясняет Claude, что нужно исправить. Обычный exit 1 считается неблокирующей ошибкой: интерфейс покажет ошибку hook, но Claude сможет остановиться. Timeout тоже не блокирует Stop, поэтому короткая проверка должна укладываться в указанный лимит.
После первой блокировки следующий Stop получает во входном JSON поле stop_hook_active: true. Пример выше даёт Claude одну попытку исправления. Если та же проверка снова не проходит, hook возвращает 0 с systemMessage для оператора и завершает цикл; CI остаётся обязательной границей. Это сознательный fail-open после одной попытки, а не гарантия качества. Для строгого проекта вместо него можно снова вернуть 2, но тогда нужно следить за повтором: Claude Code принудительно завершает ход после восьми последовательных блокировок.
Проверьте полный цикл, а не только скрипт
- Временно замените
dist/app.jsнаdist/missing.jsи выполнитеprintf '%s\n' '{"stop_hook_active":false}' | ./scripts/check-before-stop.sh; echo $?. Ожидаются строкаStop check blockedв stderr и код2. - Верните правильный путь, запустите сборку и повторите ту же команду. Ожидаемый код —
0. - Снова укажите отсутствующий файл, запустите Claude Code и попросите выполнить маленькое обратимое изменение. Когда Claude попробует закончить, Stop Hook должен оставить разговор активным и показать ему причину из stderr.
- Не исправляя путь, выполните
printf '%s\n' '{"stop_hook_active":true}' | ./scripts/check-before-stop.sh; echo $?. Скрипт должен вывести JSON сsystemMessageи вернуть0: это проверка fail-open ветки. - Верните правильный путь или создайте актуальную сборку. После следующего завершения hook должен вернуть
0, и ход закончится без повторной блокировки.
Такой тест отличает работающий Stop Hook от скрипта, который падает в терминале, но не влияет на завершение Claude Code.
Как читать сбой
Если hook вернул ненулевой код, не исправляйте проблему повторной попыткой вслепую:
- Откройте строку
Stop check blockedи запустите только названную команду вручную. - Исправьте код или тест, затем снова запустите проверку в чистом состоянии.
- Убедитесь, что ожидаемый файл создаётся именно текущей командой, а не остался от старой сборки.
- Повторите исходную задачу Claude Code в новой попытке и сохраните новый результат.
Hook может обнаружить только то, что вы в него явно поместили. Прошедший git diff --check не говорит, что бизнес-логика верна; наличие файла не доказывает, что он содержит актуальную сборку.
Где находится API-часть workflow
Если Claude Code работает через API, используйте собственный API Key и сверяйте текущие параметры Anthropic-compatible подключения с документацией BetterToken для Claude Code. Hook остаётся локальной проверкой: ему не нужен доступ к ключу, полному prompt или журналу Dashboard. BetterToken — отдельный API-доступ, а не подписка Claude; ключ создаётся в вашем аккаунте и не должен попадать в репозиторий.