Что такое хуки
Хуки в Claude Code — это shell-команды, которые выполняются автоматически в определённые моменты работы агента. Они позволяют встроить в рабочий процесс Claude Code любую логику: форматирование кода, линтинг, тесты, уведомления в Slack или логирование действий.
Ключевая идея: хуки выполняет система (оболочка), а не сам Claude. Это значит, что они работают детерминированно — Claude не может их обойти или «забыть» применить.
Типичные сценарии:
- Автоматический
prettierилиblackпосле каждого редактирования файла - Запуск
eslint --fixперед записью JS/TS файлов - Уведомление в Telegram/Slack когда агент завершил задачу
- Логирование всех выполненных команд в файл аудита
Типы хуков
Claude Code поддерживает четыре типа хуков, привязанных к жизненному циклу инструментов:
| Тип | Когда срабатывает |
|---|---|
PreToolUse | До того как Claude вызывает инструмент (Read, Write, Bash и др.) |
PostToolUse | После успешного вызова инструмента |
Notification | При отправке Claude уведомления пользователю |
Stop | Когда агент завершает сессию |
Самые полезные на практике — PreToolUse и PostToolUse. Первый позволяет заблокировать или модифицировать действие, второй — реагировать на уже совершённые изменения.
Шаг 1: Настройка settings.json
Хуки конфигурируются в файле настроек Claude Code. Есть два уровня:
- Глобальный:
~/.claude/settings.json— применяется ко всем проектам - Проектный:
.claude/settings.jsonв корне проекта — переопределяет глобальный
Базовая структура секции hooks:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "echo 'File written: $CLAUDE_TOOL_INPUT_FILE_PATH'"
}
]
}
]
}
}
Каждый хук имеет:
matcher— имя инструмента (Write,Bash,Read,Edit,*для всех)hooks— массив командtype— всегдаcommandcommand— shell-команда, которую нужно выполнить
Шаг 2: Pre-tool хук — форматирование перед записью
PreToolUse срабатывает до выполнения инструмента. Если хук завершается с кодом выхода 2 — инструмент блокируется, Claude получает сообщение об ошибке.
Пример: запускаем валидацию JSON перед записью .json файлов:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "bash -c 'if [[ \"$CLAUDE_TOOL_INPUT_FILE_PATH\" == *.json ]]; then echo \"$CLAUDE_TOOL_INPUT_CONTENT\" | python3 -m json.tool > /dev/null 2>&1 || (echo \"Invalid JSON\" && exit 2); fi'"
}
]
}
]
}
}
Переменные окружения, доступные в хуках:
| Переменная | Описание |
|---|---|
CLAUDE_TOOL_NAME | Имя инструмента (Write, Bash и т.д.) |
CLAUDE_TOOL_INPUT_FILE_PATH | Путь к файлу (для Write/Edit/Read) |
CLAUDE_TOOL_INPUT_CONTENT | Содержимое (для Write) |
CLAUDE_TOOL_INPUT_COMMAND | Команда (для Bash) |
Шаг 3: Post-tool хук — автоформатирование после записи
PostToolUse идеален для форматирования: файл уже записан, теперь приводим его к стандарту:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "bash -c 'FILE=\"$CLAUDE_TOOL_INPUT_FILE_PATH\"; case \"$FILE\" in *.py) black \"$FILE\" --quiet 2>/dev/null ;; *.js|*.ts|*.jsx|*.tsx) npx prettier --write \"$FILE\" --log-level silent 2>/dev/null ;; *.go) gofmt -w \"$FILE\" 2>/dev/null ;; esac'"
}
]
}
]
}
}
Этот хук автоматически:
- Запускает
blackдля Python файлов - Запускает
prettierдля JS/TS файлов - Запускает
gofmtдля Go файлов
Если нужный форматтер не установлен — 2>/dev/null подавляет ошибку, хук молча пропускает шаг.
Примеры хуков
Уведомление при завершении задачи
{
"hooks": {
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "curl -s -X POST 'https://api.telegram.org/bot<TOKEN>/sendMessage' -d 'chat_id=<CHAT_ID>&text=Claude+Code+завершил+задачу+✅'"
}
]
}
]
}
}
Аудит-лог всех Bash команд
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') CMD: $CLAUDE_TOOL_INPUT_COMMAND\" >> /tmp/claude-audit.log"
}
]
}
]
}
}
Запрет на изменение production конфигов
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "bash -c 'if [[ \"$CLAUDE_TOOL_INPUT_FILE_PATH\" == *prod* || \"$CLAUDE_TOOL_INPUT_FILE_PATH\" == *.env.production ]]; then echo \"Изменение production-файлов заблокировано\" && exit 2; fi'"
}
]
}
]
}
}
Полный пример settings.json с несколькими хуками:
{
"permissions": {
"allow": ["Bash", "Write", "Edit", "Read"]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"$(date '+%H:%M:%S') $ $CLAUDE_TOOL_INPUT_COMMAND\" >> /tmp/claude-session.log"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "bash -c 'FILE=\"$CLAUDE_TOOL_INPUT_FILE_PATH\"; case \"$FILE\" in *.py) black \"$FILE\" --quiet 2>/dev/null ;; *.js|*.ts) npx prettier --write \"$FILE\" --log-level silent 2>/dev/null ;; esac'"
}
]
}
],
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "echo \"Claude Code session ended at $(date)\" >> /tmp/claude-sessions.log"
}
]
}
]
}
}
Отладка
Если хук не работает как ожидается:
1. Проверь синтаксис JSON
cat .claude/settings.json | python3 -m json.tool
2. Проверь переменные окружения вручную Временно добавь хук, который печатает все переменные:
{
"type": "command",
"command": "env | grep CLAUDE >> /tmp/claude-env-debug.log"
}
3. Смотри вывод хука Claude Code показывает stdout/stderr хуков в консоли — если хук падает с ошибкой, ты увидишь её там.
4. Тестируй команды отдельно Запусти команду из хука вручную в терминале с нужными переменными:
CLAUDE_TOOL_INPUT_FILE_PATH="test.py" bash -c 'black "$CLAUDE_TOOL_INPUT_FILE_PATH" --quiet'
5. Используй абсолютные пути
В хуках лучше использовать абсолютные пути к бинарникам (/usr/local/bin/black вместо black) — окружение может отличаться от интерактивного терминала.
Хуки — это небольшая инвестиция в настройку, которая окупается на каждой сессии. Форматтер, который ты больше не забываешь запустить, или уведомление, которое само приходит — именно так выглядит Claude Code в режиме силы.