Хуки: детерминированная автоматизация
Хуки — это shell-команды, которые Claude Code запускает автоматически в определённых точках своего жизненного цикла. Там, где разрешения решают, разрешено ли действие, хуки позволяют вам выполнять детерминированную логику вокруг него — форматирование, валидацию, логирование, проверки. Именно так вы делаете поведение гарантированным, а не «пожалуйста, не забудьте».
- Когда выбирать хук вместо инструкции или разрешения
- Как настраивается хук: событие, матчер и JSON-нагрузка в stdin
- Два способа, которыми хук блокирует действие — код выхода 2 против JSON в stdout
- Хорошие практики и распространённые ошибки, отличающие быстрые и безопасные хуки от медленных и молчаливых
Когда тянуться за хуком
Тянитесь за хуком, когда хотите, чтобы поведение было гарантировано, а не просто запрошено. Каждая типичная задача соответствует событию жизненного цикла:
- Авто-форматирование / линтинг после каждого редактирования файла (
PostToolUse). - Блокировка действия, нарушающего правило, до его выполнения (
PreToolUse). - Уведомление или логирование, когда сессия завершается или задача заканчивается (
Stop). - Внедрение контекста в начале сессии.
Как они работают
Вы регистрируете хуки в settings.json, сопоставляя событие (и часто матчер инструмента). Когда событие срабатывает, Claude запускает вашу команду, передавая JSON-нагрузку на stdin (имя инструмента, его входные данные, сессию). Код выхода и вывод вашей команды решают, что произойдёт дальше.
- Зарегистрируйте хук в settings.json под нужным вам событием жизненного цикла — например, PostToolUse.
- Добавьте матчер инструмента, чтобы хук срабатывал только на релевантных инструментах, например matcher "Edit|Write" для редактирования файлов.
- Когда событие срабатывает, Claude запускает вашу команду и передаёт JSON-нагрузку на stdin — имя инструмента, его входные данные, сессию.
- Код выхода и вывод вашей команды определяют результат: позволить действию продолжиться, запустить вашу логику или заблокировать его.
{
"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 с вашими привилегиями. Сначала проверяйте любой хук из плагина или шаблона — см. Рецензирование стороннего кода.
- Хук запускает произвольный 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- Хуки делают поведение гарантированным, а не запрошенным — они запускают детерминированную логику вокруг действий, которые разрешения лишь позволяют или запрещают.
- Зарегистрируйте хук в settings.json для события плюс матчер; Claude передаёт JSON-нагрузку на stdin и читает ваш код выхода и вывод.
- Читайте путь к файлу из stdin (.tool_input.file_path) — а не из переменной окружения.
- Блокируйте кодом выхода 2 (stderr становится сообщением) или структурированным JSON на stdout (выход 0); всегда включайте ясную причину.
- Держите хуки быстрыми, идемпотентными и узко сопоставленными — и проверяйте любой хук, который написали не вы, так как он запускается с привилегиями вашего shell.
Далее
- settings.json · Разрешения
- Навыки — экспертиза против автоматизации
- Усиление автономных запусков