
Claude Code — Хуки — как защитить код
Есть разрыв между тем что просишь Claude Code сделать - и что он реально делает. Большую часть времени это нормально. Но иногда нужна гарантия, а не рекомендация. "Не трогай .env" легко написать в CLAUDE.md. Сложнее - обеспечить выполнение.
Хуки - ответ Claude Code на эту проблему. Они позволяют перехватывать жизненный цикл в конкретных точках и внедрять детерминированное поведение: запустить линтер, заблокировать команду, отправить уведомление, отклонить промпт. Разница от инструкций в CLAUDE.md - хуки не зависят от того что Claude читает и помнит. Они срабатывают безусловно, каждый раз, независимо от состояния контекстного окна.
Что хуки на самом деле представляют собой
Хук - это обработчик который прикрепляешь к именованному lifecycle событию. Когда это событие срабатывает, Claude Code передаёт JSON контекст обработчику через stdin (для command хуков) или как HTTP POST тело. Обработчик инспектирует ввод, опционально возвращает решение, и Claude Code действует на него.
Ключевое слово - детерминированный. Инструкция в промпте может быть проигнорирована, сжата из контекста, или понята неверно. Хук который выходит с кодом 2 блокирует вызов инструмента - полностью. Хуки также иммунны к давлению контекстного окна: они срабатывают из конфигурации, не из того что Claude в данный момент держит в памяти.
Конфигурация хуков находится в settings.json, под ключом hooks:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/check-command.sh"
}
]
}
]
}
}
Структура: имя события → массив групп матчеров → каждая группа имеет матчер и массив обработчиков. Матчер фильтрует какие вызовы инструментов активируют группу. Если опустить или использовать "*", группа срабатывает на каждом вхождении события.
События хуков
Около 20 lifecycle событий. Большинство никогда не понадобятся. Стоит знать:
Внутри агентного цикла (срабатывают многократно):
PreToolUse- срабатывает до выполнения вызова инструмента. Может его заблокировать. Главная точка применения правил.PostToolUse- срабатывает после успешного вызова инструмента. Для форматирования, логирования и побочных эффектов.PostToolUseFailure- срабатывает после сбоя вызова инструмента. Для очистки или алертов.UserPromptSubmit- срабатывает когда отправляешь промпт, до того как Claude его обрабатывает. Может внедрять контекст или отклонять промпт.PermissionRequest- срабатывает когда появляется диалог разрешения. Позволяет программно одобрять или отклонять.Stop- срабатывает когда Claude заканчивает ответ. Хорошо для запуска тестов или триггера post-completion работы.
Уровня сессии (срабатывают один раз или при конкретных условиях):
SessionStart- срабатывает при начале или возобновлении сессии.SessionEnd- при завершении сессии. Для очистки или audit логирования.InstructionsLoaded- когда файлCLAUDE.mdили.claude/rules/*.mdзагружается в контекст. Только наблюдение.ConfigChange- когда файл конфигурации изменяется во время сессии.PreCompactиPostCompact- вокруг автоматической компакции контекста.PreCompactпозволяет внедрить резюме до компакции, что влияет на то что сохраняется.
Типы хуков
Command (type: "command") - запускает shell скрипт. Самый простой и распространённый. Получает JSON на stdin, возвращает решения через exit code и stdout.
HTTP (type: "http") - делает POST с JSON события на URL. Хорошо для централизованного логирования или интеграции с внешними сервисами, но добавляет задержку на каждое совпавшее событие. Также актуален для MCP server воркфлоу - PreToolUse и PermissionRequest хуки могут перехватывать MCP вызовы инструментов по имени.
Prompt (type: "prompt") - отправляет событие модели Claude для оценки да/нет. Полезно когда решение требует семантического суждения которое regex не может надёжно принять. Медленнее и потребляет токены.
Agent (type: "agent") - порождает субагент с доступом к инструментам Read, Grep и Glob. Для решений которые требуют инспекции кодовой базы до разрешения вызова инструмента. Самый мощный тип - и самый дорогой.
Exit коды и решения
- Exit 0 - успех. Claude Code парсит stdout на JSON решение и продолжает.
- Exit 2 - блокирующая ошибка. Claude Code игнорирует stdout. Текст stderr передаётся Claude как сообщение об ошибке. Для
PreToolUse- блокирует вызов инструмента. ДляUserPromptSubmit- отклоняет промпт. - Любой другой exit код - неблокирующая ошибка. Хук сбойнул, Claude Code продолжает.
PreToolUse хук который хочет заблокировать команду:
#!/bin/bash
# .claude/hooks/block-env-writes.sh
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
FILE=$(echo "$INPUT" | jq -r '.tool_input.path // empty')
if [[ "$TOOL" == "Write" && "$FILE" == *".env"* ]]; then
echo "Blocked: .env files must not be modified by Claude Code" >&2
exit 2
fi
exit 0
Конфигурация:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-env-writes.sh"
}
]
}
]
}
}
Обратите внимание на префикс $CLAUDE_PROJECT_DIR. Пути хуков в settings.json разрешаются относительно рабочей директории в момент запуска Claude Code - что может варьироваться. Использование переменной окружения гарантирует правильное разрешение пути.
Два паттерна которые стоят затрат на настройку
Авто-форматирование после записи файлов
PostToolUse с матчером Write - запускай форматтер после каждой записи файла, тихо в фоне:
#!/bin/bash
# .claude/hooks/format-on-write.sh
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.path // empty')
if [[ -z "$FILE" ]]; then
exit 0
fi
EXT="${FILE##*.}"
case "$EXT" in
ts|tsx|js|jsx)
npx prettier --write "$FILE" 2>/dev/null
;;
py)
ruff format "$FILE" 2>/dev/null
;;
esac
exit 0
Сделай асинхронным чтобы не блокировать следующий вызов инструмента:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-on-write.sh",
"runInBackground": true
}
]
}
]
}
}
Предостережение: вывод форматтера подаваемый обратно в сессию добавляется к контексту. Если сессия включает много небольших правок файлов и медленный форматтер, повторяющиеся проходы форматирования могут потребить удивительно большую часть контекстного окна.
Уведомление когда Claude простаивает
Событие Notification срабатывает когда Claude Code отправляет уведомление - включая когда ждёт разрешения или закончил длинную задачу:
#!/bin/bash
# .claude/hooks/notify.sh
INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Claude Code needs attention"')
# macOS
osascript -e "display notification \"$MESSAGE\" with title \"Claude Code\""
exit 0
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/notify.sh",
"runInBackground": true
}
]
}
]
}
}
Частые ошибки
Хуки которые срабатывают слишком широко
Хук без матчера срабатывает на каждом вхождении события. Если прикрепить что-то дорогое к PreToolUse без матчера - оно запускается перед каждым одиночным вызовом инструмента.
Поле if внутри обработчика обеспечивает второй уровень фильтрации:
{
"type": "command",
"if": "Bash(rm *)",
"command": ".claude/hooks/check-rm.sh"
}
Проверка if оценивается до порождения обработчика. Если не совпадает — процесс не создаётся.
Забыть про stdin
JSON события приходит на stdin - не как аргументы, не как переменные окружения. Единственная переменная окружения которую устанавливает Claude Code - $CLAUDE_CODE_REMOTE.
# Неправильно
FILE=$1
# Правильно
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.path // empty')
Использование хуков как замены разрешений settings.json
Хуки запускают твой код. Это не система политик - это автоматизация. Хук плюс deny правило в settings.json (сколь бы несовершенным) даёт defence in depth. Хук один по себе - неправильный инструмент для чистой задачи контроля доступа.
Советы
- Используй команду
/hooksвнутри Claude Code сессии для просмотра и переключения настроенных хуков без редактирования JSON напрямую. - Запускай Claude Code с
Ctrl+O(verbose mode) чтобы видеть stdout хуков в терминале. - Помещай скрипты хуков в
.claude/hooks/и ссылайся на них через$CLAUDE_PROJECT_DIR/.claude/hooks/your-script.sh. Коммить их в репо. - Держи скрипты хуков сфокусированными. Скрипт который делает три вещи сложнее дебажить чем три скрипта которые делают одну.
Итог
Хуки дают детерминированный контроль над lifecycle Claude Code - то что инструкции CLAUDE.md одни не могут обеспечить. Два паттерна с которых стоит начать: авто-форматирование при записи с PostToolUse, и Notification хук чтобы перестать бдеть за длинными сессиями. Добавь PreToolUse гарды для файлов которые никогда не должны быть изменены.

Комментарии