Как работают хуки в Claude Code: настройка и примеры
Узнайте, как хуки Claude Code принудительно выполняют shell-команды независимо от инструкций модели. Разбор событий, передача JSON через stdin и отмена вызовов кодом 2.
Что такое хук 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 завершает ответ. Это происходит один раз за итерацию, а не один раз за выполнение всей задачи.
Каждая группа событий содержит matcher, который определяет, в каких случаях запускается хук. Для событий инструментов фильтрация происходит по имени инструмента, поэтому "Edit|Write" срабатывает только при редактировании файлов и больше нигде. Матчеры чувствительны к регистру. Пустой матчер срабатывает на любое событие. Инструменты с сервера 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 и его настройками разрешений, где подсказки отключены, но хук всё равно срабатывает.
Будьте честны в отношении того, что это такое. Сопоставление с образцом в строке команды — это защитный механизм против неосторожности агента, а не барьер против хитрого агента, поскольку одну и ту же команду можно написать в форме, которую ваш 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 секунд.
Хук, время выполнения которого истекло, отменяется и не выносит решения. Для защитного механизма PreToolUse это означает, что он не блокирует выполнение: вызов инструмента переходит в обычный поток проверки прав. По этой причине скрипты защитных механизмов должны быть небольшими. Для медленных задач, которые не требуют ожидания (например, отправка логов), используйте "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, добавляется в диалог. Хук, который передает текст извне, например из системы отслеживания задач или лог-файла, передает модели недоверенный текст так, будто вы ввели его самостоятельно. Относитесь к выводу stdout как к входным данным, а не как к результату работы.
Привилегии — это основной механизм контроля. Запускайте агента от имени выделенного пользователя без привилегий, которому предоставлены только необходимые правила sudo. Наличие запрета PreToolUse полезно, но по своей сути это лишь рекомендация: в документации сказано то же самое о фильтре 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 в файле настроек, если вы хотите, чтобы ни один из них не запускался.