Просьба «не забывай прогонять форматтер после правок» в инструкциях проекта работает через раз: модель может её выполнить, а может увлечься задачей и пропустить. Хуки (hooks — «зацепки») решают эту проблему иначе. Это обычные команды вашей системы, которые Claude Code запускает сам в строго определённые моменты своей работы — после записи файла, перед вызовом инструмента, когда сессия стартует или завершается. Разница принципиальная: инструкция в тексте — это просьба к модели, хук — гарантия. Он сработает независимо от того, что модель решила в этот раз.
Ниже — как устроен механизм, где его настраивать, как хук «разговаривает» с агентом и какие сценарии окупаются в первую очередь.
Где живут хуки
Хуки описываются в файлах настроек в формате JSON. Место файла определяет область действия:
~/.claude/settings.json— все ваши проекты, только на этой машине;.claude/settings.jsonв корне репозитория — конкретный проект, файл можно закоммитить и раздать команде;.claude/settings.local.json— тот же проект, но локально, без коммита;- управляемые политики организации — задаются администратором на всю компанию;
hooks/hooks.jsonвнутри плагина — включаются вместе с плагином;- фронтматтер скилла или сабагента — действуют, пока активен соответствующий компонент.
Команда /hooks внутри Claude Code показывает все зарегистрированные хуки, сгруппированные по событиям, — удобно, чтобы понять, откуда прилетел неожиданный запуск. Меню только для чтения: добавлять и править хуки нужно в JSON (или попросить об этом самого Claude). Аварийный рубильник — "disableAllHooks": true в настройках.
Структура конфигурации
Каждое событие — ключ внутри общего объекта hooks. Внутри — массив групп, у каждой группы есть matcher (фильтр) и список обработчиков:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
Это готовый автоформаттер: после каждой правки файла Claude Code передаёт хуку JSON события на стандартный ввод, jq вытаскивает оттуда путь к файлу, а Prettier его переписывает. Если ключ hooks в файле уже есть, новое событие добавляют соседним ключом, а не заменяют весь объект целиком — типичная ошибка при ручной правке.
Что означает matcher, зависит от события. Для инструментальных событий это регулярное выражение по имени инструмента (Bash, Edit, Write, а также MCP-инструменты вида mcp__memory__.*). Для SessionStart — источник запуска (startup, resume, clear, compact, fork). Для Notification — тип уведомления. У части событий фильтра нет вовсе.
События жизненного цикла
Событий в справочнике несколько десятков, но на практике почти всё закрывается небольшим набором:
- SessionStart — сессия началась или возобновилась. Удобно подсовывать агенту актуальный контекст: статус git, номер текущей задачи, содержимое рабочего журнала.
- UserPromptSubmit — вы отправили запрос, но модель его ещё не увидела. Можно дополнить запрос контекстом или заблокировать.
- PreToolUse — перед вызовом инструмента. Единственная точка, где вызов ещё можно остановить.
- PostToolUse и PostToolUseFailure — после успешного вызова и после упавшего. Отменить действие уже нельзя, зато можно отформатировать, залинтить, записать в журнал.
- PermissionRequest — Claude Code собирается спросить у вас разрешение. Хук может ответить за вас.
- Stop и SubagentStop — модель (или сабагент) закончила отвечать. Можно не дать остановиться и вернуть её к работе.
- PreCompact и PostCompact — до и после сжатия контекста; момент, когда часть истории теряется и её полезно переинжектить.
- Notification — Claude Code показывает уведомление: ждёт подтверждения или простаивает.
- SessionEnd — сессия завершается. Учтите жёсткий бюджет: все хуки этого события делят 1,5 секунды (до 60, если явно задать больший
timeout).
Отдельно стоят события вокруг задач и сабагентов (SubagentStart, TaskCreated, TaskCompleted), конфигурации (ConfigChange, InstructionsLoaded, FileChanged) и MCP-элиситаций. Если вы строите свою обвязку вокруг агента, загляните в полный список — там почти наверняка есть нужная точка.
Как хук отвечает агенту
Хук получает JSON события на stdin. Общие поля — session_id, transcript_path, cwd, hook_event_name; дальше идут поля конкретного события: tool_name и tool_input для инструментальных, user_prompt для пользовательского запроса, last_assistant_message для Stop.
Ответить можно двумя способами. Первый — кодом выхода:
- 0 — всё хорошо. Если хук что-то напечатал в stdout в виде JSON, этот JSON будет разобран.
- 2 — блокирующая ошибка. JSON игнорируется, текст из stderr уходит модели как сообщение об ошибке, а действие блокируется — если событие вообще умеет блокировать.
- любой другой — неблокирующая ошибка: текст покажут, но действие продолжится.
Второй способ — вернуть в stdout JSON. Универсальные поля: continue (при false агент останавливается совсем), stopReason, suppressOutput, systemMessage. Тонкое управление живёт в объекте hookSpecificOutput, где обязательно указать hookEventName. Для PreToolUse там доступны permissionDecision (allow, deny, ask, defer), пояснение permissionDecisionReason и даже updatedInput — подменённые аргументы инструмента. Для SessionStart, UserPromptSubmit и большинства прочих — additionalContext, текст, который агент увидит как системную заметку.
Три сценария, которые окупаются сразу
Защита файлов от правок
Скрипт читает stdin, достаёт путь и выходит с кодом 2, если путь попал в список защищённых:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
PROTECTED=(".env" "package-lock.json" ".git/")
for p in "${PROTECTED[@]}"; do
if [[ "$FILE_PATH" == *"$p"* ]]; then
echo "Заблокировано: $FILE_PATH попадает под правило '$p'" >&2
exit 2
fi
done
exit 0
Регистрируется как PreToolUse с матчером Edit|Write и командой "$CLAUDE_PROJECT_DIR"/.claude/hooks/protect-files.sh. Не забудьте chmod +x. Сообщение из stderr придёт модели как обратная связь, и она попробует другой путь вместо того, чтобы упереться в стену.
Уведомление, когда агент ждёт вас
Событие Notification с пустым матчером ловит все типы; матчер permission_prompt — только запросы разрешений, idle_prompt — простой. На macOS команда выглядит как osascript -e 'display notification "Claude Code ждёт ответа"', на Linux — notify-send (нужен пакет libnotify-bin и работающий демон уведомлений: на голом сервере или в контейнере он не заведётся).
Автоодобрение узкого разрешения
Хук PermissionRequest с матчером на конкретный инструмент может ответить вместо вас, вернув JSON с {"decision": {"behavior": "allow"}}. Ключевое слово — узкий: матчер .* или пустой автоматически одобрит вообще всё, включая запись файлов и запуск shell-команд.
Не только shell-команды
Кроме type: "command" есть ещё четыре типа обработчиков. http отправляет событие POST-запросом на ваш эндпоинт — годится для общего аудит-сервиса на команду; заголовки поддерживают подстановку переменных окружения, но только тех, что перечислены в allowedEnvVars. mcp_tool вызывает инструмент подключённого MCP-сервера (подробнее — в заметке о подключении MCP-серверов к Claude Code). prompt отдаёт решение отдельной модели (по умолчанию Haiku), которая возвращает {"ok": true|false, "reason": "..."} — это для случаев, когда нужно суждение, а не жёсткое правило. agent идёт дальше и поднимает сабагента, который может читать файлы и запускать команды, прежде чем вынести вердикт; тип помечен как экспериментальный, для продакшена рекомендуют обычные command-хуки.
Безопасность и грабли
Хук — это код, который выполняется на вашей машине с вашими правами, автоматически и без подтверждения. Отсюда правила: не подключайте чужие конфигурации не глядя, относитесь к .claude/settings.json из pull request как к исполняемому коду и всегда цитируйте пути в кавычках — "$CLAUDE_PROJECT_DIR", а не голая переменная. Данные из tool_input приходят от модели, а значит могут содержать что угодно: не подставляйте их в команды без экранирования. Общая логика тут та же, что и в безопасности AI-агентов: минимум прав, явные границы, журнал.
Из практических граблей: PostToolUse не может отменить уже выполненное действие — блокировать нужно на PreToolUse. Хуки одного события запускаются параллельно, поэтому если два PreToolUse-хука возвращают updatedInput для одного инструмента, победит тот, кто закончил последним, — порядок недетерминирован. Stop срабатывает после каждого ответа модели, а не только по завершении задачи, и не срабатывает при прерывании пользователем. Таймаут по умолчанию — 600 секунд для command/http/mcp_tool, но UserPromptSubmit урезает его до 30 секунд, а MessageDisplay — до 10.
Если хук «не сработал», проверьте по порядку: виден ли он в /hooks, исполняемый ли файл, есть ли в системе jq, правильный ли матчер (регистр имени инструмента важен) и валиден ли JSON настроек. Запуск с --debug показывает, какие хуки подобрались к событию и с каким кодом завершились.
Что запомнить
Хуки закрывают ровно ту дыру, которую не закрывают инструкции: детерминированность. Всё, что должно происходить всегда, — форматирование, линт, защита секретов, журналирование, уведомления — переносите из текстовых правил в хуки, а модели оставляйте то, что требует решения. Разумный порядок внедрения: начать с одного PostToolUse на форматтер, добавить PreToolUse на защиту чувствительных файлов, потом уведомления. Более тонкие вещи вроде подмены аргументов и prompt-хуков лучше подключать, когда база уже работает и вы понимаете, что именно в вашем процессе повторяется каждый день.
Если вы только начинаете с инструментом, полезно сначала пройти практический гайд по Claude Code и настроить правила проекта в CLAUDE.md — хуки хорошо ложатся поверх уже выстроенного процесса, но не заменяют его.
Частые вопросы
Инструкция — это просьба к модели, которую она может выполнить или пропустить. Хук — команда, которую Claude Code запускает сам в заданный момент жизненного цикла, независимо от решения модели. Всё, что должно происходить всегда (форматирование, защита файлов, журналирование), надёжнее вынести в хуки.
Только на событии PreToolUse: это единственная точка, где вызов инструмента ещё не выполнен. Скрипт должен завершиться с кодом 2 — тогда вызов блокируется, а текст из stderr уходит модели как объяснение. PostToolUse отменить действие уже не может.
Да. Положите конфигурацию в .claude/settings.json в корне репозитория и закоммитьте — хуки подхватятся у всех. Для организации целиком есть управляемые политики, а для переиспользования между проектами — плагины с файлом hooks/hooks.json.
Хук выполняется на вашей машине с вашими правами и без подтверждения, поэтому чужой .claude/settings.json нужно читать так же внимательно, как исполняемый код. Цитируйте пути в кавычках, не подставляйте данные из tool_input в команды без экранирования и держите под рукой аварийный выключатель disableAllHooks.
Источники
- 1.Claude Code Docs — Hooks referencehttps://code.claude.com/docs/en/hooks
- 2.Claude Code Docs — Automate actions with hookshttps://code.claude.com/docs/en/hooks-guide
- 3.Claude Code Docs — Settingshttps://code.claude.com/docs/en/settings



