Что такое хуки
Хуки - заданные пользователем команды оболочки, HTTP-эндпоинты, вызовы MCP-инструментов, LLM-промпты или субагенты, которые выполняются автоматически в конкретных точках жизненного цикла Claude Code. Они позволяют автоматизировать действия, применять политики и связывать Claude Code с внешними системами - без того, чтобы каждый раз напоминать об этом словами в разговоре.
Три частоты событий
События хуков делятся на три группы по тому, как часто они срабатывают.
Раз за сессию: SessionStart - сессия начинается или возобновляется, SessionEnd - сессия завершается.
Раз за реплику: UserPromptSubmit - промпт отправлен, ещё до того, как Claude начнёт его обрабатывать; Stop - Claude закончил отвечать; StopFailure - ответ завершился с ошибкой.
На каждый вызов инструмента: PreToolUse - перед выполнением вызова, может его заблокировать; PostToolUse - после успешного выполнения; PostToolUseFailure - после неудачного вызова (кроме вызовов EndConversation).
Как настраивается
Хуки описываются в JSON-файле настроек с тремя уровнями вложенности: название события, затем matcher (какие инструменты или условия подходят), и дальше список самих хуков с типом и командой.
{
"hooks": {
"НАЗВАНИЕ_СОБЫТИЯ": [
{
"matcher": "ИМЯ_ИНСТРУМЕНТА",
"hooks": [
{
"type": "command",
"command": "/путь/к/скрипту.sh",
"args": []
}
]
}
]
}
}Хуки можно прописать на трёх уровнях: ~/.claude/settings.json - для всех своих проектов на этой машине, .claude/settings.json - для одного проекта, с возможностью поделиться настройкой через контроль версий, и .claude/settings.local.json - тоже для одного проекта, но без попадания в git.
Что скрипт получает и что возвращает
Хук получает контекст события в формате JSON через стандартный ввод - с общими полями (идентификатор сессии, путь к рабочей папке, режим разрешений) и полями, специфичными для конкретного события. Для PreToolUse, например, это ещё и название вызываемого инструмента вместе с его входными параметрами:
{
"session_id": "abc123",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /tmp/build"
}
}Управлять поведением можно двумя способами: кодом возврата и структурированным JSON на выходе. Код 0 означает успех - вывод обрабатывается, если это валидный JSON; код 2 - блокирующая ошибка, которая останавливает действие; любой другой код - не блокирует, действие продолжается, если хук не вернул явное переопределение через JSON.
Практический пример: блокировка опасных команд
Ниже - хук, который проверяет каждую команду Bash перед выполнением и блокирует конкретно rm -rf.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}#!/bin/bash
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0
fiДальше файл нужно сделать исполняемым: chmod +x .claude/hooks/block-rm.sh. Логика по шагам: событие PreToolUse срабатывает перед выполнением Bash-команды, matcher Bash отбирает нужный тип вызова, условие if дополнительно сужает круг до команд с rm, скрипт получает JSON, разбирает саму команду и, если в ней найдено rm -rf, возвращает решение deny с причиной - вызов блокируется ещё до выполнения.
Чек-лист
- Выбрано подходящее событие (SessionStart, PreToolUse, Stop и т.д.)
- Matcher сужен до нужного инструмента, а не оставлен универсальным без причины
- Скрипт хука сделан исполняемым (chmod +x)
- Хук проверен на реальном срабатывании события, а не только по коду
- Настройка сохранена на нужном уровне: личном, проектном публичном или проектном локальном