Мастер Claude · Блок 5. Hooks

Урок 50 из 93 · ~10 мин чтения

Итог фазы 5 — архитектура hook-системы

Урок пока закрыт

Курс проходится последовательно.

К текущему уроку

Почему это важно именно вам

Десять дней назад вы не знали, что такое hooks. Сейчас вы знаете пять типов, двадцать событий, правила матчеров и exit-коды. Это хорошо, но ещё не работает. Знание, которое лежит по отдельным урокам, не превращается само по себе в систему.

Сегодня — сборка. Не новая теория, а архитектура из того, что уже изучено. Конкретный вопрос: как выглядит минимальный production-стек hooks для человека, который работает с документами, отчётами и письмами каждый день?

Если вы настроите то, что будет в «Задании на сегодня», — Claude перестанет быть инструментом, который нужно постоянно контролировать. Он начнёт сам отчитываться о своих действиях, предупреждать вас о завершении задач и работать с защитой от разрушительных операций. Это не фича — это рабочий процесс, который держится сам.


Что это такое

Прежде чем собирать — короткое резюме того, что изучили в фазе 5. Не полный курс, а карта: где что лежит.

Типы hooks (День 41) — пять видов, каждый для своего уровня перехвата:

В ежедневной практике директора нужны первые два. mcp_tool и agent — для более продвинутых сценариев.

События (День 42) — двадцать точек перехвата, но для работы хватает пяти:

Событие Когда срабатывает
SessionStart Claude начинает сессию
UserPromptSubmit Пользователь отправил запрос, до обработки
PreToolUse Claude собирается вызвать инструмент
PostToolUse Инструмент вызван и завершил работу
Stop Claude завершил ответ и ждёт следующего ввода

Остальные — SubagentStop, PreCompact, различные варианты Notification — нужны в редких специализированных сценариях.

Матчеры (День 43) — как указать, на какой именно инструмент реагировать:

"matcher": "Edit"                    // точная строка
"matcher": "Edit|Write"    // pipe-separated для нескольких
"matcher": "^(Bash|Edit)$"           // regexp

Поле if позволяет добавить условие по содержимому события — например, смотреть на конкретный аргумент команды.

Exit codes и JSON (День 44) — как hook управляет поведением Claude:

Если hook возвращает JSON в stdout, Claude читает поля:
- "continue": false — остановить сессию
- {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "allow"}} — явное решение по разрешению; значения allow, deny, ask, defer
- {"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": "текст"}} — добавить текст в контекст (для UserPromptSubmit). Поля prompt_injection не существует

Не путайте блокировку операции и остановку сессии

Одну операцию отклоняют двумя способами: код выхода 2 или JSON с permissionDecision: "deny" на PreToolUse. А "continue": false завершает всю сессию целиком. Не путайте эти два механизма — последствия разные.


Как работает на практике

Абстрактные механизмы понятны. Теперь — конкретный сценарий: «Director's hook stack». Минимальный набор из четырёх hooks, которые реально полезны в ежедневной работе.

Hook 1: Лог изменённых файлов

Событие: PostToolUse
Matcher: Edit|Write
Что делает: записывает в лог-файл дату, имя инструмента и путь к изменённому файлу

Зачем: вы отдали Claude задачу и ушли на встречу. Вернулись через час. Что именно было изменено? Без лога — нужно вручную сравнивать git diff или вспоминать. С логом — один взгляд на ~/.claude/audit.log.

#!/bin/bash
# ~/.claude/hooks/log-edits.sh

exec 2>> ~/.claude/hooks-errors.log

INPUT=$(cat)
TOOL_NAME=$(jq -r '.tool_name' <<<"$INPUT")
FILE_PATH=$(jq -r '.tool_input.file_path // "неизвестен"' <<<"$INPUT")
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

echo "${TIMESTAMP} | ${TOOL_NAME} | ${FILE_PATH}" >> ~/.claude/audit.log

exit 0

Hook 2: Уведомление в Telegram при завершении

Событие: Stop
Что делает: отправляет POST на Telegram Bot API с текстом «Claude завершил задачу» и временем

Зачем: Claude работает с большим файлом — вы занимаетесь чем-то другим. Когда он закончит — телефон вибрирует. Не нужно переключаться каждые три минуты чтобы проверить статус.

#!/bin/bash
# ~/.claude/hooks/notify-stop.sh

exec 2>> ~/.claude/hooks-errors.log

BOT_TOKEN="${TELEGRAM_BOT_TOKEN}"
CHAT_ID="${TELEGRAM_CHAT_ID}"
TIMESTAMP=$(date '+%H:%M:%S')

MESSAGE="✓ Claude завершил задачу в ${TIMESTAMP}"

curl -s -X POST \
  "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
  -d "chat_id=${CHAT_ID}&text=${MESSAGE}" \
  > /dev/null

exit 0

Не хардкодьте токены в скриптах

TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID берутся из переменных окружения, которые заданы в ~/.zshenv. Если положить токен прямо в скрипт — это файл на диске, который синхронизируется в облако. Переменные окружения в ~/.zshenv безопаснее.

Hook 3: Автодобавление даты к каждому запросу

Событие: UserPromptSubmit
Что делает: возвращает JSON с additionalContext внутри hookSpecificOutput — текст попадает в контекст запроса (в самом чате он не отображается)

Зачем: Claude не знает текущую дату без явного указания. Если вы пишете «подготовь отчёт за прошлую неделю» — Claude не знает, какая сейчас неделя. Этот hook решает задачу один раз и навсегда.

#!/bin/bash
# ~/.claude/hooks/inject-date.sh

exec 2>> ~/.claude/hooks-errors.log

DATE=$(date '+%Y-%m-%d, %A')
WEEKNUM=$(date '+%V')

python3 -c "
import json
print(json.dumps({
    'hookSpecificOutput': {
        'hookEventName': 'UserPromptSubmit',
        'additionalContext': f'[Контекст: сегодня {\"$DATE\"}, неделя №{\"$WEEKNUM\"}, часовой пояс UTC+3]'
    }
}))
"

exit 0

python3 для JSON — не перестраховка

Если дата содержит символы, которые ломают JSON (этого не будет, но пусть будет принцип): конкатенация строк bash ломается на любом спецсимволе. python3 -c "json.dumps(...)" — единственный надёжный способ формировать JSON программно. Привыкайте к этому паттерну.

Hook 4: Блокировка опасных команд Bash

Событие: PreToolUse
Matcher: Bash
Что делает: смотрит на команду, которую Claude собирается выполнить; если там есть rm -rf или похожее — возвращает exit 2

Зачем: Claude иногда предлагает «почистить временные файлы» или «удалить старую папку». Большинство раз это безопасно. Но один раз из ста — не то дерево. Hook позволяет перехватить именно опасные варианты, не блокируя весь Bash.

#!/bin/bash
# ~/.claude/hooks/guard-bash.sh

exec 2>> ~/.claude/hooks-errors.log

INPUT=$(cat)
COMMAND=$(jq -r '.tool_input.command // ""' <<<"$INPUT")

# Опасные паттерны
DANGEROUS_PATTERNS=(
  "rm -rf /"
  "rm -rf ~"
  "rm -rf \$HOME"
  "chmod -R 777"
  "dd if="
  "> /etc/"
)

for PATTERN in "${DANGEROUS_PATTERNS[@]}"; do
  if echo "${COMMAND}" | grep -qF "${PATTERN}"; then
    echo "BLOCKED: опасная команда обнаружена: ${PATTERN}" >&2
    echo "BLOCKED: ${COMMAND}" >> ~/.claude/audit.log
    exit 2
  fi
done

exit 0

Этот hook — не замена осторожности

Список паттернов неполный — невозможно перечислить все опасные варианты. Hook ловит очевидные случаи. Для критических папок используйте PreToolUse с более строгой логикой или просто не работайте в autonomy режиме без присмотра.


Полный settings.json

Все четыре hooks в одном файле. Это кладётся в ~/.claude/settings.json — глобально, для всех проектов.

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/inject-date.sh"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/guard-bash.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/log-edits.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/notify-stop.sh"
          }
        ]
      }
    ]
  }
}

После того как файл создан — сделайте все четыре скрипта исполняемыми:

chmod +x ~/.claude/hooks/*.sh

И создайте директорию для логов, если её нет:

mkdir -p ~/.claude/hooks
touch ~/.claude/audit.log ~/.claude/hooks-errors.log

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

Скрипт молча падает. Claude продолжает работу — exit code отличный от 2 не останавливает его. Вы думаете, что лог пишется. На самом деле скрипт упал на второй строке. Без exec 2>> ~/.claude/hooks-errors.log в начале каждого скрипта вы об этом никогда не узнаете. Добавьте эту строку первой — до любой другой логики.

Дублирование hooks в проектном и глобальном settings. Если log-edits.sh прописан и в ~/.claude/settings.json, и в .claude/settings.json внутри проекта — он сработает дважды. Каждая строка в логе появится два раза. Правило простое: глобальные hooks (аудит, уведомления, дата) — только в ~/.claude/settings.json. Проектные (специфические блокировки, проектный контекст) — только в .claude/settings.json проекта.

JSON сломан из-за спецсимволов. Если в подставляемый текст попадают кавычки, обратными слешами или переносами строк — конкатенация bash сформирует невалидный JSON. Claude не поймёт его и проигнорирует. Всегда используйте python3 -c "json.dumps(...)" для формирования JSON в скриптах — это единственный надёжный способ.

hook уведомления срабатывает слишком часто. Stop — это завершение каждого ответа, не завершение сессии. Если Claude пишет пять ответов в диалоге — придёт пять уведомлений. Для коротких интерактивных сессий это раздражает. Решение: добавьте в скрипт проверку на длительность сессии или используйте SessionEnd вместо Stop — оно срабатывает только когда сессия закрыта полностью.

Блокирующий hook замедляет работу. Каждый PreToolUse hook запускает отдельный процесс. Если у вас три матчера на Bash и каждый скрипт делает что-то медленное (сетевой запрос, тяжёлый grep) — Claude будет ощутимо тормозить. Держите guard-скрипты лёгкими: только grep по строке, exit, ничего лишнего.


Когда нужно / когда нет

Это важнее, чем знать синтаксис. Hook — это инфраструктура: её нужно создать, поддерживать, отлаживать когда что-то идёт не так. Плохо написанный hook хуже, чем отсутствие hook.

Используйте hooks когда:

Достаточно CLAUDE.md когда:

Достаточно skill или агента когда:

Ключевой принцип разграничения

Hooks — это инфраструктурный слой: что происходит вокруг Claude, до и после него. CLAUDE.md — поведенческий слой: как Claude думает и отвечает внутри сессии. Skills и агенты — процессный слой: конкретные рабочие процессы. Смешивать их — источник путаницы и сложно отлаживаемых систем. Если вы ловите себя на мысли «напишу hook, который скажет Claude делать X» — это, скорее всего, задача для CLAUDE.md.

Не нужно ничего когда:


Связь с другими уроками

День 41 — первое знакомство с hooks: что это, зачем, пять типов. Сегодняшний урок предполагает, что вы его прошли.

День 42 — полный список событий; именно оттуда взяты UserPromptSubmit, PreToolUse, PostToolUse, Stop. Если не помните, чем Stop отличается от SessionEnd — вернитесь.

День 43 — матчеры; синтаксис "matcher": "Edit|Write" и поле if разобраны там.

День 44 — exit codes; принципиальное различие между exit 0, exit 2 и остальными. Без этого guard-hook в Hook 4 непонятен.

Дни 45-46 — command и http hooks; скрипты выше построены на этих принципах.

День 47 — контекст через хуки на UserPromptSubmit; Hook 3 из сегодняшнего урока — прямое применение того материала.

Дни 48-49 — практика аудита и уведомлений; сегодня объединяем обе практики в единую конфигурацию.

День 8 («Режимы разрешений») — autonomy-режим без hooks это повышенный риск; hooks и permission-mode работают в паре. Если включён автономный режим — PreToolUse guard становится обязательным, не опциональным.


Задание на сегодня

Создайте три файла и проверьте, что всё работает.

Шаг 1. Создайте директорию и заготовьте скрипты:

mkdir -p ~/.claude/hooks
touch ~/.claude/audit.log ~/.claude/hooks-errors.log

Шаг 2. Создайте ~/.claude/hooks/inject-date.sh и ~/.claude/hooks/log-edits.sh — код выше в разделе «Как работает на практике». Сделайте их исполняемыми:

chmod +x ~/.claude/hooks/inject-date.sh
chmod +x ~/.claude/hooks/log-edits.sh

Шаг 3. Обновите ~/.claude/settings.json — добавьте блок hooks из раздела «Полный settings.json». Если файл не существует — создайте его с нуля.

Шаг 4. Запустите короткую тестовую сессию: попросите Claude создать или отредактировать любой тестовый файл.

Критерий выполнено:
- cat ~/.claude/audit.log — показывает хотя бы одну строку с операцией Edit или Write
- cat ~/.claude/hooks-errors.log — пустой или не существует (ошибок нет)
- Спросите Claude «какое сегодня число» — он отвечает верно, хотя вы этого не писали (в самом диалоге блок с датой не виден: он уходит в контекст системным напоминанием)

Уведомление в Telegram (Hook 2) и guard-bash (Hook 4) — опциональны на этом шаге; настройте их если есть Telegram-бот и токен в ~/.zshenv.


Резюме

Фаза 5 — десять дней, от «что такое hooks» до рабочей production-конфигурации. Вот что осталось:

Следующая фаза — MCP. Если hooks это «что происходит вокруг Claude», то MCP это «к чему Claude имеет доступ». День 51 — старт.

Следующий урок откроется после отметки.