Claude Code — Хуки — как защитить код
26 августа 2026 г.
5 мин
1

Claude Code — Хуки — как защитить код

Есть разрыв между тем что просишь Claude Code сделать - и что он реально делает. Большую часть времени это нормально. Но иногда нужна гарантия, а не рекомендация. "Не трогай .env" легко написать в CLAUDE.md. Сложнее - обеспечить выполнение.

Хуки - ответ Claude Code на эту проблему. Они позволяют перехватывать жизненный цикл в конкретных точках и внедрять детерминированное поведение: запустить линтер, заблокировать команду, отправить уведомление, отклонить промпт. Разница от инструкций в CLAUDE.md - хуки не зависят от того что Claude читает и помнит. Они срабатывают безусловно, каждый раз, независимо от состояния контекстного окна.

Что хуки на самом деле представляют собой

Хук - это обработчик который прикрепляешь к именованному lifecycle событию. Когда это событие срабатывает, Claude Code передаёт JSON контекст обработчику через stdin (для command хуков) или как HTTP POST тело. Обработчик инспектирует ввод, опционально возвращает решение, и Claude Code действует на него.

Ключевое слово - детерминированный. Инструкция в промпте может быть проигнорирована, сжата из контекста, или понята неверно. Хук который выходит с кодом 2 блокирует вызов инструмента - полностью. Хуки также иммунны к давлению контекстного окна: они срабатывают из конфигурации, не из того что Claude в данный момент держит в памяти.

Конфигурация хуков находится в settings.json, под ключом hooks:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/check-command.sh"
          }
        ]
      }
    ]
  }
}

Структура: имя события → массив групп матчеров → каждая группа имеет матчер и массив обработчиков. Матчер фильтрует какие вызовы инструментов активируют группу. Если опустить или использовать "*", группа срабатывает на каждом вхождении события.

События хуков

Около 20 lifecycle событий. Большинство никогда не понадобятся. Стоит знать:

Внутри агентного цикла (срабатывают многократно):

  • PreToolUse - срабатывает до выполнения вызова инструмента. Может его заблокировать. Главная точка применения правил.
  • PostToolUse - срабатывает после успешного вызова инструмента. Для форматирования, логирования и побочных эффектов.
  • PostToolUseFailure - срабатывает после сбоя вызова инструмента. Для очистки или алертов.
  • UserPromptSubmit - срабатывает когда отправляешь промпт, до того как Claude его обрабатывает. Может внедрять контекст или отклонять промпт.
  • PermissionRequest - срабатывает когда появляется диалог разрешения. Позволяет программно одобрять или отклонять.
  • Stop - срабатывает когда Claude заканчивает ответ. Хорошо для запуска тестов или триггера post-completion работы.

Уровня сессии (срабатывают один раз или при конкретных условиях):

  • SessionStart - срабатывает при начале или возобновлении сессии.
  • SessionEnd - при завершении сессии. Для очистки или audit логирования.
  • InstructionsLoaded - когда файл CLAUDE.md или .claude/rules/*.md загружается в контекст. Только наблюдение.
  • ConfigChange - когда файл конфигурации изменяется во время сессии.
  • PreCompact и PostCompact - вокруг автоматической компакции контекста. PreCompact позволяет внедрить резюме до компакции, что влияет на то что сохраняется.

Типы хуков

Command (type: "command") - запускает shell скрипт. Самый простой и распространённый. Получает JSON на stdin, возвращает решения через exit code и stdout.

HTTP (type: "http") - делает POST с JSON события на URL. Хорошо для централизованного логирования или интеграции с внешними сервисами, но добавляет задержку на каждое совпавшее событие. Также актуален для MCP server воркфлоу - PreToolUse и PermissionRequest хуки могут перехватывать MCP вызовы инструментов по имени.

Prompt (type: "prompt") - отправляет событие модели Claude для оценки да/нет. Полезно когда решение требует семантического суждения которое regex не может надёжно принять. Медленнее и потребляет токены.

Agent (type: "agent") - порождает субагент с доступом к инструментам Read, Grep и Glob. Для решений которые требуют инспекции кодовой базы до разрешения вызова инструмента. Самый мощный тип - и самый дорогой.

Exit коды и решения

  • Exit 0 - успех. Claude Code парсит stdout на JSON решение и продолжает.
  • Exit 2 - блокирующая ошибка. Claude Code игнорирует stdout. Текст stderr передаётся Claude как сообщение об ошибке. Для PreToolUse - блокирует вызов инструмента. Для UserPromptSubmit - отклоняет промпт.
  • Любой другой exit код - неблокирующая ошибка. Хук сбойнул, Claude Code продолжает.

PreToolUse хук который хочет заблокировать команду:

#!/bin/bash
# .claude/hooks/block-env-writes.sh
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
FILE=$(echo "$INPUT" | jq -r '.tool_input.path // empty')

if [[ "$TOOL" == "Write" && "$FILE" == *".env"* ]]; then
  echo "Blocked: .env files must not be modified by Claude Code" >&2
  exit 2
fi

exit 0

Конфигурация:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-env-writes.sh"
          }
        ]
      }
    ]
  }
}

Обратите внимание на префикс $CLAUDE_PROJECT_DIR. Пути хуков в settings.json разрешаются относительно рабочей директории в момент запуска Claude Code - что может варьироваться. Использование переменной окружения гарантирует правильное разрешение пути.

Два паттерна которые стоят затрат на настройку

Авто-форматирование после записи файлов

PostToolUse с матчером Write - запускай форматтер после каждой записи файла, тихо в фоне:

#!/bin/bash
# .claude/hooks/format-on-write.sh
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.path // empty')

if [[ -z "$FILE" ]]; then
  exit 0
fi

EXT="${FILE##*.}"

case "$EXT" in
  ts|tsx|js|jsx)
    npx prettier --write "$FILE" 2>/dev/null
    ;;
  py)
    ruff format "$FILE" 2>/dev/null
    ;;
esac

exit 0

Сделай асинхронным чтобы не блокировать следующий вызов инструмента:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-on-write.sh",
            "runInBackground": true
          }
        ]
      }
    ]
  }
}

Предостережение: вывод форматтера подаваемый обратно в сессию добавляется к контексту. Если сессия включает много небольших правок файлов и медленный форматтер, повторяющиеся проходы форматирования могут потребить удивительно большую часть контекстного окна.

Уведомление когда Claude простаивает

Событие Notification срабатывает когда Claude Code отправляет уведомление - включая когда ждёт разрешения или закончил длинную задачу:

#!/bin/bash
# .claude/hooks/notify.sh
INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Claude Code needs attention"')

# macOS
osascript -e "display notification \"$MESSAGE\" with title \"Claude Code\""

exit 0
{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/notify.sh",
            "runInBackground": true
          }
        ]
      }
    ]
  }
}

Частые ошибки

Хуки которые срабатывают слишком широко

Хук без матчера срабатывает на каждом вхождении события. Если прикрепить что-то дорогое к PreToolUse без матчера - оно запускается перед каждым одиночным вызовом инструмента.

Поле if внутри обработчика обеспечивает второй уровень фильтрации:

{
  "type": "command",
  "if": "Bash(rm *)",
  "command": ".claude/hooks/check-rm.sh"
}

Проверка if оценивается до порождения обработчика. Если не совпадает — процесс не создаётся.

Забыть про stdin

JSON события приходит на stdin - не как аргументы, не как переменные окружения. Единственная переменная окружения которую устанавливает Claude Code - $CLAUDE_CODE_REMOTE.

# Неправильно
FILE=$1

# Правильно
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.path // empty')

Использование хуков как замены разрешений settings.json

Хуки запускают твой код. Это не система политик - это автоматизация. Хук плюс deny правило в settings.json (сколь бы несовершенным) даёт defence in depth. Хук один по себе - неправильный инструмент для чистой задачи контроля доступа.

Советы

  • Используй команду /hooks внутри Claude Code сессии для просмотра и переключения настроенных хуков без редактирования JSON напрямую.
  • Запускай Claude Code с Ctrl+O (verbose mode) чтобы видеть stdout хуков в терминале.
  • Помещай скрипты хуков в .claude/hooks/ и ссылайся на них через $CLAUDE_PROJECT_DIR/.claude/hooks/your-script.sh. Коммить их в репо.
  • Держи скрипты хуков сфокусированными. Скрипт который делает три вещи сложнее дебажить чем три скрипта которые делают одну.

Итог

Хуки дают детерминированный контроль над lifecycle Claude Code - то что инструкции CLAUDE.md одни не могут обеспечить. Два паттерна с которых стоит начать: авто-форматирование при записи с PostToolUse, и Notification хук чтобы перестать бдеть за длинными сессиями. Добавь PreToolUse гарды для файлов которые никогда не должны быть изменены.

Навигация по Claude Code
Часть 6 из 6 · продолжается
← Пред.След. →
Читайте дальше
Подобрано по темам
Все заметки →

Комментарии

Загружаю...
Оставить комментарий