AmigaОбучение ИИ
Модуль 2 · Настраиваем Claude · урок 5 из 10

Хуки

12 мин чтения▶ есть видеоверсия

CLAUDE.md — это просьба, правила разрешений — это фильтр по шаблону. Но есть класс требований, которые нужно выполнять всегда и с проверкой содержимого: форматировать каждый изменённый файл, не подпускать к миграциям, не давать завершить ход, пока не прошли тесты. Для этого в Claude Code есть хуки — ваши shell-команды, привязанные к событиям жизненного цикла. В этом уроке разберём, как они устроены, и напишем три хука, которые пригодятся в любом проекте агентства.

Что такое хук и чем он отличается от CLAUDE.md

Хук — команда (или скрипт), которую Claude Code запускает при наступлении события: перед вызовом инструмента, после него, при отправке вашего запроса, перед завершением хода. Ключевое отличие от инструкций: хук детерминирован. CLAUDE.md может быть проигнорирован, если контекст переполнен; хук выполнится в любом случае. Документация формулирует правило выбора коротко: хуки — для действий, которые должны происходить каждый раз без исключений.

Хук получает на стандартный ввод JSON с описанием события (какой инструмент, какие аргументы, в какой папке), может что-то сделать и ответить — кодом выхода или JSON на стандартном выводе.

События

Список событий длинный и растёт; полный — в справочнике по хукам. Для практики достаточно этих:

Событие Когда срабатывает Может заблокировать
PreToolUse Перед вызовом инструмента Да
PostToolUse После успешного вызова Нет (но может дать обратную связь)
UserPromptSubmit Вы отправили запрос, Claude ещё не начал Да
Stop Claude собирается завершить ход Да
SubagentStop Субагент завершает работу Да
SessionStart Старт или возобновление сессии Нет
PreCompact Перед сжатием контекста Нет
Notification Claude Code показывает уведомление (например, ждёт подтверждения) Нет

Есть и более специальные: FileChanged (файл на диске изменился), ConfigChange, PermissionRequest, PostToolUseFailure и другие.

Формат настроек

Хуки живут в тех же файлах, что и права: ~/.claude/settings.json для себя, .claude/settings.json для команды, .claude/settings.local.json для себя в этом проекте. Структура: событие → список групп с matcher → список обработчиков.

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

matcher для событий инструментов — имя инструмента: Bash, Edit|Write, регулярное выражение вроде mcp__.* для всех MCP-инструментов. Пустая строка или * — любое. Для Stop и UserPromptSubmit matcher не нужен. Дополнительно можно сузить условием "if": "Bash(rm *)" в синтаксисе правил разрешений.

Тип обработчика command — обычная команда. Ещё есть prompt (решение принимает модель по вашему тексту) и agent (субагент с инструментами, экспериментальный). У обработчика есть timeout в секундах (по умолчанию 600 для команд) и флаг async, чтобы не блокировать сессию.

Скрипты удобно держать в .claude/hooks/ и ссылаться через ${CLAUDE_PROJECT_DIR} — так путь не зависит от того, из какой подпапки запущен Claude. Скрипт должен быть исполняемым (chmod +x).

Посмотреть, что настроено, — команда /hooks. Меню только для чтения; менять — в JSON руками или попросив Claude («напиши хук, который запускает eslint после каждой правки» — он умеет).

Как хук отвечает

Три варианта ответа, из которых нужно выбрать один на хук:

  • Код выхода 0 без вывода — всё в порядке, продолжаем.
  • Код выхода 2 — заблокировать действие. Текст из stderr уходит Claude как обратная связь (для PreToolUse, Stop, UserPromptSubmit), и он подстраивается. Для PostToolUse блокировать уже нечего — событие после факта.
  • Код выхода 0 и JSON на stdout — структурированное решение. Для PreToolUse это permissionDecision: allow, deny или ask, плюс permissionDecisionReason и при необходимости updatedInput — изменённые аргументы. Есть общие поля additionalContext (добавить Claude информацию) и systemMessage (показать пользователю).

Любой другой код выхода — неблокирующая ошибка: в транскрипте появится пометка, действие продолжится.

Важно про взаимодействие с правами: PreToolUse срабатывает до проверки режима, в любом режиме, включая bypassPermissions. Хук, ответивший deny, блокирует даже там. Обратное неверно: allow от хука не отменяет правило deny из настроек. Хуки могут ужесточить, но не ослабить.

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

Самый простой и самый полезный хук. После любой правки файла запускаем форматтер — и в дифах больше нет шума от отступов:

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

jq вытаскивает путь из входного JSON, xargs передаёт его Prettier. Для файлов, которые меняются shell-командами, а не инструментом редактирования, есть событие FileChanged.

Пример 2. Защита файлов

Запрещаем Claude трогать .env, lock-файл, папку миграций и .git/, чем бы ни закончилась проверка прав. Скрипт .claude/hooks/protect-files.sh:

bash
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
FILE_PATH="${FILE_PATH//\\//}"

PROTECTED=(".env" "pnpm-lock.yaml" "prisma/migrations/" ".git/")

for pattern in "${PROTECTED[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Заблокировано: $FILE_PATH попадает под шаблон '$pattern'" >&2
    exit 2
  fi
done
exit 0

Регистрация:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

Код выхода 2 блокирует правку, а сообщение из stderr Claude получает как объяснение и меняет подход — например, предлагает вам самому создать миграцию. Правило deny в настройках сделало бы похожее, но хук умеет то, что правилу недоступно: проверять содержимое команды, время, ветку, что угодно.

Пример 3. Не завершать ход, пока не прошли тесты

Тот самый жёсткий барьер, о котором шла речь в уроке про навыки проверки. Хук на Stop запускает тесты; если они падают, Claude не может завершить ход и продолжает работу. Скрипт .claude/hooks/tests-before-stop.sh:

bash
#!/bin/bash
INPUT=$(cat)

# Если хук уже возвращал Claude к работе, не зацикливаемся
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0
fi

cd "$CLAUDE_PROJECT_DIR" || exit 0

# Нечего проверять — выходим
if git diff --quiet && git diff --cached --quiet; then
  exit 0
fi

OUTPUT=$(pnpm test 2>&1)
if [ $? -ne 0 ]; then
  echo "Тесты не проходят. Исправь падения и покажи вывод pnpm test:" >&2
  echo "$OUTPUT" | tail -n 30 >&2
  exit 2
fi
exit 0
json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/tests-before-stop.sh",
            "timeout": 300
          }
        ]
      }
    ]
  }
}

Две детали, без которых хук будет вредить. Поле stop_hook_active во входном JSON говорит, что Claude уже продолжил работу по требованию этого хука; без проверки он будет гонять тесты по кругу. Claude Code сам снимает барьер после восьми блокировок подряд без прогресса, но лучше до этого не доводить. И timeout: тесты могут идти дольше стандартного лимита — задайте свой.

Если проверка требует не команды, а суждения, есть хук типа prompt: он отправляет ваш текст и данные события модели, которая отвечает ok: true или ok: false с причиной. Например: «Проверь, все ли пункты задачи выполнены; если нет — верни список оставшегося». Это дешёвый способ добавить самопроверку без скрипта.

Уведомление, когда Claude ждёт

Для длинных сессий полезен хук на Notification: когда Claude Code ждёт подтверждения, а вы переключились в другое окно, придёт системное уведомление. На macOS:

json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code ждёт ответа\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Этот хук личный, ему место в ~/.claude/settings.json.

Где какой хук держать

Командные хуки (форматтер, защита файлов, тесты перед остановкой) коммитьте в .claude/settings.json — они должны работать у всех одинаково. Личные (уведомления) — в ~/.claude/settings.json. Если набор хуков нужен в двадцати проектах, его упаковывают в плагин с файлом hooks/hooks.json того же формата — об этом в уроке «Плагины».

Помните и о доверии: хуки из .claude/settings.json чужого репозитория выполняются на вашей машине. Claude Code спрашивает, доверяете ли вы папке, прежде чем применить проектные хуки; читайте, что подключаете.

Попробуйте сами

10–15 мин на рабочем месте
  1. Добавьте хук форматирования в .claude/settings.json своего проекта (замените Prettier на ваш форматтер). Попросите Claude внести правку с заведомо кривыми отступами и откройте файл.
  2. Создайте protect-files.sh со списком файлов, которые в вашем проекте нельзя трогать. Попросите Claude добавить комментарий в .env и посмотрите, как он отреагирует на блокировку.
  3. Настройте Stop-хук с тестами. Попросите Claude сделать правку, которая ломает один тест, и понаблюдайте: он должен не завершить ход, а исправить падение. Проверьте, что stop_hook_active отрабатывает и цикла нет.

Коротко

  • Хук — ваша команда на событии жизненного цикла; в отличие от CLAUDE.md, выполняется всегда.
  • Основные события: PreToolUse (может блокировать), PostToolUse, UserPromptSubmit, Stop, Notification.
  • Настройки: hooks → событие → matcher → список обработчиков type: command; скрипты в .claude/hooks/.
  • Код выхода 2 блокирует и передаёт stderr Claude; JSON с permissionDecision — структурированное решение.
  • Хуки ужесточают права, но не ослабляют; PreToolUse работает даже в bypassPermissions.
  • Stop-хук с тестами обязан проверять stop_hook_active, иначе зациклится.

Видеоверсия

Сценарий озвучки · 567 слов, ≈ 4 мин

Файл клод-эм-дэ — это просьба. Правила разрешений — фильтр по шаблону. Но есть требования, которые нужно выполнять всегда и с проверкой содержимого: форматировать каждый изменённый файл, не подпускать к миграциям, не давать закончить работу, пока не прошли тесты. Для этого есть хуки. Разберём, как они устроены, и напишем три полезных.

Хук — это ваша команда или скрипт, которые Claude Code запускает сам при наступлении события: перед вызовом инструмента, после него, когда вы отправили запрос, когда Claude собирается завершить ход. Главное отличие от инструкций — хук детерминирован. Инструкция может быть проигнорирована, если контекст переполнен. Хук выполнится в любом случае. Правило простое: хуки — для того, что должно происходить каждый раз без исключений.

Хук получает на вход джейсон с описанием события — какой инструмент, какие аргументы — и отвечает кодом выхода или джейсоном. Код ноль — всё в порядке. Код два — заблокировать действие, а текст ошибки уходит Claude как объяснение, и он подстраивается. Либо код ноль и джейсон с решением: разрешить, запретить, спросить. Из событий чаще всего нужны четыре: перед вызовом инструмента — единственное, что может остановить действие заранее; после вызова; перед завершением хода; и уведомление, когда Claude ждёт вашего ответа.

Настраиваются хуки в тех же файлах, что и права: личный файл в домашней папке, проектный в гите, локальный проектный. Структура такая: событие, потом матчер — фильтр по имени инструмента, например «редактирование или запись», — потом список обработчиков с командой. Посмотреть, что настроено, можно командой «хукс», но менять там нельзя, только в файле. Можно попросить Claude: «напиши хук, который запускает линтер после каждой правки» — он умеет.

Важная деталь о взаимодействии с правами. Хук перед вызовом инструмента срабатывает до проверки режима, в любом режиме, даже при полном обходе проверок. Хук, который сказал «запретить», блокирует и там. Но обратное неверно: «разрешить» от хука не отменяет запрет из настроек. Хуки ужесточают, но не ослабляют.

Первый пример — форматирование. После каждой правки файла вытаскиваем путь из входного джейсона и передаём его форматтеру. В дифах больше нет шума от отступов. Одна строка в настройках.

Второй пример — защита файлов. Скрипт перед каждой правкой проверяет путь по списку: файл с переменными окружения, лок-файл, папка миграций, папка гита. Если совпало — пишет причину в поток ошибок и выходит с кодом два. Правка заблокирована, Claude получил объяснение и меняет подход — например, предлагает вам самому создать миграцию. Правило запрета в настройках сделало бы похожее, но хук умеет то, что правилу недоступно: проверять содержимое, время, ветку, что угодно.

Третий пример — тот самый жёсткий барьер из урока про проверку. Хук на завершение хода запускает тесты. Если они падают, скрипт выходит с кодом два и передаёт последние строки вывода. Claude не может закончить работу и продолжает исправлять. Здесь две детали, без которых хук будет вредить. Во входном джейсоне есть поле «стоп-хук-актив»: оно говорит, что Claude уже продолжил работу по требованию этого хука. Без этой проверки тесты будут гоняться по кругу. Claude Code сам снимет барьер после восьми блокировок подряд, но лучше до этого не доводить. И второе — таймаут: тесты могут идти дольше стандартного лимита, задайте свой.

Если проверка требует суждения, а не команды, есть хук с промптом: он отправляет ваш текст и данные события модели, и та отвечает — всё выполнено или нет, и что осталось. Дешёвый способ добавить самопроверку без скрипта.

И бытовой хук для длинных сессий — уведомление. Когда Claude Code ждёт подтверждения, а вы в другом окне, приходит системное сообщение. Это личный хук, ему место в домашней папке. А командные — форматтер, защита, тесты — коммитьте в проект, чтобы работали у всех одинаково. Только помните: хуки из чужого репозитория выполняются на вашей машине. Читайте, что подключаете.

Отметка хранится только в вашем браузере