Хуки: автоматизация без компромиссов
Перейти к разделу
Что такое хуки и зачем они нужны
Хуки — это правила, которые автоматически срабатывают при определённых действиях Claude Code. Когда Claude Code хочет записать файл, выполнить команду или завершает задачу, хук может вмешаться — заблокировать действие, изменить его или запустить кастомную логику. Думайте о них как о git hooks, но для AI-агента.
Без хуков вы вручную запускаете линтер после каждого изменения, проверяете форматирование, прогоняете тесты. С хуками это происходит автоматически. Claude Code записывает файл, хук запускает ruff, если есть ошибки — Claude Code видит их и исправляет. Никакого ручного вмешательства.
Типы хуков
Claude Code поддерживает четыре типа хуков. Каждый срабатывает в разный момент и служит разной цели.
PreToolUse — перед использованием инструмента
Срабатывает ДО того, как Claude Code использует инструмент (Write, Bash, Edit и др.). Может заблокировать действие, изменить его или просто залогировать. Идеально для валидации — «никогда не записывай в production-конфиг», «никогда не пушь в main».
PostToolUse — после использования инструмента
Срабатывает ПОСЛЕ того, как Claude Code использует инструмент. Вывод хука передаётся обратно в Claude Code как контекст. Идеально для автолинтинга, форматирования, запуска тестов после изменения файла.
Notification — при уведомлении
Срабатывает, когда Claude Code отправляет уведомление (как правило, при ожидании ввода или по завершении). Идеально для кастомных уведомлений — сообщений в Slack, звуков, десктопных алертов.
Stop — при остановке
Срабатывает, когда Claude Code завершает свой ход и перестаёт генерировать. Полезен для финальной валидации или очистки.
Настройка хуков
Хуки настраиваются в .claude/settings.json (уровень проекта) или ~/.claude/settings.json (глобально). У каждого хука есть matcher (на какой инструмент реагировать) и command (что запускать).
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo 'About to run a bash command'"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "cd $PROJECT_DIR && uv run ruff check --fix $CLAUDE_FILE_PATH 2>&1 || true"
}
]
}
]
}
}Переменные окружения в хуках
Хуки имеют доступ к переменным окружения, описывающим контекст действия. Они необходимы для написания полезных хуков.
# Available variables in hooks:
$CLAUDE_FILE_PATH # Path to the file being worked on
$CLAUDE_TOOL_NAME # Tool name (Write, Edit, Bash...)
$CLAUDE_TOOL_INPUT # JSON tool input (via stdin)
$PROJECT_DIR # Project root directoryПрактический пример: автолинт после каждого редактирования
Самый распространённый хук — запускает линтер и форматировщик после каждого изменения файла. Результат передаётся обратно в Claude Code, который автоматически исправляет ошибки.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "if [[ \"$CLAUDE_FILE_PATH\" == *.py ]]; then cd $PROJECT_DIR && uv run ruff check --fix \"$CLAUDE_FILE_PATH\" && uv run ruff format \"$CLAUDE_FILE_PATH\"; fi"
}
]
}
]
}
}Команды хуков должны возвращать код выхода 0 при успехе. Если хук завершается с ошибкой (ненулевой код выхода), Claude Code видит вывод ошибки и пытается исправить проблему. Это на самом деле желательное поведение для линтинга — вы хотите, чтобы Claude Code видел ошибки и исправлял их.
Практический пример: блокировка опасных команд
Хук PreToolUse, блокирующий опасные git-команды. Хук возвращает ненулевой код выхода, и Claude Code не выполнит действие.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "INPUT=$(cat); if echo \"$INPUT\" | jq -r '.command' | grep -qE 'git\\s+(push\\s+--force|reset\\s+--hard|clean\\s+-fd)'; then echo 'BLOCKED: Dangerous git command detected' >&2; exit 1; fi"
}
]
}
]
}
}Практический пример: автотест после изменений
Запускает релевантные тесты после каждого изменения Python-файла. Ключевое — запускать только релевантные тесты, а не весь тестовый набор, иначе хук занимает слишком много времени.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "if [[ \"$CLAUDE_FILE_PATH\" == *.py && \"$CLAUDE_FILE_PATH\" != *test* ]]; then MODULE=$(echo \"$CLAUDE_FILE_PATH\" | sed 's|/|.|g' | sed 's|\\.py$||'); TEST_FILE=$(echo \"$CLAUDE_FILE_PATH\" | sed 's|\\([^/]*\\)\\.py$|tests/test_\\1.py|'); if [ -f \"$TEST_FILE\" ]; then cd $PROJECT_DIR && uv run pytest \"$TEST_FILE\" -x -q 2>&1 | tail -5; fi; fi"
}
]
}
]
}
}Практический пример: кастомные уведомления
Хук Notification, отправляющий десктопное уведомление macOS, когда Claude Code требует внимания.
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "INPUT=$(cat); TITLE=$(echo \"$INPUT\" | jq -r '.title // \"Claude Code\"'); MSG=$(echo \"$INPUT\" | jq -r '.body // \"Needs attention\"'); osascript -e \"display notification \\\"$MSG\\\" with title \\\"$TITLE\\\"\""
}
]
}
]
}
}Комбинирование хуков для production-рабочего процесса
На практике вам нужна комбинация хуков — линтинг, форматирование, тестирование и проверки безопасности. Вот полная конфигурация для Python/Django-проекта.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r '.command'); if echo \"$CMD\" | grep -qE 'rm\\s+-rf\\s+/|git\\s+push\\s+--force\\s+(origin\\s+)?main'; then echo 'BLOCKED: Dangerous command' >&2; exit 1; fi"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "if [[ \"$CLAUDE_FILE_PATH\" == *.py ]]; then cd $PROJECT_DIR && uv run ruff check --fix \"$CLAUDE_FILE_PATH\" && uv run ruff format \"$CLAUDE_FILE_PATH\"; fi"
}
]
}
],
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "INPUT=$(cat); MSG=$(echo \"$INPUT\" | jq -r '.body // \"Done\"'); osascript -e \"display notification \\\"$MSG\\\" with title \\\"Claude Code\\\"\""
}
]
}
]
}
}Хуки запускаются в порядке их определения. Если для одного matcher'а несколько хуков — все они выполняются последовательно. Если любой хук PreToolUse завершается с ошибкой — действие блокируется.
Создайте файл .claude/settings.json в вашем проекте и настройте хук PostToolUse, который: 1. Реагирует на операции Write и Edit 2. Проверяет, имеет ли изменённый файл расширение вашего языка (.py, .ts, .js...) 3. Запускает соответствующий линтер (ruff, eslint, biome...) 4. Возвращает результат в Claude Code Затем измените файл через Claude Code и убедитесь, что хук срабатывает автоматически.
Подсказка
Используйте проверку расширения файла: if [[ "$CLAUDE_FILE_PATH" == *.py ]]. Не забудьте cd $PROJECT_DIR, иначе линтер запустится из неверной директории.
Напишите хук PreToolUse, блокирующий: 1. Force push в ветку main/master 2. rm -rf на корневых директориях 3. Любую команду с --no-verify Проверьте его, попросив Claude Code выполнить одну из этих команд, и убедитесь, что хук блокирует её.
Подсказка
Хук читает stdin как JSON с полем 'command'. Используйте jq -r '.command' для извлечения команды и grep -qE для сопоставления с паттерном.
- Хуки автоматизируют повторяющиеся задачи — линтинг, форматирование, тестирование, проверки безопасности
- PreToolUse блокирует опасные действия ДО выполнения, PostToolUse реагирует ПОСЛЕ выполнения
- Вывод хука передаётся обратно в Claude Code — он может автоматически исправлять ошибки линтера
- Хуки Notification позволяют создавать кастомные оповещения (десктоп, Slack, звуки)
- Хуки настраиваются в .claude/settings.json (проект) или ~/.claude/settings.json (глобально)
- Комбинируйте хуки для полного автоматизированного рабочего процесса — lint + format + test + safety
В следующем уроке мы погружаемся в режимы разрешений: безопасность vs. скорость — техника, которая даёт вам явное преимущество. Откройте полный курс и продолжайте прямо сейчас.
2/8 завершено — продолжайте!