Хуки Claude Code: примеры блокировки с тестами
Хуки Claude Code — это команды, которые выполняются в определённых точках сессии: перед запуском инструмента, после него, при отправке промпта, при остановке сессии. Именно они превращают «агент не должен этого делать» в правило, которое реально проверяется. В этой статье — хуки Claude Code с примерами, которые блокируют то, что должно быть заблокировано, способы их тестирования и причины большинства поисковых запросов вида «hook error».
Всё ниже соответствует официальному справочнику по хукам и документации по разрешениям. Мы ежедневно используем Claude Code с хуками в собственной студии; один из наших хуков — Stop-хук, предупреждающий, когда расход токенов сессии превышает лимит.
Где настраиваются хуки Claude Code
Хуки хранятся в файлах настроек:
| Файл | Область действия | Общий доступ |
|---|---|---|
~/.claude/settings.json |
все ваши проекты | нет |
.claude/settings.json |
один проект | да, можно коммитить |
.claude/settings.local.json |
один проект | нет |
Записи из этих файлов объединяются, а не заменяют друг друга. Плагины, навыки и субагенты тоже могут приносить свои хуки.
Запись хука имеет три уровня: событие, группа matcher и обработчики:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/block_rm.py" }
]
}
]
}
}
CLAUDE_PROJECT_DIR — корневая папка проекта, в которой была запущена сессия. В Windows вызывайте Python так, как принято в вашей системе.
Хук PreToolUse, блокирующий опасную команду
Хук PreToolUse получает запланированный вызов инструмента в виде JSON на стандартный ввод: tool_name, tool_input и несколько полей сессии. Для Bash tool_input.command — это текст команды.
#!/usr/bin/env python3
import json, re, sys
data = json.load(sys.stdin)
cmd = data.get("tool_input", {}).get("command", "")
if re.search(r"\brm\s+-\w*(rf|fr)\w*\b", cmd):
print("Blocked: recursive force delete. Ask the user to run it.", file=sys.stderr)
sys.exit(2)
sys.exit(0)
Главное правило: вызов блокирует код выхода 2, а stderr становится причиной, которую видит Claude. Код 0 пропускает команду. Любой другой код — неблокирующая ошибка: действие выполняется, вы получаете лишь уведомление.
Текстовый шаблон ловит привычную форму команды, но не все формы: этот останавливает rm -rf и rm -fr, но rm -r -f с раздельными флагами пройдёт. Поэтому каждое правило нужно тестировать, и поэтому рядом с хуками существуют правила разрешений и песочница.
Если вам ближе структурированный вывод, хук может завершиться с кодом 0 и вывести JSON с hookSpecificOutput, где hookEventName — PreToolUse, а permissionDecision — allow, deny, ask или defer, вместе с permissionDecisionReason.
Matcher для PreToolUse без сюрпризов
Matcher фильтрует по имени инструмента:
Bashили список вродеEdit|Write— точные имена.- Всё, что содержит другие символы, — регулярное выражение без привязки:
^Notebook,mcp__memory__.*. - Инструменты MCP именуются
mcp__<server>__<tool>. Matcher видаmcp__memoryсам по себе не совпадёт ни с чем; нужноmcp__memory__.*. *, пустая строка или отсутствие matcher означают все инструменты.
Есть также поле if, использующее синтаксис правил разрешений, например Bash(git *). Документация называет его «best-effort»: для жёсткого allow или deny используйте систему разрешений.
Тестируйте каждый хук: один вызов заблокирован, один пропущен
Хук без теста — это хук, на который вы просто надеетесь. Самый дешёвый тест — подать ему тот же JSON, который отправил бы Claude Code, и проверить код выхода:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf build"}}' | python3 .claude/hooks/block_rm.py; echo "exit $?" # expect 2
echo '{"tool_name":"Bash","tool_input":{"command":"ls build"}}' | python3 .claude/hooks/block_rm.py; echo "exit $?" # expect 0
Наше правило для каждого хука: одно действие, которое должно быть заблокировано, и одно, которое должно пройти, — оба в тестовом файле. Второй тест ловит другую частую проблему: слишком широкий шаблон, блокирующий нормальную работу.
Почему ошибка хука PreToolUse ничего не останавливает
Типичные причины, все из справочника:
- Код выхода 1 вместо 2. Появляется уведомление об ошибке, но инструмент всё равно выполняется.
- Таймаут. Хук PreToolUse, завершившийся по таймауту, не блокирует; вызов проходит обычный путь через систему разрешений. По умолчанию у командных хуков 600 секунд.
- Matcher, который никогда не совпадает, например
mcp__memoryбез.*. - Падающий скрипт. По умолчанию упавший хук не блокирует. Последние версии поддерживают
"onFailure": "block"(v2.1.295 и новее), чтобы блокировать, когда командный хук падает, висит по таймауту или возвращает некорректный вывод.
Хуки, правила разрешений и навыки: кто за что отвечает
- Правила разрешений (
allow,ask,denyв settings.json) проверяются в таком порядке: deny, затем ask, затем allow. Правило deny применяется даже когда хук PreToolUse возвращаетallow. - Блокирующий хук (код 2) останавливает вызов до проверки правил разрешений, поэтому он также побеждает правило allow.
- Навыки — это инструкции и ресурсы, которые агент загружает по мере необходимости. Они подсказывают, но ничего не запрещают.
Для конфиденциальных папок начните с правил deny вроде Read(./secrets/**). Знайте их предел: они охватывают встроенные инструменты работы с файлами и команды вроде cat, head и tail, но не grep -r, запущенный из папки, и не скрипт, который сам открывает файлы. Хук PreToolUse в таких случаях может проанализировать полный текст команды, а для жёсткого запрета на уровне операционной системы документация рекомендует песочницу.
Когда передать это нам
Если ваша агентская инфраструктура выросла быстро и вы не уверены, какие правила реально работают, мы составляем карту агентов, пишем правила хуков с тестами и показываем прогон тестов, а не только конфиг. Как устроена наша собственная система, можно посмотреть на странице работ.
Вопросы
Почему мой хук PreToolUse ничего не блокирует?
Чаще всего он завершается с кодом 1. Вызов инструмента блокирует только код выхода 2; любой другой ненулевой код — это неблокирующая ошибка, и действие выполняется. Хук, завершившийся по таймауту, тоже не блокирует.
Нужны ли хуки, если уже есть правила запрета в разрешениях?
Для простых случаев правила deny применяются в первую очередь: они проверяются даже когда хук возвращает allow. Хуки нужны для логики, которую нельзя выразить шаблоном — например, для анализа полного текста команды перед выполнением.
Останавливают ли правила Read запретов команды cat и grep в Bash?
Они охватывают встроенные инструменты работы с файлами и те файловые команды, которые Claude Code распознаёт в Bash: cat, head и tail. Они не охватывают команду, читающую файлы без указания имён, например grep -r из папки, или скрипт, который сам открывает файлы. Для таких случаев документация рекомендует песочницу.
Где хранятся хуки Claude Code?
В файлах настроек: ~/.claude/settings.json для всех ваших проектов, .claude/settings.json для одного проекта и всей команды или .claude/settings.local.json только для вас. Записи из нескольких файлов объединяются.