Инструкция

Claude Code hooks: как превратить правила агента в гарантии

Обновлено: · Skillaria.Top

Хуки Claude Code — это shell-команды, которые сам Claude Code запускает на определённых событиях: до и после вызова инструмента, при отправке промпта, при старте сессии и перед остановкой агента. Их настраивают в settings.json, а код выхода 2 блокирует действие и возвращает агенту сообщение об ошибке, поэтому хук превращает правило из CLAUDE.md в гарантию, которую модель не может забыть.

Что такое хуки Claude Code

Хук — это команда, которую запускает не модель, а сама программа Claude Code в заданный момент работы агента. Агент собирается выполнить rm -rf — срабатывает хук и решает, пропустить ли команду. Агент отредактировал файл — хук запускает форматтер. Агент хочет закончить работу — хук прогоняет тесты и не даёт остановиться, пока они красные.

Главное отличие от инструкций в CLAUDE.md: инструкция — это текст, который модель учитывает с некоторой вероятностью, а хук — детерминированный код. Он сработает всегда, независимо от того, насколько длинный контекст, что агент «решил» и не забыл ли он правило на сотой минуте сессии. Поэтому claude code hooks — основной способ превратить пожелание в гарантию.

Почему правил в CLAUDE.md недостаточно

Типичная картина: агент дописал фичу, юнит-тесты зелёные, в чате уверенное «Готово». А сквозной сценарий никто не запускал, линтер не прогонялся, миграция упала на середине. В CLAUDE.md при этом написано «перед завершением запусти тесты». Модель не обманывает намеренно — она просто оценивает себя сама, и эта самооценка ненадёжна.

Отсюда принцип харнеса: всё, что можно проверить механически, проверяет механика, а не модель. Правило «запускай линтер» становится хуком, который запускает линтер. Правило «не трогай прод-конфиг» становится хуком, который блокирует запись в этот файл. Текстовая инструкция остаётся, но лишь объясняет агенту, почему его остановили.

События: когда срабатывают хуки

Каждый хук привязан к событию жизненного цикла сессии. Основные события:

СобытиеКогда срабатываетТипичное применение
PreToolUseперед вызовом инструмента (Bash, Edit, Write и др.)запрет опасных команд, защита файлов
PostToolUseпосле успешного вызова инструментаформаттер, линтер, быстрые тесты после правки
UserPromptSubmitкогда пользователь отправил промпт, до его обработкидобавить контекст, отсечь промпт с секретом
Stopкогда агент собирается закончить ответне дать остановиться, пока проверки не пройдены
SubagentStopкогда завершает работу субагентпроверка результата субагента
SessionStartпри старте или возобновлении сессииподгрузить состояние: git status, план, журнал прогресса
SessionEndпри завершении сессииочистка, запись логов
Notificationкогда Claude Code отправляет уведомление (например, ждёт разрешения)звук или сообщение на телефон
PreCompactперед сжатием контекстасохранить важное перед компактизацией

Самые полезные для надёжности — PreToolUse, PostToolUse и Stop: они закрывают три слабых места агента — опасные действия, непроверенные правки и преждевременное «готово».

Где настраиваются хуки: settings.json

Хуки описываются в файлах настроек Claude Code:

  • ~/.claude/settings.json — ваши личные, для всех проектов;
  • .claude/settings.json в репозитории — общие для команды, коммитятся в git;
  • .claude/settings.local.json — локальные для проекта, в git не попадают.

Структура такая: событие → список групп с matcher → список команд.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/lint.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

matcher — имя инструмента или регулярное выражение по нему: Bash, Edit|Write, .*. Он нужен для событий, связанных с инструментами; для Stop или UserPromptSubmit его можно не указывать. Переменная CLAUDE_PROJECT_DIR указывает на корень проекта, поэтому скрипты удобно держать в .claude/hooks/ и вызывать через неё. Посмотреть текущие хуки можно командой /hooks внутри Claude Code.

Как хук получает данные и отвечает

Вход: JSON в stdin

Claude Code передаёт хуку JSON со сведениями о событии: идентификатор сессии, рабочий каталог, имя события, а для инструментов — tool_name и tool_input. Для Bash в tool_input.command лежит команда, для Edit и Write в tool_input.file_path — путь к файлу. Читать удобно через jq.

{
  "session_id": "abc123",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "rm -rf build/" },
  "cwd": "/home/dev/project"
}

Выход: код возврата

КодЧто происходит
0успех, работа продолжается; для UserPromptSubmit и SessionStart stdout добавляется в контекст агента
2блокирующая ошибка: stderr передаётся агенту, а действие останавливается (для PreToolUse — вызов инструмента отменяется, для Stop — агент продолжает работу)
другойнеблокирующая ошибка: stderr показывается пользователю, работа идёт дальше

Exit code 2 — ключевая механика. Агент получает текст из stderr как обратную связь и может исправиться: переписать команду, починить тест, не трогать файл. Поэтому пишите в stderr не «ошибка», а понятную причину и что сделать вместо этого.

Кроме кода возврата хук может вывести в stdout JSON с решением, например "decision": "block" и "reason". Это нужно для тонкой настройки, но для большинства задач хватает кода 2 и сообщения в stderr.

Пример 1: линтер и тесты после каждой правки

Хук на PostToolUse с matcher Edit|Write срабатывает после изменения файла. Инструмент уже выполнен, отменить правку нельзя, но код 2 вернёт агенту вывод линтера, и он исправит ошибку сразу, а не через полчаса.

#!/usr/bin/env bash
# .claude/hooks/lint.sh
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in
  *.py)
    if ! out=$(ruff check "$file" 2>&1); then
      echo "Линтер нашёл ошибки в $file:" >&2
      echo "$out" >&2
      exit 2
    fi ;;
  *.php)
    if ! out=$(php -l "$file" 2>&1); then
      echo "$out" >&2
      exit 2
    fi ;;
esac
exit 0

Не вешайте сюда полный прогон тестов на десять минут: хук срабатывает на каждую правку. После правки — быстрые проверки (синтаксис, линтер, тесты одного модуля), а тяжёлые — в хуке Stop.

Пример 2: запрет опасных команд

Хук на PreToolUse с matcher Bash видит команду до выполнения. Код 2 отменяет вызов, и агент получает объяснение.

#!/usr/bin/env bash
# .claude/hooks/guard.sh
cmd=$(jq -r '.tool_input.command // empty')
if echo "$cmd" | grep -Eq 'rm -rf /|git push (-f|--force)|DROP (TABLE|DATABASE)'; then
  echo "Команда заблокирована политикой проекта: $cmd. Опасные операции выполняет человек." >&2
  exit 2
fi
exit 0

Так же защищают файлы: хук на Edit|Write проверяет file_path и блокирует запись в .env, прод-конфиги, сгенерированный код или файлы миграций, которые уже применены. Учтите, что поиск по регулярному выражению обходится — агент может написать команду иначе. Хук — сильная страховка, но не замена правам доступа и песочнице.

Пример 3: агент не может сказать «готово», пока тесты красные

Хук Stop срабатывает, когда агент собирается закончить. Если тесты не проходят, код 2 не даёт остановиться, а вывод тестов уходит агенту как следующая задача. Это механическая версия Definition of Done.

#!/usr/bin/env bash
# .claude/hooks/done.sh
input=$(cat)
# защита от бесконечного цикла: хук уже один раз продлил работу
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0
fi
if ! out=$(npm test --silent 2>&1); then
  echo "Тесты не прошли, работа не закончена:" >&2
  echo "$out" | tail -n 40 >&2
  exit 2
fi
exit 0

Поле stop_hook_active равно true, если агент уже продолжает работу из-за stop-хука. Без этой проверки хук может гонять агента по кругу без конца — именно та ситуация, когда агент «трудится всю ночь не туда». В автономных циклах добавьте ещё лимит попыток и условие, при котором зовут человека.

Пример 4: подгрузить состояние в начале сессии

На SessionStart stdout хука попадает в контекст. Удобно выводить текущую ветку, незакоммиченные изменения и следующую задачу из плана, чтобы агент не начинал с нуля.

#!/usr/bin/env bash
echo "Ветка: $(git branch --show-current)"
git status --short | head -n 20
[ -f PROGRESS.md ] && tail -n 15 PROGRESS.md
exit 0

Как хуки превращают правила в гарантии

Удобно делить правила харнеса на три уровня. Первый — текст в CLAUDE.md: объясняет, как работать, но соблюдается вероятностно. Второй — исполняемые проверки: тесты, E2E, архитектурные правила в виде скрипта, который падает при нарушении. Третий — хуки, которые запускают эти проверки автоматически в нужный момент, без участия модели.

Практическое правило: если агент нарушил одно и то же правило дважды, не добавляйте в CLAUDE.md ещё одно предупреждение капслоком. Спросите, можно ли проверить нарушение скриптом. Если можно — сделайте скрипт и повесьте его на хук. Тогда текстовая инструкция становится короче, а поведение — стабильнее.

Правило в текстеГарантия через хук
«Форматируй код по стандарту»PostToolUse запускает форматтер на изменённый файл
«Не пушь с force»PreToolUse на Bash блокирует команду, exit 2
«Не говори готово, пока тесты красные»Stop запускает тесты и не даёт закончить
«Не редактируй .env»PreToolUse на Edit|Write проверяет путь
«Помни, на чём остановился»SessionStart выводит план и git status

Типичные ошибки

  • Код 1 вместо 2. Скрипт падает с кодом 1, агент ничего не узнаёт, а действие проходит. Блокирует только код 2.
  • Сообщение в stdout. При коде 2 агенту уходит stderr. Пишите через >&2.
  • Медленный хук на частом событии. Полный прогон тестов в PostToolUse делает каждую правку минутной. Тяжёлое — в Stop.
  • Бесконечный Stop. Нет проверки stop_hook_active или лимита — агент крутится без конца.
  • Относительные пути. Хук запускается в рабочем каталоге сессии; используйте $CLAUDE_PROJECT_DIR.
  • Непроверенный чужой хук. Хук выполняется с вашими правами без подтверждения. Читайте скрипты из чужих репозиториев, прежде чем им доверять.

Итог

Claude Code hooks переносят самые важные правила из текста в код: PreToolUse останавливает опасные действия, PostToolUse проверяет каждую правку, Stop не даёт рапортовать о готовности раньше времени, SessionStart возвращает агенту память о проекте. Начните с двух-трёх хуков на правила, которые агент уже нарушал, и держите сообщения в stderr понятными — тогда агент сам исправляет ошибки, а вы проверяете результат, а не процесс. Валидацию, проверку готовности и автономные циклы мы разбираем на практике в курсе.

Частые вопросы

Нужно ли перезапускать Claude Code после изменения хуков?

Изменения в settings.json надёжнее всего применяются в новой сессии; текущую конфигурацию хуков можно посмотреть командой /hooks.

Можно ли писать хуки не на bash?

Да, хук — это любая команда: скрипт на Python, Node.js или бинарник. Важно только читать JSON из stdin и возвращать правильный код выхода.

Видит ли агент вывод успешного хука?

Обычно нет: при коде 0 вывод в контекст не попадает. Исключения — UserPromptSubmit и SessionStart, где stdout добавляется в контекст агента.

Работают ли хуки для субагентов?

Да, вызовы инструментов субагентов проходят через те же хуки PreToolUse и PostToolUse, а для их завершения есть отдельное событие SubagentStop.

Безопасно ли коммитить хуки в репозиторий?

Да, .claude/settings.json и скрипты в .claude/hooks/ — обычная практика для команды; личные хуки держите в settings.local.json или ~/.claude/settings.json.