Хуки Claude Code: приклади блокування з тестами
Хуки Claude Code — це команди, які виконуються у визначених точках сесії: перед запуском інструменту, після нього, при надсиланні запиту, при зупинці сесії. Саме так можна перетворити «агент не повинен цього робити» на те, що реально перевіряється. Ця стаття показує приклади хуків Claude Code, які блокують те, що треба блокувати, як їх тестувати та які помилки стоять за більшістю запитів «hook error».
Усе нижче ґрунтується на офіційному довіднику хуків і документації з дозволів. Ми щодня використовуємо Claude Code з хуками у власній студії; один із наших — це Stop-хук, який попереджає, коли використання токенів у сесії перевищує ліміт.
Де налаштовуються хуки Claude Code
Хуки живуть у файлах налаштувань:
| Файл | Застосовується до | Спільний |
|---|---|---|
~/.claude/settings.json |
усі ваші проєкти | ні |
.claude/settings.json |
один проєкт | так, можна закомітити |
.claude/settings.local.json |
один проєкт | ні |
Записи з цих файлів об’єднуються, а не замінюються. Плагіни, скіли та субагенти також можуть приносити власні хуки.
Запис хука має три рівні: подія, група матчерів і обробники:
{
"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.
Матчер PreToolUse без сюрпризів
Матчер фільтрує за назвою інструменту:
Bashабо список на кшталтEdit|Write— точні назви.- Усе з іншими символами — це регулярний вираз без прив’язки:
^Notebook,mcp__memory__.*. - Інструменти MCP називаються
mcp__<server>__<tool>. Матчер лишеmcp__memoryне збігається ні з чим; потрібенmcp__memory__.*. *, порожній рядок або відсутність матчера означає всі інструменти.
Є також поле 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 секунд.
- Матчер, який ніколи не збігається, як-от
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-правила дозволів?
Для простих правил спершу застосовуються deny-правила: їх перевіряють навіть тоді, коли хук повертає allow. Хуки потрібні для логіки, яку не виразити шаблоном, наприклад для аналізу повного тексту команди перед її виконанням.
Чи зупиняють deny-правила для Read команди cat і grep у Bash?
Вони охоплюють вбудовані інструменти роботи з файлами та файлові команди, які Claude Code розпізнає в Bash, як-от cat, head і tail. Вони не охоплюють команду, що читає файли, не називаючи їх, наприклад grep -r із теки, або скрипт, який сам відкриває файли. Для таких випадків документація вказує на пісочницю.
Де зберігаються хуки Claude Code?
У файлах налаштувань: ~/.claude/settings.json — для всіх ваших проєктів, .claude/settings.json — для одного проєкту і вашої команди, або .claude/settings.local.json — лише для вас. Записи з кількох файлів об'єднуються.