Как работают хуки в Claude Code: настройка и примеры
Узнайте, как хуки Claude Code принудительно выполняют команды независимо от модели. Разбираем события, обработку кода завершения 2 и передачу данных через stdin и stderr.
Что такое хук Claude Code
Хуки Claude Code — это shell-команды, которые Claude Code выполняет самостоятельно в заданные моменты своего жизненного цикла. В этом заключается главное отличие хука от файла правил. Инструкция в CLAUDE.md — это рекомендация, которую модель сопоставляет со всем остальным контекстом. Хук — это код, и он выполняется независимо от того, согласна с этим модель или нет. Если ваш агент постоянно игнорирует форматировщик, о котором вы упоминали дважды, вам не нужна более строгая инструкция. Вам нужен хук.
Механизм прост. Вы регистрируете команду в файле настроек под именем события. Когда это событие происходит, Claude Code запускает вашу команду и передает данные события на стандартный ввод (stdin) в формате JSON (JavaScript object notation). Ваша команда считывает эти данные, выполняет свою задачу и возвращает статус завершения. Код завершения 2 из хука PreToolUse отменяет вызов инструмента до его выполнения, а всё, что ваш скрипт записал в стандартный поток ошибок (stderr), передается модели в качестве причины.
Имена событий и полей здесь взяты из справочника по хукам Claude Code, актуального на август 2026 года для версии 2.1.232. Этот интерфейс быстро меняется, поэтому перед копированием JSON из любых публикаций, включая эту, сверяйтесь со справочником для вашей версии. Выведите свои настройки с помощью claude --version.
Где хранятся настройки хуков
Хук представляет собой JSON-блок в файле настроек. Его можно разместить в одном из шести мест, при этом область действия файла определяет область действия хука.
~/.claude/settings.json: для всех проектов на вашей машине, но не на других..claude/settings.json: для одного проекта, с фиксацией в репозитории, поэтому хук будет у всех, кто клонирует проект..claude/settings.local.json: для одного проекта, только на вашей машине.- Управляемые настройки политики: для всей организации, задаются администратором.
hooks/hooks.jsonвнутри плагина, действуют, пока плагин включен.- Frontmatter навыка или субагента, действуют, пока активен этот компонент.
Записи хуков из этих файлов объединяются, а не перезаписывают друг друга. Файл настроек проекта добавляет свои хуки к тем, что указаны в пользовательских настройках, а не заменяет их, поэтому одно событие может содержать несколько хуков из разных файлов. Параметр "disableAllHooks": true отключает их, за одним исключением: хуки из управляемых настроек политики продолжают выполняться, если только этот параметр не применен также в управляемых настройках.
Выполните /hooks в рамках сессии, чтобы вывести список всех зарегистрированных на данный момент хуков, сгруппированных по событиям, с указанием исходного файла и матчера для каждого. Меню доступно только для чтения, поэтому для изменения хука необходимо отредактировать файл настроек. Файловый наблюдатель обычно подхватывает изменения без перезапуска.
Какие события хуков существуют в Claude Code
В релизе 2.1.232 перечислено тридцать одно событие, от SessionStart до SessionEnd, которые охватывают процессы сжатия (compaction), работу с субагентами, рабочими деревьями (worktrees) и файлами конфигурации. При работе с сервером используется лишь небольшая часть из них.
PreToolUse: перед выполнением вызова инструмента. Это событие позволяет блокировать выполнение.PostToolUse: после успешного вызова инструмента.PostToolUseFailureсрабатывает при ошибке, поэтому хук, который должен отслеживать любой результат, требует обработки обоих событий.PermissionRequest: когда для вызова инструмента требуется решение о предоставлении прав доступа; в этот момент появляется запрос на подтверждение.UserPromptSubmit: при отправке промпта, до того как Claude начнёт его обработку. Всё, что этот хук выводит в stdout, добавляется в контекст модели.SessionStartиSessionEnd: в начале и конце сессии соответственно.SessionStartтакже срабатывает после сжатия данных, при условии соответствия значениюcompact.Stop: когда Claude завершает ответ. Это происходит один раз за итерацию (turn), а не один раз за выполнение всей задачи.
Каждая группа содержит matcher, который определяет, в каких случаях запускается хук. Для событий инструментов фильтрация происходит по имени инструмента, поэтому "Edit|Write" срабатывает только при редактировании файлов и больше нигде. Матчеры (matchers) чувствительны к регистру. Пустой матчер срабатывает при каждом событии. Инструменты с сервера MCP (model context protocol) имеют префикс mcp__<server>__<tool>, поэтому матчер "mcp__github__.*" перехватывает инструменты конкретного сервера, не затрагивая остальные.
Хуки Stop имеют особенность, о которой следует знать перед написанием кода. Блокирующий хук Stop возвращает модель к работе, при этом Claude Code отключает хук после восьми блокировок подряд. Считывайте поле stop_hook_active из входных данных хука и завершайте работу с кодом 0, если оно имеет значение true, иначе хук будет зациклен до достижения указанного лимита.
Что хук получает через stdin
Когда Claude собирается выполнить npm test, хук PreToolUse на Bash считывает следующие данные через stdin:
{
"session_id": "abc123",
"cwd": "/home/deploy/myproject",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}Каждое событие содержит session_id, cwd, permission_mode, transcript_path и hook_event_name. События инструментов добавляют tool_name, tool_input и tool_use_id. Другие события содержат собственные поля: UserPromptSubmit получает текст prompt, а SessionStart получает source из startup, resume, clear, compact или fork.
jq — это стандартный способ чтения таких данных внутри shell-скрипта, однако в минимальном образе сервера эта утилита отсутствует. Сначала установите её с помощью sudo apt install -y jq в Ubuntu или Debian.
Как код завершения влияет на вызов инструмента во время выполнения
Существует три возможных исхода.
- Код завершения 0 означает, что ваш хук не имеет возражений. В случае
PreToolUseэто не равносильно одобрению, и стандартный процесс проверки прав продолжает выполняться. В случаеUserPromptSubmitиSessionStartсодержимое stdout добавляется в контекст модели. - Код завершения 2 блокирует действие для событий, которые можно заблокировать (включая
PreToolUse), а содержимое stderr становится причиной, которую видит модель. Для событий, которые нельзя заблокировать (например,PostToolUse), блокировка игнорируется, хотя stderr всё равно передаётся модели в качестве обратной связи. - Любой другой код завершения является неблокирующей ошибкой. Действие выполняется. В протоколе отображается уведомление об ошибке хука, содержащее первую строку stderr после текста
Failed with non-blocking status code:.
Если вам нужно не просто заблокировать действие или промолчать, выведите в stdout JSON-объект с кодом завершения 0. Хук PreToolUse принимает решение с помощью permissionDecision:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Database drops go through a migration, not through the agent."
}
}"allow" пропускает интерактивный запрос, "deny" отменяет вызов и отправляет причину модели, а "ask" отображает запрос в обычном режиме. Выбирайте один стиль для каждого хука. Сочетание кода завершения 2 с JSON-решением в stdout приведёт к результату, который потребует дополнительной проверки.
Если с одним событием сопоставлено несколько хуков, они выполняются параллельно, и каждый из них доводится до завершения. deny от одного хука не останавливает выполнение остальных: например, хук логирования запишет свою строку, даже если хук-ограничитель (guardrail) отклонит тот же вызов. Затем Claude Code объединяет ответы и выбирает наиболее строгий из них, соблюдая приоритет: отклонить (deny), отложить (defer), спросить (ask), разрешить (allow).
Пример 1: блокировка деструктивной команды до её выполнения
Сохраните этот файл как .claude/hooks/block-destructive.sh в вашем проекте:
#!/bin/bash
# Deny a Bash tool call whose command matches a banned pattern.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
for pattern in 'rm -rf /' 'mkfs' 'dd if=' 'DROP TABLE'; do
if printf '%s' "$COMMAND" | grep -qiF -- "$pattern"; then
echo "Blocked by policy: the command matches '$pattern'. A human runs this one." >&2
exit 2
fi
done
exit 0Сделайте его исполняемым, а затем зарегистрируйте в PreToolUse в .claude/settings.json:
chmod +x .claude/hooks/block-destructive.sh{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive.sh",
"timeout": 10,
"statusMessage": "Checking the command against policy"
}
]
}
]
}
}Протестируйте скрипт вручную, прежде чем доверять ему, так как хук, который аварийно завершается при получении входных данных, по умолчанию разрешает выполнение:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /var/lib/postgresql"}}' \
| .claude/hooks/block-destructive.sh
echo $?Вы должны увидеть строку Blocked by policy: в stderr и код завершения 2. Передайте ему безопасную команду, например ls -la, и вы не должны увидеть никакого вывода, а код завершения должен быть 0. В сеансе заблокированный вызов появится в протоколе с вашим сообщением в качестве причины, модель прочитает это сообщение и адаптируется.
Одно свойство делает этот подход полезным: хуки PreToolUse срабатывают до проверки прав доступа и во всех режимах разрешений, поэтому запрет действует даже при bypassPermissions. Именно это делает хук полезным в сочетании с автоматическим режимом Claude Code и его настройками разрешений, где подсказки (prompts) отключены, но хук всё равно срабатывает.
Будьте реалистичны в оценке этого инструмента. Сопоставление шаблонов в строке команды — это защитный барьер против неосторожности агента, а не граница против намеренных действий, так как одну и ту же команду можно написать в форме, которую ваш grep никогда не увидит. Жёсткие правила должны быть реализованы в системе разрешений и в учётной записи, от имени которой выполняется процесс.
Пример 2: форматирование и линтинг после каждого изменения
PostToolUse с матчером Edit|Write запускается после любого инструмента редактирования файлов. Сохраните это как .claude/hooks/after-edit.sh:
#!/bin/bash
# Format the edited file, then report lint failures back to the model.
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0
case "$FILE" in
*.py)
ruff format "$FILE" >/dev/null 2>&1
if ! ruff check "$FILE" >&2; then
exit 2
fi
;;
*.sh)
if ! shellcheck "$FILE" >&2; then
exit 2
fi
;;
esac
exit 0{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/after-edit.sh",
"timeout": 60
}
]
}
]
}
}Попросите Claude добавить функцию с нарушенными отступами в Python-файл, а затем откройте этот файл. Он вернётся отформатированным. Это подтверждает, что хук сработал, так как успешное выполнение хука не отображается в диалоге.
Код выхода 2 здесь ничего не отменяет. PostToolUse срабатывает уже после того, как инструмент завершил работу, поэтому изменения в любом случае записаны на диск. Код выхода 2 нужен для того, чтобы вывод ruff check попал к модели в качестве обратной связи. Благодаря этому модель исправляет только что допущенную ошибку, вместо того чтобы продолжать работу. В этом заключается разница между ошибкой линтинга, которую вы обнаруживаете при коммите, и ошибкой, которую агент исправляет в рамках одного и того же шага.
Здесь важны два ограничения матчеров. Edit|Write не видит файлы, изменённые с помощью shell-команд, а Claude достаточно часто записывает файлы через Bash, чтобы этот пробел стал существенным. Для покрытия каждого вызова добавьте в матчер Bash и настройте скрипт так, чтобы он выводил список изменённых файлов через git status --porcelain. Для покрытия один раз за шаг (turn) поместите сканирование в хук Stop.
Пример 3: логирование каждого вызова инструмента для аудита
Пустой матчер в PostToolUse срабатывает для каждого инструмента. Отправка записи в системный журнал вместо файла в домашнем каталоге защищает её от доступа со стороны самой оболочки агента:
{
"hooks": {
"PostToolUse": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "jq -c '{time: now|todate, session: .session_id, cwd: .cwd, tool: .tool_name, input: .tool_input}' | logger -t claude-code -p local0.info"
}
]
}
]
}
}Прочитайте записи с помощью journalctl -t claude-code -o cat | tail -n 5. Вы увидите по одной строке JSON на каждый вызов инструмента, самые новые — в конце. Если ничего не отображается, значит, хук не сработал; причины этого описаны в разделе по устранению неполадок ниже.
Добавьте такой же блок в PostToolUseFailure, чтобы фиксировать неудачные вызовы, так как PostToolUse срабатывает только при успешном выполнении, а именно неудачные команды обычно представляют наибольший интерес. Причина использования logger вместо добавления данных в файл в домашнем каталоге заключается в правах доступа: хук выполняется от имени того же пользователя, что и оболочка агента, поэтому всё, во что этот пользователь может записывать данные, он может и удалить. Журнал записывается процессом systemd-journald от имени собственной учётной записи.
Продолжительность выполнения хука
The data behind this chart
[
{
"label": "command, http or mcp_tool hook",
"default_timeout_seconds": 600
},
{
"label": "agent hook",
"default_timeout_seconds": 60
},
{
"label": "prompt hook",
"default_timeout_seconds": 30
},
{
"label": "command hook on UserPromptSubmit",
"default_timeout_seconds": 30
},
{
"label": "command hook on MessageDisplay",
"default_timeout_seconds": 10
},
{
"label": "any hook on SessionEnd",
"default_timeout_seconds": 1.5
}
]Команда хука по умолчанию получает 600 секунд, что составляет десять минут. Некоторые события значительно сокращают это время. Хуки SessionEnd делят между собой общий бюджет в 1.5 секунд, поэтому очистка в конце сессии должна выполняться быстро. Однако установка более длительного значения timeout для хука увеличивает этот общий бюджет до 60 секунд.
Хук, превысивший время ожидания, отменяется и не выносит решения. Для guardrail типа PreToolUse это означает отсутствие блокировки: вызов инструмента продолжает выполняться в рамках стандартного процесса проверки прав. По этой причине скрипты guardrail должны быть небольшими. Для медленных задач, которые не требуют немедленного ответа (например, отправка логов), используйте "async": true: тогда хук будет выполняться в фоновом режиме, не задерживая вызов инструмента.
Хуки, файлы правил, навыки и MCP-серверы
Четыре понятия часто путают между собой, так как все они меняют поведение агента. Однако только одно из них перестает быть просто рекомендацией.
Файл правил (CLAUDE.md или файл в директории .claude/rules/) — это текст, загружаемый в контекст модели. Он формирует поведение, но ничего не принуждает. В условиях длинного диалога, большого diff-файла и нового запроса пользователя одна строка из этого файла может быть проигнорирована. Это обычная причина, по которой агенты игнорируют написанные вами инструкции.
Навык (skill) — это папка с инструкциями и скриптами, которую модель загружает, когда считает навык уместным. Это суждение является сутью навыка, но в нем же кроется и ограничение: модель по-прежнему принимает решение сама. Обе стороны этого процесса видны в таком навыке, как Ponytail, который подталкивает агента к внесению минимально необходимых изменений. Он формирует подход к выполнению всей задачи так, как не смог бы ни один хук, и работает только до тех пор, пока модель решает его использовать.
MCP-сервер (Model Context Protocol) предоставляет модели новые инструменты для вызова. Он расширяет возможности доступа агента. Он не заставляет агента что-либо предпринимать, к тому же это отдельный процесс, который нужно обслуживать, что само по себе является отдельной задачей: см. запуск MCP-серверов на VPS.
Хук — единственный из четырех механизмов, который срабатывает без участия модели. Используйте файл правил для задания предпочтений, а навык — для процедур, которым модель должна следовать при их применении. Используйте хук для шагов, которые должны выполняться всегда, или для действий, которые никогда не должны происходить. Более глубокое сравнение, включая случаи, когда навык эффективнее файла правил, приведено в сравнении навыков, MCP и файлов правил.
Плагин — это скорее способ упаковки, а не пятый механизм. Он объединяет хуки и навыки в единый устанавливаемый модуль. Именно так команда распространяет одни и те же правила безопасности на все машины: см. как работают плагины Claude Code.
Вопросы безопасности на общем VPS
Хук — это код, который запускается агентом от имени пользователя, запустившего Claude Code. Он наследует окружение и права доступа этого пользователя. На ноутбуке это вопрос рабочего процесса. На VPS, где агент работает без присмотра, это вопрос безопасности, состоящий из четырех практических аспектов.
Хук в репозитории — это код, который написали не вы. .claude/settings.json фиксируется в коммитах, поэтому клонирование репозитория и начало сессии внутри него могут зарегистрировать хуки, которые уже были в этом репозитории. Claude Code блокирует проектные хуки до подтверждения доверия к рабочей области для этой папки; это означает, что, подтверждая доверие, вы принимаете решение об их запуске. Сначала ознакомьтесь с блоком hooks.
Хук видит все входные данные инструментов. Аудиторский хук, который записывает tool_input, сохраняет каждый аргумент каждой команды в файл, включая любые токены, которые могли оказаться в командной строке. Этот лог требует такой же защиты, как и сам секрет, что является частью более широкой проблемы защиты секретов от доступа AI-агента.
Хук может записывать данные в контекст модели. Все, что хук SessionStart или UserPromptSubmit выводит в stdout, добавляется в диалог. Хук, который передает текст извне, из системы отслеживания задач или лог-файла, передает модели недоверенный текст так, будто вы ввели его самостоятельно. Хук, пересылающий заметку из другой сессии Claude Code на том же VPS, делает то же самое, и вывод одного агента заслуживает не больше доверия, чем данные из системы отслеживания задач. Относитесь к этому stdout как к входным данным, а не как к результату работы.
Привилегии — это реальный контроль. Запускайте агента от имени выделенного непривилегированного пользователя, имеющего только те правила sudo, которые ему необходимы. Стоит использовать PreToolUse deny, но по своей сути это лишь дополнительная мера: в документации сказано то же самое о фильтре if, и рекомендуется использовать системные права доступа, если вам нужен строгий запрет. Правила доступа и учетная запись, от которой работает процесс, — это те элементы, которые обеспечивают реальную защиту.
Одно свойство сохраняется в любой конфигурации. Хуки PreToolUse срабатывают до проверки прав доступа в любом режиме, поэтому хук, возвращающий deny, блокирует инструмент даже в режиме bypassPermissions. Хуки могут ограничивать то, что разрешено правилами доступа. Они не могут расширять эти права.
Почему мой хук не срабатывает?
Выполняйте проверку по порядку. Каждый шаг описывает симптом, который вы увидите.
- Запустите
/hooksи убедитесь, что хук отображается в ожидаемом событии. Если хук отсутствует в меню, это обычно означает синтаксическую ошибку JSON в файле настроек, так как завершающие запятые и комментарии недопустимы, либо файл находится не в одном из шести указанных выше расположений. - Сравните сопоставитель (matcher) с именем инструмента в точности. Сопоставители чувствительны к регистру, поэтому
"bash"никогда не совпадет с инструментомBash. - Запустите скрипт вручную с тестовыми входными данными, как в примере 1 выше. Неожиданный код завершения — это ошибка в вашем скрипте, и Claude Code сообщает о ней как об ошибке хука, а не как о решении.
- Уведомление
jq: command not foundозначает, что на этой машине отсутствуетjq. Ошибкаcommand not foundдля вашего собственного скрипта означает, что путь не был разрешен, поэтому используйте${CLAUDE_PROJECT_DIR}или абсолютный путь. Если скрипт вообще не запускается, вероятно, у него нет прав на выполнение. - Хук выводит корректный JSON, но ничего не происходит. Хук в формате shell запускается через
sh -c, и если ваш профиль оболочки выводит приветственное сообщение (banner), оно добавляется перед вашим JSON. Стандартный вывод (stdout) перестает начинаться с{, поэтому Claude Code считывает всё как обычный текст и игнорирует решение. При коде завершения 0 нигде не выводится никаких сообщений, кроме отладочного лога. Оберните любойechoв вашем профиле так, чтобы он выполнялся только в интерактивных оболочках. - Если проблема сохраняется: начните сессию с
claude --debug-file /tmp/claude.logи запуститеtail -f /tmp/claude.logво втором терминале. В отладочном логе записывается, какие хуки сработали, какой код завершения вернул каждый из них, а также всё, что они записали в stdout и stderr.
FAQ
В чем разница между хуком Claude Code и инструкцией в CLAUDE.md?
Инструкция CLAUDE.md — это текст в контексте модели, поэтому она конкурирует за внимание с диалогом и текущим запросом, и модель может сопоставлять её с ними. Хук — это shell-команда, которую Claude Code запускает в фиксированный момент своего жизненного цикла, поэтому она выполняется при каждом наступлении события, независимо от решения модели. Используйте инструкцию для предпочтений. Используйте хук для шага, который должен выполняться всегда, или для действия, которое никогда не должно происходить.
Как запретить Claude Code выполнение конкретной shell-команды?
Зарегистрируйте хук PreToolUse с матчером Bash, который считывает команду из .tool_input.command, записывает причину в stderr и завершается с кодом 2. Claude Code отменяет вызов и показывает модели вашу причину; это происходит до проверки режима разрешений, поэтому запрет действует даже в режиме bypassPermissions. Сопоставление с шаблоном в строке команды — это скорее ограничитель, чем граница безопасности, так как одну и ту же команду можно написать в форме, которую шаблон пропустит. Поэтому подкрепляйте это правилами разрешений и использованием непривилегированной учетной записи.
Мой хук выводит корректный JSON, но ничего не происходит. Почему?
Самая частая причина — ваш shell-профиль. Хук без поля args выполняется через sh -c, а некоторые профили выводят приветственное сообщение при каждом запуске оболочки, которое попадает в stdout перед вашим JSON. Поскольку вывод больше не начинается с {, Claude Code воспринимает всё как обычный текст и игнорирует решение, а при коде завершения 0 в транскрипте вообще ничего не отображается. Оберните любой echo в вашем профиле проверкой на интерактивную оболочку, а затем подтвердите исправление, прочитав лог отладки из claude --debug-file /tmp/claude.log.
Безопасно ли запускать хуки Claude Code на общем сервере?
Хуки запускаются от имени пользователя, который запустил Claude Code, с правами доступа этого пользователя, поэтому хук может делать всё, что доступно этой учетной записи. Две привычки покрывают большую часть рисков: запускайте агента от имени выделенной непривилегированной учетной записи с узкой политикой sudo и читайте блок hooks любого репозитория перед тем, как принять диалог доверия к рабочей области, так как хуки проекта поставляются внутри .claude/settings.json. Установите "disableAllHooks": true в файле настроек, если хотите, чтобы ни один из них не запускался.