Hooks — события жизненного цикла
Если навыки и субагенты расширяют то, что умеет Claude, то хуки контролируют как он это делает — и делают это детерминированно. Хук — это обычная shell-команда (или HTTP-запрос, MCP-инструмент, даже вызов LLM), которую Claude Code запускает автоматически в определённые моменты жизненного цикла сессии. Модель не участвует в этом решении: хук срабатывает всегда, независимо от того, что думает Claude.
Это принципиальное отличие от инструкций в CLAUDE.md или навыках. Написать «всегда запускай prettier после редактирования файлов» — это просьба, которую Claude может проигнорировать в длинной цепочке инструментов. Хук с тем же правилом — это гарантия.
Анатомия хука
Хуки живут в "hooks" внутри любого settings.json (пользовательского, проектного или локального). Структура трёхуровневая:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh",
"timeout": 30
}
]
}
]
}
}- Ключ верхнего уровня — имя события (
PostToolUse,PreToolUse,SessionStartи т.д.) - Матчер (
matcher) — фильтр: к каким инструментам или ситуациям применять."Edit|Write"— только для этих двух инструментов;"Bash(rm *)"— только для bash-команд сrm;"mcp__github__.*"— для всех инструментов GitHub MCP-сервера. - Массив
hooks— список команд, выполняющихся последовательно при совпадении.
Хуки можно коммитить в репозиторий (.claude/settings.json) — команда получает их вместе с кодом. Персональные правила (пути к локальным скриптам, токены) — в .claude/settings.local.json, который попадает в .gitignore.
flowchart TD
A[Открытие сессии] --> B[[SessionStart]]
B --> C[Пользователь вводит промпт]
C --> D[[UserPromptSubmit]]
D --> E{Агентный цикл}
E --> F[[PreToolUse]]
F --> G{Заблокирован?}
G -- нет --> H[Инструмент выполняется]
G -- да --> E
H --> I[[PostToolUse]]
I --> E
E --> J[[Stop]]
J --> K{Остановиться?}
K -- нет --> E
K -- да --> L[Ответ готов]
L --> C
C --> M[[SessionEnd]]Типы событий
События делятся на три ритма:
Сессионные (один раз за сессию):
SessionStart— сразу после открытия или возобновления. Удобен для загрузки окружения и подтягивания актуальных данных.SessionEnd— при закрытии. Для логирования, очистки временных файлов.Setup— приinitилиmaintenance.
На каждый промпт (один раз на пользовательский запрос):
UserPromptSubmit— сразу после отправки промпта, до обработки Claude. Можно блокировать или модифицировать.Stop— когда Claude собирается завершить ответ. Можно заставить его продолжить.
Агентный цикл (на каждый вызов инструмента):
PreToolUse— до выполнения. Самый мощный: можно заблокировать вызов, изменить аргументы или залогировать.PostToolUse— после выполнения. Инструмент уже отработал; можно добавить контекст или инициировать форматирование.
Как хук общается с Claude Code
Хук-скрипт получает на stdin JSON с контекстом события. Для PreToolUse это выглядит примерно так:
{
"session_id": "abc123",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf ./dist"
},
"cwd": "/home/user/myproject",
"permission_mode": "default"
}Хук возвращает решение через код выхода и JSON в stdout:
| Код выхода | Значение |
|---|---|
0 | Успех. Stdout парсится как JSON с инструкциями. |
2 | Блокирующая ошибка. Stderr передаётся Claude как контекст. |
| Любой другой | Не блокирующая ошибка; сессия продолжается, в лог пишется предупреждение. |
Критически важно: JSON в stdout обрабатывается только при exit 0. Если скрипт упал с кодом 1 — его вывод игнорируется.
Блокировка и модификация
Самый частый сценарий для PreToolUse — заблокировать опасный вызов:
#!/bin/bash
# .claude/hooks/block-destructive.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -qE 'rm -rf|DROP TABLE|truncate'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Деструктивная команда заблокирована политикой проекта"
}
}'
else
exit 0
fiПри exit 0 без JSON-вывода Claude Code просто продолжает работу. Хук обязан что-то писать в stdout только тогда, когда хочет повлиять на поведение.
Второй вариант — не блокировать, а изменить аргументы инструмента. Поле updatedInput подменяет то, что получит инструмент:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": { "command": "npm run build -- --no-cache" }
}
}Для PostToolUse блокировка недоступна (инструмент уже отработал), зато можно добавить дополнительный контекст, который Claude увидит рядом с результатом:
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Этот файл генерируется автоматически. Редактируй src/schema.ts и запусти npm run generate."
}
}Три практических паттерна
1. Автоформатирование после редактирования
Классический случай: Claude написал код, хук тут же форматирует файл.
#!/bin/bash
# .claude/hooks/auto-format.sh
FILE=$(cat | jq -r '.tool_input.file_path // empty')
if [[ "$FILE" =~ \.(ts|tsx|js|jsx)$ ]]; then
prettier --write "$FILE" 2>/dev/null
fi
exit 0{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/auto-format.sh", "timeout": 15 }]
}]
}
}2. Линт-гейт: заставить Claude починить то, что сломано
PostToolUse не может отменить уже выполненный инструмент, но exit code 2 передаёт ошибку обратно Claude:
#!/bin/bash
# .claude/hooks/lint-gate.sh
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ "$FILE" =~ \.ts$ ]]; then
if ! npx tsc --noEmit --skipLibCheck "$FILE" 2>/dev/null; then
npx tsc --noEmit --skipLibCheck "$FILE" >&2
exit 2
fi
fi
exit 0При exit 2 Claude Code передаёт stderr обратно модели. Claude видит ошибки TypeScript и обычно сразу их исправляет.
3. Аудит: лог всех действий агента
#!/bin/bash
# ~/.claude/hooks/audit.sh
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | $(cat | jq -c '.')" \
>> ~/.claude/audit.jsonl
exit 0{
"hooks": {
"PreToolUse": [{
"hooks": [{
"type": "command",
"command": "${HOME}/.claude/hooks/audit.sh",
"async": true
}]
}]
}
}Поле "async": true запускает скрипт фоново, не блокируя агентный цикл — идеально для логирования, где задержка не нужна.
SessionStart: подготовка окружения
SessionStart — единственное событие, которое может внедрить контекст до первого промпта. Это делает его незаменимым для подгрузки актуальных данных:
#!/bin/bash
# .claude/hooks/session-start.sh
BRANCH=$(git branch --show-current 2>/dev/null || echo "неизвестна")
UNCOMMITTED=$(git status --short 2>/dev/null | wc -l | tr -d ' ')
LAST_COMMIT=$(git log --oneline -1 2>/dev/null || echo "нет коммитов")
jq -n \
--arg branch "$BRANCH" \
--arg uncommitted "$UNCOMMITTED" \
--arg last "$LAST_COMMIT" '{
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: ("Ветка: " + $branch + "\nНезакоммиченных файлов: " + $uncommitted + "\nПоследний коммит: " + $last),
sessionTitle: $branch
}
}'Claude начинает сессию уже зная контекст репозитория — без лишних вопросов и git status в первом запросе.
Кроме additionalContext, SessionStart умеет:
- Устанавливать переменные окружения через
CLAUDE_ENV_FILE - Задавать заголовок сессии (
sessionTitle) - Перезагружать навыки (
reloadSkills: true) — полезно если навыки хранятся в git и обновляются
Типы хуков помимо command
Документация описывает пять типов, command — лишь самый распространённый:
| Тип | Что делает |
|---|---|
command | Shell-скрипт. Читает stdin, пишет stdout. |
http | POST-запрос на endpoint. Тело — тот же JSON контекста. |
mcp_tool | Вызывает инструмент на подключённом MCP-сервере. |
prompt | Однотурновый LLM-запрос, возвращает yes/no решение. |
agent | Запускает субагент с доступом к инструментам для сложной проверки. |
http хук особенно полезен для команд: централизованный сервер получает все события, логирует их, применяет политики — и всё это без установки скриптов на каждой машине.
Просмотр всех хуков
Команда /hooks в Claude Code открывает read-only браузер хуков — показывает все настроенные правила, их события, матчеры, типы и источники (User / Project / Local / Plugin). Незаменимо при отладке: сразу видно, какой хук сработает на каком событии и нет ли конфликтующих правил из разных уровней иерархии.
See also
- Skills — переносимые навыки — навыки дают инструкции Claude; хуки обеспечивают выполнение — вместе они перекрывают большинство сценариев кастомизации
- Слэш-команды: встроенные и кастомные — легковесные шаблоны промптов; хуки дополняют их детерминированным поведением
- Субагенты и контекстная изоляция — хук типа
agentсам по себе запускает субагент - Plugins и marketplace — плагин пакует хуки вместе с командами и навыками в одну устанавливаемую единицу
- Что выбрать: команда, навык, субагент, MCP или хук — полная матрица выбора механизма расширения
- Модель разрешений, безопасность и доверие — хуки взаимодействуют с системой разрешений;
PreToolUseможет расширять или сужать её - Настройки и иерархия конфигурации — хуки живут в settings.json, приоритет наследуется по той же иерархии
- Headless-режим и скриптинг через CLI — в headless-режиме хуки работают так же, открывая путь к полной автоматизации