Перейти к основному содержимому

Хуки: детерминированная автоматизация

Продвинутый

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

What you'll learn
  • Когда выбирать хук вместо инструкции или разрешения
  • Как настраивается хук: событие, матчер и JSON-нагрузка в stdin
  • Два способа, которыми хук блокирует действие — код выхода 2 против JSON в stdout
  • Хорошие практики и распространённые ошибки, отличающие быстрые и безопасные хуки от медленных и молчаливых

Когда тянуться за хуком

Тянитесь за хуком, когда хотите, чтобы поведение было гарантировано, а не просто запрошено. Каждая типичная задача соответствует событию жизненного цикла:

  • Авто-форматирование / линтинг после каждого редактирования файла (PostToolUse).
  • Блокировка действия, нарушающего правило, до его выполнения (PreToolUse).
  • Уведомление или логирование, когда сессия завершается или задача заканчивается (Stop).
  • Внедрение контекста в начале сессии.
События хуков с первого взгляда
Нажмите Enter или пробел, чтобы перевернуть карточку. Используйте стрелки влево и вправо для перехода между карточками.Показан термин.
1 / 4

Как они работают

Вы регистрируете хуки в settings.json, сопоставляя событие (и часто матчер инструмента). Когда событие срабатывает, Claude запускает вашу команду, передавая JSON-нагрузку на stdin (имя инструмента, его входные данные, сессию). Код выхода и вывод вашей команды решают, что произойдёт дальше.

Guided walkthrough1 of 4
  1. Зарегистрируйте хук в settings.json под нужным вам событием жизненного цикла — например, PostToolUse.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}

Хук выше читает путь отредактированного файла из JSON на stdin (.tool_input.file_path) и форматирует его. Не предполагайте, что путь хранится в переменной окружения — читайте его из stdin. Полезные плейсхолдеры путей, такие как ${CLAUDE_PROJECT_DIR}, доступны для нахождения скриптов.

Как хук блокирует

Два способа, в зависимости от события:

  • Код выхода 2 — хук проваливает действие, и всё, что он записал в stderr, становится сообщением, которое видит Claude. Просто и работает для командных хуков.
  • JSON на stdout (выход 0) — вернуть структурированное решение. Для PreToolUse это permissionDecision со значением deny; для PostToolUse/Stop/и т. д. это {"decision": "block", "reason": "…"}.

Скрипт ниже — это хук PreToolUse на инструменте Bash. Читайте его сверху вниз: он извлекает команду из stdin, и если она выглядит деструктивной, записывает причину в stderr и выходит с кодом 2, чтобы заблокировать.

#!/usr/bin/env bash
# PreToolUse hook on the Bash tool: refuse to delete things.
command=$(jq -r '.tool_input.command' < /dev/stdin)
if [[ "$command" == rm\ * || "$command" == *"rm -rf"* ]]; then
echo "Blocked: destructive 'rm' is not allowed by policy." >&2
exit 2
fi
exit 0

Ментальная модель

Хук PreToolUse запускается перед действием и может его заблокировать; хук PostToolUse запускается после его успешного выполнения и реагирует на результат.

Хорошие практики

  • Держите хуки быстрыми и идемпотентными — они запускаются часто.
  • Шумите громко при настоящих проблемах, но не блокируйте на косметических.
  • Относитесь к выводу хука как к обратной связи для Claude — ясное сообщение помогает ему самостоятельно скорректироваться.
  • Хуки запускаются с привилегиями вашего shell — проверяйте любой хук, который написали не вы (Рецензирование стороннего кода).

Частые ошибки

  • Чтение пути к файлу из переменной окружения. Путь живёт в JSON на stdin (.tool_input.file_path), а не в $CLAUDE_FILE_PATH. Пропускайте stdin через jq.
  • Тихие блокировки. Если хук PreToolUse выходит с кодом 2 без ничего в stderr, Claude заблокирован, но не знает, почему, и не может адаптироваться. Всегда пишите ясную причину.
  • Медленные хуки. Хук PostToolUse запускается после каждого подходящего редактирования. Линтер на 3 секунды делает всю сессию вялой — держите хуки быстрыми и, в идеале, действуйте только на том, что изменилось.
  • Слишком широкие матчеры. matcher: ".*" срабатывает на каждом инструменте. Сужайте точным именем, списком Edit|Write или полем if на уровне обработчика (например, "if": "Bash(git push *)").
  • Доверие хукам, которые написали не вы. Хук запускает произвольный shell с вашими привилегиями. Сначала проверяйте любой хук из плагина или шаблона — см. Рецензирование стороннего кода.
Watch out
  • Хук запускает произвольный shell с вашими привилегиями — никогда не подключайте хук из плагина или шаблона, не прочитав его сначала.

Готовые к копированию заготовки — в Рецептах хуков и settings.json.

Авто-форматирование отредактированных файлов (PostToolUse на Edit|Write)

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

Проверьте себя

0/3
  1. Где хук находит путь только что отредактированного файла?
  2. Хук PreToolUse выходит с кодом 2. Что происходит?
  3. Почему matcher ".*" считается частой ошибкой?
Key takeaways
  • Хуки делают поведение гарантированным, а не запрошенным — они запускают детерминированную логику вокруг действий, которые разрешения лишь позволяют или запрещают.
  • Зарегистрируйте хук в settings.json для события плюс матчер; Claude передаёт JSON-нагрузку на stdin и читает ваш код выхода и вывод.
  • Читайте путь к файлу из stdin (.tool_input.file_path) — а не из переменной окружения.
  • Блокируйте кодом выхода 2 (stderr становится сообщением) или структурированным JSON на stdout (выход 0); всегда включайте ясную причину.
  • Держите хуки быстрыми, идемпотентными и узко сопоставленными — и проверяйте любой хук, который написали не вы, так как он запускается с привилегиями вашего shell.

Далее