Бонусы за приглашения

Как работают бонусы за приглашения

Поделитесь ссылкой. Когда друг зарегистрируется по ней и пополнит баланс, вы получите указанный бонус за его последующие пополнения.

Stop Hook в Claude Code: как проверить тесты до сообщения «готово»

Настройте короткую проверку завершения Claude Code: тесты, Git-статус или ожидаемый файл, понятный exit code и безопасные логи.

Содержание

Stop Hook в Claude Code: как проверить тесты до сообщения «готово»

Сообщение «готово» в Claude Code означает, что агент решил завершить ход. Оно не доказывает, что тесты действительно запускались, рабочее дерево чистое или ожидаемый файл появился. Stop Hook позволяет запускать короткую локальную команду перед завершением и вернуть агенту понятный код ошибки.

Хороший hook отвечает на один проверяемый вопрос: «после этого хода есть ли файл dist/app.js и проходит ли быстрая проверка?». Он не заменяет CI, полноценный набор тестов или ручное решение о публикации.

Если Claude Code работает через API, сначала откройте актуальную инструкцию BetterToken, настройте собственный API Key в конфигурации инструмента и выполните короткий тестовый запрос. Затем в Dashboard проверьте ожидаемые модель, статус и расход токенов. Не передавайте ключ локальному hook и не выводите его в лог.

Чем Stop Hook отличается от CLAUDE.md и CI

  • Stop Hook запускает команду в момент завершения ответа. Он подходит для быстрой проверки факта: git diff --check, существования файла или короткого тестового набора.
  • CLAUDE.md описывает правило для работы агента: что менять нельзя, какую команду запускать и какой результат считать успешным. Сам файл ничего не проверяет.
  • CI запускается в отдельной среде после push или pull request и остаётся обязательной границей для команды. Hook не должен заменять эту проверку.

Если проверка меняет внешнюю систему, делает deploy или отправляет сообщение, вынесите её из Stop Hook. При каждом завершении такой side effect будет трудно повторять и откатывать.

Опишите один проверяемый результат

Перед настройкой hook запишите четыре строки:

  1. Claim: что агент должен утверждать после завершения, например «сборка создана».
  2. Проверка: команда или файл, которыми это можно подтвердить.
  3. Успех: exit code 0.
  4. Блокировка Stop: JSON с decision: "block" и короткой причиной reason, без секретов и полного лога.

Для небольшого Node-проекта контракт может выглядеть так:

Claim: сборка готова
Проверка: npm test -- --runInBand и test -s dist/app.js
Успех: обе команды завершились с кодом 0
Сбой: вернуть JSON с decision=block и именем проверки в reason

Сначала запустите эти команды вручную. Если они занимают несколько минут или требуют сети, сократите их до быстрой локальной проверки, а полный прогон оставьте CI.

Минимальный Stop Hook

Claude Code хранит проектные настройки в .claude/settings.json. Добавьте один Stop hook с ограничением времени и отдельным скриптом:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "./scripts/check-before-stop.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Скрипт должен быть коротким и предсказуемым:

#!/usr/bin/env sh
set -eu

if [ -t 0 ]; then
  input='{"stop_hook_active":false}'
else
  input=$(cat)
fi
stop_hook_active=$(printf '%s' "$input" | node -e '
let raw = "";
process.stdin.on("data", chunk => raw += chunk);
process.stdin.on("end", () => {
  try { process.stdout.write(String(Boolean(JSON.parse(raw).stop_hook_active))); }
  catch { process.stdout.write("false"); }
});')

block_stop() {
  if [ "$stop_hook_active" = "true" ]; then
    printf '%s\n' "Stop check still fails: $block_reason. Run the check manually; CI remains required." >&2
    exit 0
  fi
  BLOCK_REASON="$block_reason" node -e 'process.stdout.write(JSON.stringify({decision:"block",reason:`Stop check failed: ${process.env.BLOCK_REASON}`}) + "\n")'
  exit 0
}

if ! npm test -- --runInBand >/dev/null 2>&1; then
  block_reason='run npm test and fix the failing test'
  block_stop
fi

if ! test -s dist/app.js; then
  block_reason='rebuild dist/app.js'
  block_stop
fi

printf '%s\n' 'Stop check passed: tests and dist/app.js'
exit 0

Сделайте файл исполняемым (chmod +x scripts/check-before-stop.sh) и проверьте его отдельно. В реальном проекте замените npm test и путь к файлу на команды из репозитория. Не добавляйте в вывод переменные окружения, API Key, содержимое .env или полный ответ тестов.

По актуальной документации Claude Code, Stop Hook может вернуть с кодом 0 структурированный JSON: decision: "block" не даёт завершить ход, а reason объясняет Claude, что исправить. Ненулевой код и timeout имеют отдельную семантику ошибок hook, поэтому не используйте их как единственный контракт блокировки; короткая проверка должна укладываться в указанный лимит.

После первой блокировки следующий Stop получает во входном JSON поле stop_hook_active: true. Пример выше даёт Claude одну попытку исправления. Если та же проверка снова не проходит, hook пишет короткое предупреждение в stderr и возвращает 0, чтобы завершить цикл; CI остаётся обязательной границей. Это сознательный fail-open после одной попытки, а не гарантия качества. Если проект должен блокировать повторно, задайте собственный счётчик и явную границу — не полагайтесь на недокументированное фиксированное число повторов.

Проверьте полный цикл, а не только скрипт

  1. Временно замените dist/app.js на dist/missing.js и выполните printf '%s\n' '{"stop_hook_active":false}' | ./scripts/check-before-stop.sh; echo $?. Ожидаются JSON с "decision":"block", причиной в reason и код 0.
  2. Верните правильный путь, запустите сборку и повторите ту же команду. Ожидаемый код — 0.
  3. Снова укажите отсутствующий файл, запустите Claude Code и попросите выполнить маленькое обратимое изменение. Когда Claude попробует закончить, Stop Hook должен оставить разговор активным и показать причину из reason.
  4. Не исправляя путь, выполните printf '%s\n' '{"stop_hook_active":true}' | ./scripts/check-before-stop.sh; echo $?. Скрипт должен вывести короткое предупреждение в stderr и вернуть 0: это проверка fail-open ветки.
  5. Верните правильный путь или создайте актуальную сборку. После следующего завершения hook должен вернуть 0, и ход закончится без повторной блокировки.

Такой тест отличает работающий Stop Hook от скрипта, который падает в терминале, но не влияет на завершение Claude Code.

Как читать сбой

Сначала разделите два разных случая. Если hook вернул JSON с decision: "block", прочитайте reason: это штатный провал проверяемого условия. Запустите названную проверку вручную, исправьте код или тест и повторите её в чистом состоянии. Убедитесь, что ожидаемый файл создан текущей командой, а не остался от старой сборки. Затем повторите исходную задачу Claude Code в новой попытке.

Если команда hook сама завершилась ненулевым кодом или по timeout, это ошибка выполнения hook, а не подтверждённая блокировка по reason. Откройте сообщение об ошибке и stderr, запустите скрипт вручную, исправьте путь, права, зависимость или лимит времени и только после этого повторите проверку.

Hook может обнаружить только то, что вы в него явно поместили. Прошедший git diff --check не говорит, что бизнес-логика верна; наличие файла не доказывает, что он содержит актуальную сборку.

Источники

Готовы оптимизировать LLM workflow?

Подключите единый API, управляйте ключами и контролируйте расходы на AI-модели в BetterToken.

Начать бесплатно