Hooks у Claude Code: автоматизація без втручання моделі

Один з трьох файлів settings.json доступний для редагування: ~/.claude/settings.json (user), <repo>/.claude/settings.json (project), .claude/settings.local.json (local, у gitignore). Базове знання shell на цільовій ОС. Корисно мати jq — деякі hooks повертають JSON.
Що таке hook у двох реченнях
Hook — це shell-команда у settings.json, яку CC запускає на одній із подій життєвого циклу. Команда виконується процесом-нащадком shell'а; її stdout/stderr/exit code впливають на CC (можна заблокувати tool-виклик, можна повернути дані моделі, можна нічого не повертати — тоді hook просто «зробив свою справу»).
Це принципово відрізняється від tool-ів: tool — це функція, яку викликає модель; hook — функція, яку викликає система навколо моделі.
6 типів подій
| Тип | Коли запускається | Може заблокувати? |
|---|---|---|
PreToolUse | Перед викликом tool-а | Так (exit 2) |
PostToolUse | Після виклику tool-а | Ні (вже виконалось) |
SessionStart | Старт сесії | Ні |
Stop | Кінець відповіді моделі (end-of-turn) | Ні |
Notification | Коли CC хоче повідомити користувача (наприклад, чекає підтвердження) | Ні |
UserPromptSubmit | Перед обробкою кожного промта користувача | Так (заблокує промт) |
Структура hook у settings.json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"command": "ruff format "${tool_input.file_path}""
}
]
}
}
Розбір:
"PostToolUse"— тип події."matcher"— regex по імені tool-а."Edit"ловить тільки виклики Edit,"Bash"— тільки Bash,".*"— всі. Можна"Edit|Write"."command"— сама shell-команда. Має доступ до плейсхолдерів${tool_input.<поле>}— параметрів, з якими викликано tool.
Реальні рецепти
1. Auto-format Python після Edit
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"command": "case "${tool_input.file_path}" in *.py) ruff format "${tool_input.file_path}";; esac"
}]
}
}
Після кожного Edit/Write по .py-файлу — запускається ruff format. CC бачить уже відформатований код у наступному Read. Це знімає 80% перепалок «модель пише з 2-tab, а проект на 4-space».
2. Audit-лог Bash-команд
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"command": "echo "$(date -Iseconds) ${tool_input.command}" >> ~/.claude/bash-audit.log"
}]
}
}
Кожна Bash-команда, яку CC хоче виконати, записується в audit-log до виконання. Корисно для:
- Compliance (можна показати команді безпеки, що саме виконує агент).
- Debugging — згадати «що ж він тоді зробив».
- Постфактумного reviewing — підозрілий patern (наприклад, виконав 50 однакових команд) видно у логу.
3. Push-сповіщення у Telegram на завершення сесії
{
"hooks": {
"Stop": [{
"command": "curl -s 'https://api.telegram.org/bot$TOKEN/sendMessage' -d 'chat_id=$CHAT_ID' -d 'text=CC session done'"
}]
}
}
На Stop (кінець відповіді моделі) — пуш у Telegram. Корисно для cron-запусків («Recurring jobs») — отримуєш сповіщення, що нічна задача завершилась успішно (або, якщо exit-code-aware, що впала).
4. Заборона rm -rf без явного підтвердження
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"command": "case "${tool_input.command}" in *'rm -rf'*) echo 'BLOCKED: rm -rf needs manual confirmation' >&2; exit 2;; esac"
}]
}
}
Якщо CC спробує виконати команду з rm -rf — hook повертає exit 2 і CC отримує stderr як reason для блокування. Модель бачить блок, шукає альтернативу.
Це не замінює permissions (спиця «Permissions і settings.json» циклу), а доповнює — permissions працює на рівні patterns, hook може мати багатшу логіку.
5. Перевірка комітів на secrets
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"command": "case "${tool_input.command}" in *'git commit'*) git diff --cached | grep -E 'API_KEY|SECRET|TOKEN' && { echo 'BLOCKED: possible secret in commit' >&2; exit 2; };; esac; exit 0"
}]
}
}
Перед git commit сканує staged-diff на регекс API_KEY|SECRET|TOKEN. Якщо знайдено — блокує. Не замінює gitleaks/trufflehog (ті точніше), але fail-fast захист.
6. SessionStart-банер «де ти зараз»
{
"hooks": {
"SessionStart": [{
"command": "echo '== Branch:' $(git branch --show-current 2>/dev/null) '== Status:' $(git status --porcelain 2>/dev/null | wc -l) 'changes'"
}]
}
}
На старті сесії — короткий стан репо у консолі. Економить запит «де ми зараз». Особливо корисно, якщо часто перемикаєш гілки і повертаєшся через /resume.
Підводні камені
Format виходу критичний. Hook може повертати:
- Просто exit code (0 — OK; 2 — блокувати tool/prompt, stderr передається моделі як reason).
- JSON на stdout (для подачі повідомлень моделі або зміни параметрів).
- Інші exit codes — трактуються як помилка hook'а, але не блокують tool автоматично.
Якщо твій hook пише в stdout щось не-JSON і виходить з кодом 0 — CC проігнорує вихід. Якщо повертаєш JSON — він має бути валідний, інакше hook fail.
Hook довше 5 секунд блокує сесію. Користувач сидить і чекає, поки CC чекає на hook. Не роби в hook мережевих викликів (curl на API), повільних linter'ів, scan-ів. Якщо це обов'язково — запускай у фоні через & або у бічному процесі.
Hook працює у shell ОС, з повними правами CC-користувача. Це не пісочниця: rm, chmod, мережа — все доступне. Це diff від tool permissions у самому CC. Тому: не клади у hook script-и з зовнішніми залежностями, не довіряй ${tool_input.*} сліпо (екрануй).
Регекс у matcher — точкова річ. "Edit" матчить тільки точну назву tool-а. "Edit|Write" — обидва. "^E" — все, що починається з E (Edit, ExitPlanMode тощо). Тестуй регулярки на реальних tool-іменах.
JSON у settings.json — JSON. Trailing commas, comments — все це не валідне. CC при невалідному settings.json може просто проігнорувати hooks і не сказати про це у вікно сесії. Перевір jq . ~/.claude/settings.json перед перезапуском.
Project-level hooks vs user-level. Project hooks застосовуються тільки у тому репо; user-level — глобально. Якщо переплутав місця — побачиш, що hook не спрацював, хоча в JSON він є.
Як налагоджувати hook
- Перевір синтаксис JSON:
jq . ~/.claude/settings.json. - Подивись список активних hooks: slash
/hooks. - Додай у hook
tee -a ~/.claude/hooks-debug.log, щоб бачити, чи спрацював. - Запусти tool, який має тригерити hook. Подивись у лог. Якщо порожньо — matcher неправильний.
- Якщо лог є, але hook не блокує (а має) — перевір exit code (повинно бути
exit 2).
Cross-refs
- Де лежить
hooksу JSON, поряд з permissions — «Permissions і settings.json». - PreToolUse може блокувати виклики Agent — «Sub-агенти Claude Code».
- Stop-hook після cron-сесії — «Recurring jobs».
- JSON-синтаксис валідний (
jqне падає). - Hook виконується за < 1 секунду (інакше блокуєш UX).
- Жодних мережевих викликів у тілі (виключити з PreToolUse/PostToolUse).
- Якщо блокує — повертає
exit 2з reason у stderr. - Параметри з
${tool_input.*}екрановано (double-quotes, case-statement, не пряма interpolation у пайп). - Hook логує себе, щоб дебажити коли «не спрацював».
- Hook не виконує деструктивних дій (delete, kill) без явного фільтра matcher.
Формат hooks у settings.json для CC станом на травень 2026. Anthropic може додати нові типи подій або змінити схему JSON — перевіряти у release notes. Швидкий тест — /hooks у своїй сесії.