Библиотека · Хуки и помощники-агенты

Hooks LIVE — строим хуки с нуля

Строитель75 минОбновлено: октябрь 2026
36 из 105 в библиотеке

Модуль: 7. Хуки — автономность системного уровня | Время: ~15 мин теории + 60 мин практики


Суть урока

В предыдущем уроке (Hooks: автоматические правила) — теория: около 30 событий, 5 обработчиков, JSON-протокол. Здесь — три реальных хука, которые используются каждый день. Первый защищает от случайной утечки API-ключей. Второй не даёт превысить бюджет. Третий пишет полный журнал что Claude трогал и когда. Бонус — HTTP webhook для внешних уведомлений. Строим с нуля, разбираем каждую строку.


Ключевые концепции

  • pre-tool-use-no-secrets.sh — сканирует файлы перед записью на наличие паттернов секретов
  • pre-tool-use-budget-check.sh — проверяет счётчик операций и останавливает если лимит превышен
  • post-tool-use-audit-log.sh — записывает лог каждого изменения файла с timestamp
  • exit code 0 / 2 — как хук сообщает Claude Code "разрешить" (0) или "заблокировать" (2)
  • matcher — фильтр: на какие инструменты реагировать ("Write|Edit", "Bash", "*")
  • tool_input — JSON-объект с входными данными инструмента (путь, содержимое, команда)
  • grep — поиск паттернов (API-ключи, токены) в содержимом файлов
  • jq — парсинг JSON который Claude Code передаёт хуку через stdin
  • Тестирование хука — как проверить что хук срабатывает правильно

Теория

Как хук работает технически

🎨 Образ: хук — это таможня на границе. Каждый груз (инструмент) проходит через scanner (stdin JSON). Таможня проверяет — и либо пропускает (exit 0), либо задерживает (exit 2). Груз не знает о таможне — просто движется по конвейеру.

Claude Code вызывает хук как обычный bash-скрипт. Передаёт данные через stdin в формате JSON. Хук анализирует данные, выполняет свою логику, возвращает результат через exit code.

Код
Claude Code хочет записать файл
        ↓
Вызывает PreToolUse хук (matcher: "Write|Edit")
        ↓
Передаёт через stdin JSON:
{
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/project/config.py",
    "content": "API_KEY = 'sk-proj-abc123...'"
  },
  "session_id": "abc123",
  "cwd": "/Users/me/project"
}
        ↓
Хук анализирует tool_input.content
        ↓
exit 0 → Claude Code записывает файл
exit 2 → Claude Code останавливается, stderr хука передаётся Claude

Важно: блокирует exit code 2, не 1. Exit code 1 — это обычная ошибка скрипта, Claude продолжит работу.

При блокировке хук пишет сообщение в stderr (>&2) — это то что увидит Claude и сообщит пользователю.


🎨 Образ: хук no-secrets — как металлодетектор на входе в банк. Ты несёшь ключи в кармане — рамка пищит. Не потому что ты плохой — просто правило: ключи не проходят. Убрал в урну — прошёл.

Хук 1: pre-tool-use-no-secrets.sh

Задача: предотвратить случайный хардкод API-ключей, токенов и паролей в коде.

Проблема которую решает: разработчик часто вставляет ключ прямо в код "на время", забывает удалить, коммитит в git. Хук останавливает это до записи файла.

Создание файла

bash
mkdir -p ~/.claude/hooks
touch ~/.claude/hooks/pre-tool-use-no-secrets.sh
chmod +x ~/.claude/hooks/pre-tool-use-no-secrets.sh

Содержимое скрипта

bash
#!/bin/bash
# pre-tool-use-no-secrets.sh
# Блокирует запись файлов с захардкоженными секретами

# Читаем данные от Claude Code через stdin
INPUT=$(cat)

# Извлекаем данные из официального JSON-формата
# tool_name — на верхнем уровне
# file_path и content — внутри tool_input
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // empty')

# Для Edit-инструмента контент в поле new_string
if [[ "$TOOL_NAME" == "Edit" ]]; then
  CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // empty')
fi

# Проверяем только инструменты записи файлов
# (matcher "Write|Edit" в settings.json уже фильтрует,
#  но двойная проверка не помешает)
if [[ "$TOOL_NAME" != "Write" && "$TOOL_NAME" != "Edit" ]]; then
  exit 0  # Не запись — пропускаем
fi

# Паттерны которые ищем (возможные API-ключи и токены)
PATTERNS=(
  'sk-[a-zA-Z0-9]{20,}'          # OpenAI / Anthropic API keys
  'ghp_[a-zA-Z0-9]{36}'          # GitHub Personal Access Token
  'xoxb-[0-9]+-[a-zA-Z0-9]+'     # Slack Bot Token
  'AKIA[0-9A-Z]{16}'              # AWS Access Key
  'AIza[0-9A-Za-z_-]{35}'         # Google API Key
  'password\s*=\s*["\'][^"\']+["\']'  # Явный пароль в коде
  'secret\s*=\s*["\'][^"\']+["\']'    # Явный секрет в коде
)

# Проверяем контент на каждый паттерн
for PATTERN in "${PATTERNS[@]}"; do
  if echo "$CONTENT" | grep -qE "$PATTERN"; then
    # Сообщение в stderr — его увидит Claude и передаст пользователю
    echo "БЛОКИРОВАНО: Обнаружен возможный секрет/API-ключ в файле $FILE_PATH" >&2
    echo "Паттерн: $PATTERN" >&2
    echo "Используй переменные окружения (.env) или secrets manager вместо хардкода." >&2
    exit 2  # Exit code 2 = блокировка действия
  fi
done

exit 0  # Секретов не найдено — разрешаем

Добавление в settings.json

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/pre-tool-use-no-secrets.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Обрати внимание на структуру:

  • "PreToolUse" — CamelCase (не pre_tool_use)
  • "matcher": "Write|Edit" — хук срабатывает только при записи/редактировании файлов (не при чтении, не при bash-командах)
  • "type": "command" (не "type": "bash")
  • "timeout": 30 — если скрипт не ответит за 30 секунд, Claude продолжит

Тест хука

Дай Claude Code команду:

Напиши в чат
Создай файл config.py с содержимым: API_KEY = 'sk-proj-test123456789012345678901234'

Ожидаемый результат:

Напиши в чат
БЛОКИРОВАНО: Обнаружен возможный секрет/API-ключ в файле config.py
Используй переменные окружения (.env) или secrets manager вместо хардкода.

Claude Code не запишет файл. Предложит использовать .env.


🎨 Образ: budget-check хук — это счётчик воды. Набрал 500 литров — подача перекрыта. Не потому что воды нет — просто лимит установлен. Хочешь больше — открой кран вручную завтра.

Хук 2: pre-tool-use-budget-check.sh

Задача: контролировать расходы — останавливать Claude Code если операций за день слишком много.

Проблема которую решает: длинные автономные задачи могут делать тысячи операций. Хук устанавливает жёсткий лимит.

bash
#!/bin/bash
# pre-tool-use-budget-check.sh
# Контроль бюджета по количеству операций в сутки

COUNTER_FILE="/tmp/claude_ops_$(date +%Y%m%d).count"
DAILY_LIMIT=500  # Максимум операций за день

# Читаем текущий счётчик
if [ -f "$COUNTER_FILE" ]; then
  CURRENT=$(cat "$COUNTER_FILE")
else
  CURRENT=0
fi

# Проверяем лимит
if [ "$CURRENT" -ge "$DAILY_LIMIT" ]; then
  echo "СТОП: Дневной лимит операций достигнут ($CURRENT/$DAILY_LIMIT)" >&2
  echo "Сброс произойдёт в полночь. Для ручного сброса: rm $COUNTER_FILE" >&2
  exit 2  # Exit code 2 = блокировка
fi

# Увеличиваем счётчик
echo $((CURRENT + 1)) > "$COUNTER_FILE"

# Предупреждение на 80% использования (через stdout — не блокирует)
THRESHOLD=$((DAILY_LIMIT * 80 / 100))
if [ "$CURRENT" -ge "$THRESHOLD" ]; then
  echo "ПРЕДУПРЕЖДЕНИЕ: Использовано $CURRENT/$DAILY_LIMIT операций (80% лимита)"
fi

exit 0

Добавление в settings.json (рядом с первым хуком)

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/pre-tool-use-no-secrets.sh",
            "timeout": 30
          }
        ]
      },
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/pre-tool-use-budget-check.sh"
          }
        ]
      }
    ]
  }
}

Обрати внимание: у первого хука "matcher": "Write|Edit" — он проверяет только запись файлов. У второго matcher не указан — значит он срабатывает на все инструменты.

Несколько PreToolUse хуков выполняются параллельно. Если любой возвращает exit 2 — действие блокируется.


🎨 Образ: audit-log хук — это бортовой самописец (чёрный ящик) в самолёте. Записывает каждое движение непрерывно. После "аварии" (что-то сломалось) открываешь ящик и видишь точно: в 14:23 Claude изменил config.py, в 14:25 запустил bash-команду. Без ящика — только гадать.

Хук 3: post-tool-use-audit-log.sh

Задача: вести полный журнал что Claude изменял — какие файлы, в какое время, какой инструмент.

Проблема которую решает: после сессии непонятно что именно изменил Claude. Лог позволяет отследить каждое изменение и откатить если нужно.

bash
#!/bin/bash
# post-tool-use-audit-log.sh
# Аудит-лог всех изменений файлов

LOG_FILE="$HOME/.claude/audit-log.txt"
mkdir -p "$(dirname "$LOG_FILE")"

# Читаем данные от Claude Code через stdin
INPUT=$(cat)

# Извлекаем информацию о действии
# tool_name — верхний уровень, остальное — внутри tool_input
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
PROJECT=$(basename "$(pwd)")

# Пишем только действия с файлами
if [[ -n "$FILE_PATH" ]]; then
  echo "[$TIMESTAMP] PROJECT=$PROJECT TOOL=$TOOL_NAME FILE=$FILE_PATH" >> "$LOG_FILE"
fi

# Также логируем bash-команды (поле command внутри tool_input)
BASH_CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if [[ -n "$BASH_CMD" ]]; then
  # Показываем первые 100 символов команды
  SHORT_CMD="${BASH_CMD:0:100}"
  echo "[$TIMESTAMP] PROJECT=$PROJECT BASH: $SHORT_CMD" >> "$LOG_FILE"
fi

exit 0  # PostToolUse хуки не блокируют — всегда exit 0

Полный settings.json с тремя хуками + уведомление

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/pre-tool-use-no-secrets.sh",
            "timeout": 30
          }
        ]
      },
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/pre-tool-use-budget-check.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit|Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/post-tool-use-audit-log.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude завершил задачу\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Чеклист формата — проверь у себя:

  • Имена событий CamelCase: PreToolUse, PostToolUse, Stop (не snake_case)
  • Тип обработчика: "type": "command" (не "type": "bash")
  • Каждое событие → массив → объект с matcher + hooks → массив обработчиков
  • matcher фильтрует инструменты: "Write|Edit", "Bash", или пусто для всех

Как читать аудит-лог

Код
[2026-10-04 14:23:01] PROJECT=acme-realty TOOL=Write FILE=/project/index.md
[2026-10-04 14:23:04] PROJECT=acme-realty TOOL=Edit FILE=/project/CLAUDE.md
[2026-10-04 14:23:09] PROJECT=acme-realty BASH: mkdir -p .claude/skills
[2026-10-04 14:25:33] PROJECT=my-platform TOOL=Write FILE=/strategy/plan.md

Видишь: время, проект, инструмент, файл. Если что-то сломалось — знаешь точно что и когда Claude трогал.

bash
# Посмотреть лог за сегодня
tail -50 ~/.claude/audit-log.txt

# Найти все изменения конкретного файла
grep "CLAUDE.md" ~/.claude/audit-log.txt

# Найти все действия в конкретном проекте
grep "PROJECT=acme-realty" ~/.claude/audit-log.txt

🎨 Образ: защита .env файла — как красная пломба на распределительном щите. "Не трогать без разрешения электрика." Claude видит пломбу — останавливается. Без пломбы — может случайно перепутать провода, и всё оборудование встанет.

Из практики: реальный кейс с .env файлом

Из транскрипта: "Идеально, мы бы не хотели чтобы Claude затронул .env документ, потому что если он его изменяет — все автоматизации ломаются, они все зависят от этих паролей."

Вариация хука для защиты конкретного файла:

bash
#!/bin/bash
# Защита .env файла от любых изменений Claude

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Блокируем любые изменения .env
if [[ "$FILE_PATH" == *".env"* ]] && [[ "$TOOL_NAME" == "Write" || "$TOOL_NAME" == "Edit" ]]; then
  echo "БЛОКИРОВАНО: .env файл защищён от изменений" >&2
  echo "Файл содержит секреты. Редактируй вручную." >&2
  exit 2  # Exit code 2 = блокировка
fi

exit 0

После этого хука Claude Code буквально ответит: "Я не могу этого сделать — хук блокирует мне доступ к этому файлу."

Ещё проще — можно использовать "if" фильтр в settings.json вместо проверки в скрипте:

json
{
  "matcher": "Write|Edit",
  "hooks": [
    {
      "type": "command",
      "if": "Write(*.env)",
      "command": "echo 'БЛОКИРОВАНО: .env защищён' >&2 && exit 2"
    },
    {
      "type": "command",
      "if": "Edit(*.env)",
      "command": "echo 'БЛОКИРОВАНО: .env защищён' >&2 && exit 2"
    }
  ]
}

Здесь "if" работает как дополнительный фильтр по аргументам: форма Инструмент(шаблон) проверяет один инструмент, поэтому для Write и Edit два обработчика. Хук сработает только для файлов .env.


Бонус: HTTP webhook — хук без bash-скрипта

Не обязательно всё делать через bash. Если у тебя есть сервер (или сервис типа Telegram Bot API), можно отправлять данные через HTTP.

Пример: уведомление в Telegram при изменении файлов

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:3000/hooks/file-changed",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

Claude Code отправит POST-запрос с JSON-данными о файле. Твой сервер получит:

json
{
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": { "file_path": "/project/index.md", "content": "..." },
  "tool_response": "File written successfully",
  "cwd": "/Users/me/project"
}

Сервер может переслать это в Telegram, Slack, записать в базу — что угодно.

🎨 Образ: command-хук — это охранник на посту. HTTP-хук — это охранник который звонит в центральный офис. Офис решает что делать. Полезно когда логика проверки сложная или когда нужно связать Claude Code с внешними системами.

Когда использовать HTTP вместо command:

  • Уведомления во внешний сервис (Telegram, Slack, Discord)
  • Централизованный аудит для нескольких машин
  • Когда логика проверки живёт на сервере (микросервис валидации)

Тестирование хуков: чеклист

После создания каждого хука — проверить:

Для no-secrets хука:

Напиши в чат
Создай файл test.py с содержимым: token = 'sk-proj-realkey123456789012345'

Ожидание: Claude заблокирован, видишь сообщение хука.

Для audit-log хука:

Напиши в чат
Создай файл test-audit.md с текстом "Тест аудита"

Затем: tail -5 ~/.claude/audit-log.txt — новая запись должна появиться.

Для stop-notification хука:

Напиши в чат
Что такое Claude Code? (короткий вопрос)

Ожидание: после ответа появится Mac OS уведомление.


Практика

Задание: Поднять все три хука

  1. Создай папку ~/.claude/hooks/
  2. Создай три bash-скрипта с содержимым из урока
  3. Дай права на выполнение: chmod +x ~/.claude/hooks/*.sh
  4. Создай или обнови .claude/settings.json — добавь все три хука по шаблону из урока
  5. Протестируй каждый хук (чеклист выше)
  6. Посмотри как выглядит аудит-лог после нескольких операций

Цель: три рабочих хука, понимание exit code логики, первый аудит-лог с реальными записями


Инструменты и ресурсы

  • jq — парсинг JSON в bash (brew install jq на Mac)
  • chmod +x — права на выполнение скрипта
  • osascript — Mac OS нативные уведомления (встроен в macOS)
  • tail -f ~/.claude/audit-log.txt — live-просмотр лога в реальном времени
  • /hooks — команда для просмотра активных хуков через Claude Code terminal

Ключевые выводы

exit 0 = разрешить, exit 2 = заблокировать. Не 1, а именно 2! Exit 1 — это просто ошибка скрипта, Claude продолжит работу.

При блокировке сообщение пишем в stderr (>&2), не в stdout. Stderr передаётся Claude как причина блокировки.

matcher фильтрует по инструменту: "Write|Edit" — только файловые операции. Без matcher хук срабатывает на всё.

PostToolUse хуки всегда exit 0 — они логируют, не блокируют. Не нужно останавливать Claude после действия.

Данные от Claude Code приходят в формате JSON через stdin. Путь к файлу — в tool_input.file_path, не просто file_path.

Три хука закрывают три основные задачи: безопасность (секреты), экономика (бюджет), аудит (кто что трогал). Плюс HTTP-webhook для внешних уведомлений.

Формат settings.json: имена событий CamelCase (PreToolUse), тип обработчика "command" (не "bash"), трёхуровневая вложенность.


Что дальше

→ Субагенты: специализация и контекст

Отметка хранится только в этом браузере и никуда не отправляется. Мой прогресс