Перейти к основному содержимому

Хуки: детерминированная автоматизация

Продвинутый

Хуки — это shell-команды, которые Claude Code запускает автоматически в определённых точках своего жизненного цикла. Там, где разрешения решают, разрешено ли действие, хуки позволяют вам выполнять вокруг него детерминированную логику — форматирование, валидацию, логирование, проверки, маскирование. С их помощью поведение становится гарантированным, а не зависит от просьбы «пожалуйста, не забудь».

What you'll learn
  • Когда выбирать хук вместо инструкции, разрешения или скилла
  • Полный жизненный цикл: события сессии, промпта, инструмента, субагента, задачи, компактификации и конца хода
  • Как настраивается хук: событие, матчер и JSON-нагрузка в stdin
  • Два способа, которыми хук блокирует действие, — код выхода 2 против JSON в stdout
  • Универсальные поля JSON-вывода, общие для событий: continue, additionalContext, systemMessage, reloadSkills, sessionTitle, suppressOutput, updatedInput
  • Хорошие практики и типичные ошибки, отличающие быстрые и безопасные хуки от медленных и молчаливых

Когда выбирать хук​

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

  • Автоформатирование / линтинг после каждой правки файла (PostToolUse на Edit|Write).
  • Блокировка действия, нарушающего правило, до его выполнения (PreToolUse).
  • Маскирование сообщения ассистента до того, как оно попадёт в терминал (MessageDisplay).
  • Перезагрузка скиллов или установка заголовка сессии после SessionStart.
  • Уведомление или запись в лог, когда сессия завершается, задача заканчивается или ход падает с ошибкой (Stop, StopFailure, SessionEnd).
  • Внедрение контекста при старте сессии или при загрузке инструкций.

Как они работают​

Вы регистрируете хуки в settings.json, сопоставляя их с событием (и часто с матчером — обычно это имя инструмента, иногда тип субагента, триггер компактификации или вид уведомления). Когда событие срабатывает, Claude запускает вашу команду и передаёт ей JSON-нагрузку в stdin (имя события, сессию и поля, специфичные для события, такие как имя инструмента и его входные данные). Код выхода и stdout вашей команды определяют, что произойдёт дальше.

Guided walkthrough1 of 4
  1. Зарегистрируйте хук в settings.json под нужным событием жизненного цикла — например, PostToolUse для автоматизации после инструмента или MessageDisplay для маскирования.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}

Хук выше достаёт путь изменённого файла из JSON в stdin (.tool_input.file_path) и форматирует его. Не рассчитывайте, что путь лежит в переменной окружения — читайте его из stdin. Полезные плейсхолдеры путей вроде ${CLAUDE_PROJECT_DIR} доступны для поиска ваших скриптов.

Жизненный цикл с одного взгляда​

Хуки Claude Code охватывают всю сессию, а не только вызовы инструментов. Сгруппируйте их по фазам, чтобы понимать, куда подвесить свою логику.

ФазаСобытияТипичное применение
СессияSessionStart, SessionEnd, Setup, InstructionsLoaded, ConfigChange, CwdChanged, DirectoryAdded, FileChangedВнедрить контекст при старте, сделать снимок в конце, отреагировать на перезагрузку CLAUDE.md или изменение .env на диске
ПромптUserPromptSubmit, UserPromptExpansionПроверить или переписать промпт, прежде чем его увидит Claude
ИнструментPreToolUse, PermissionRequest, PermissionDenied, PostToolUse, PostToolUseFailure, PostToolBatchРазрешить/запретить действие, передать решение человеку, отформатировать после правок, повторить после отказа в авто-режиме
СубагентSubagentStart, SubagentStopЛогировать запуск/остановку, не давать субагенту вернуть результат
ЗадачаTaskCreated, TaskCompletedОткатить случайно созданную задачу, поставить проверку на завершение
КомпактификацияPreCompact, PostCompactСделать снимок транскрипта до компактификации, проверить после
МодельPreModelSwitch, PostModelSwitchЗаблокировать смену модели до её применения; отреагировать после смены, в том числе когда возобновлённая сессия восстанавливает модель
ХодStop, StopFailure, Notification, MessageDisplayУведомить о завершении, поднять тревогу при сбое API, замаскировать отображаемый текст
Команда + MCPTeammateIdle, Elicitation, ElicitationResult, WorktreeCreate, WorktreeRemoveКоординация между сессиями, проверка MCP-elicitation, обёртка операций с worktree

Точные поля stdin каждого события, грамматика матчеров и правила блокировки описаны в официальном справочнике по хукам. Каталог выше — это карта, а справочник — местность.

Как хук блокирует​

Двумя способами, в зависимости от события:

  • Код выхода 2 — хук проваливает действие, а то, что он записал в stderr, становится сообщением, которое видит Claude. Просто и работает для command-хуков на блокирующих событиях (PreToolUse, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange, PostToolBatch, PreCompact, PreModelSwitch, Elicitation, ElicitationResult, WorktreeCreate, WorktreeRemove). На PermissionRequest код выхода 2 игнорируется; отказывайте через его объект decision.
  • JSON в stdout (код выхода 0) — верните структурированное решение. Для PreToolUse это permissionDecision со значением "allow", "deny", "ask" (показать вызов пользователю) или "defer" (только для запусков -p, которыми управляет приложение, возобновляющее их), вместе с permissionDecisionReason. Неблокирующие события (PostToolUse, Notification, MessageDisplay, StopFailure, большинство событий сессии) игнорируют попытки блокировки — но их JSON-вывод всё равно может направлять то, что Claude увидит дальше.

Скрипт ниже — хук PreToolUse на инструмент Bash. Читайте его сверху вниз: он достаёт команду из stdin, и если она выглядит разрушительной, пишет причину в stderr и завершается с кодом 2, блокируя её.

#!/usr/bin/env bash
# PreToolUse hook on the Bash tool: refuse to delete things.
command=$(jq -r '.tool_input.command' < /dev/stdin)
if [[ "$command" == rm\ * || "$command" == *"rm -rf"* ]]; then
echo "Blocked: destructive 'rm' is not allowed by policy." >&2
exit 2
fi
exit 0

Универсальные поля JSON-вывода​

Большинство событий принимает в stdout структурированный JSON-объект, который направляет дальнейший ход событий — отдельно от полей решений, специфичных для события, таких как permissionDecision. Самые полезные:

ПолеЧто делаетГде работает
continueЕсли false, Claude полностью прекращает обработку — приоритетнее любого решения, специфичного для событияБольшинство событий
systemMessageДобавляет сообщение в контекст / транскрипт Claude, чтобы он видел, почему вы вмешалисьБольшинство блокирующих событий
additionalContextВнедряет дополнительный контекст, который Claude может прочитать и использовать на следующем ходуSessionStart, UserPromptSubmit, другие
reloadSkillsПовторно сканирует каталоги скиллов, чтобы сессия подхватила только что написанный скиллSessionStart (и другие события сессии)
sessionTitleУстанавливает заголовок сессии в интерфейсе — внутри hookSpecificOutput на SessionStartSessionStart
suppressOutputСкрывает вывод инструмента от Claude (полезно для шумных команд)События инструментов
updatedInputПереписывает входные данные инструмента перед запуском (например, добавляет флаг --dry-run)PreToolUse
terminalSequenceВыдаёт escape-последовательности терминала — звонок, заголовок окна, значок уведомленияNotification, StopFailure, другие

Поля решений, специфичные для события, — permissionDecision ("allow", "deny", "ask" или "defer", плюс permissionDecisionReason) на PreToolUse, decision.behavior ("allow" или "deny") на PermissionRequest и retry: true на PermissionDenied, позволяющее Claude повторить отклонённый вызов, — накладываются поверх универсальных.

Мысленная модель​

Хук PreToolUse запускается до действия и может его заблокировать; хук PostToolUse запускается после его успешного выполнения и реагирует на результат.

События хуков с одного взгляда
Нажмите Enter или пробел, чтобы перевернуть карточку. Используйте стрелки влево и вправо для перехода между карточками.Показан термин.
1 / 6

Хорошие практики​

  • Делайте хуки быстрыми и идемпотентными — они запускаются часто. Хуки SessionEnd делят один общий бюджет в 1,5 секунды, поэтому работу при завершении сессии держите минимальной.
  • Громко падайте на настоящих проблемах, но не блокируйте из-за косметических.
  • Относитесь к выводу хука как к обратной связи для Claude — чёткое сообщение в stderr (код выхода 2) или в systemMessage (JSON) помогает ему исправиться самому.
  • Предпочитайте JSON-вывод кодам выхода, когда хотите сказать больше, чем «заблокировано»: permissionDecision: "ask" на PreToolUse передаёт решение человеку, не хлопая дверью, а additionalContext позволяет направлять, а не только отказывать.
  • Хуки выполняются с привилегиями вашей оболочки — проверяйте любой хук, который написали не вы (Проверка стороннего кода).

Типичные ошибки​

  • Чтение пути к файлу из переменной окружения. Путь лежит в JSON на stdin (.tool_input.file_path), а не в $CLAUDE_FILE_PATH. Пропустите stdin через jq.
  • Молчаливые блокировки. Если хук PreToolUse завершается с кодом 2 и ничего не пишет в stderr, Claude заблокирован, но не знает, почему, и не может подстроиться. Всегда пишите понятную причину (или используйте permissionDecisionReason).
  • Попытка заблокировать неблокирующее событие. PostToolUse срабатывает после выполнения инструмента — код выхода 2 лишь шепнёт Claude сообщение, но не отменит действие. Если нужна проверка, вешайтесь на PreToolUse.
  • Медленные хуки. Хук PostToolUse запускается после каждой подходящей правки. Линтер на 3 секунды делает всю сессию вязкой — держите хуки быстрыми и, в идеале, обрабатывайте только то, что изменилось.
  • Слишком широкие матчеры. matcher: ".*" срабатывает на каждом инструменте. Сужайте точным именем, списком Edit|Write или полем if для отдельного обработчика (например, "if": "Bash(git push *)").
  • Забывать, что матчеры есть и у событий, не связанных с инструментами. SessionStart сопоставляется с startup|resume|clear|compact|fork; Notification — с видами вроде permission_prompt|idle_prompt|agent_completed; PreCompact — с manual|auto. Один хук, привязанный не к тому подмножеству, срабатывает либо слишком часто, либо никогда.
  • Доверять хукам, которые вы не писали. Хук выполняет произвольный shell с вашими привилегиями. Сначала проверяйте любой хук из плагина или шаблона — см. Проверка стороннего кода.
Watch out
  • Хук выполняет произвольный shell с вашими привилегиями — никогда не подключайте хук из плагина или шаблона, не прочитав его.

Рецепты, которые стоит позаимствовать​

Заготовки для копирования лежат в Рецептах хуков и settings.json. Несколько самых полезных, показывающих форму более новых событий:

Автоформатирование изменённых файлов (PostToolUse на Edit|Write)

{
"hooks": {
  "PostToolUse": [
    {
      "matcher": "Edit|Write",
      "hooks": [
        { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
      ]
    }
  ]
}
}

Заголовок сессии для каждого проекта при старте (SessionStart)

{
"hooks": {
  "SessionStart": [
    {
      "matcher": "startup|resume",
      "hooks": [
        {
          "type": "command",
          "command": "jq -nc --arg t \"$(basename \"$PWD\") — $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo detached)\" '{hookSpecificOutput:{sessionTitle:$t}}'"
        }
      ]
    }
  ]
}
}

Перезагрузка скиллов после SessionStart (подхватывает только что написанный скилл)

{
"hooks": {
  "SessionStart": [
    {
      "matcher": "startup|resume",
      "hooks": [
        { "type": "command", "command": "printf '{\"reloadSkills\": true}\n'" }
      ]
    }
  ]
}
}

Маскирование API-ключей в отображаемом тексте ассистента (MessageDisplay)

{
"hooks": {
  "MessageDisplay": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "jq -r '.message.content' | sed -E 's/(sk-[A-Za-z0-9_-]{20,})/sk-***REDACTED***/g'"
        }
      ]
    }
  ]
}
}

Уведомление о сбое хода (StopFailure — лимиты, перегрузка, аутентификация)

{
"hooks": {
  "StopFailure": [
    {
      "matcher": "rate_limit|overloaded|authentication_failed|billing_error",
      "hooks": [
        { "type": "command", "command": "osascript -e 'display notification \"Claude Code turn failed — check the terminal\" with title \"Claude Code\"'" }
      ]
    }
  ]
}
}

Проверьте себя

0/4
  1. Где хук находит путь к только что изменённому файлу?
  2. Хук PreToolUse завершается с кодом 2. Что происходит?
  3. Вы хотите, чтобы сессия подхватила скилл, который только что добавили в .claude/skills/, без перезапуска. Какое поле вывода и на каком событии?
  4. Вы хотите замаскировать секреты в тексте Claude до того, как он появится на экране пользователя. Какое событие?
Key takeaways
  • Хуки делают поведение гарантированным, а не запрошенным — детерминированная логика вокруг действий, которые разрешения лишь разрешают или запрещают.
  • Жизненный цикл охватывает более 30 событий по фазам сессии, промпта, инструмента, субагента, задачи, компактификации и хода — выберите правильную фазу, прежде чем писать скрипт.
  • Регистрируйте в settings.json событие плюс матчер; Claude передаёт JSON-нагрузку в stdin и читает ваш код выхода и вывод.
  • Читайте путь к файлу из stdin (.tool_input.file_path), а не из переменной окружения.
  • Блокируйте кодом выхода 2 (stderr становится сообщением) или структурированным JSON в stdout (permissionDecision на событиях инструментов, {continue: false}, чтобы остановить всё).
  • Универсальные поля вывода — additionalContext, systemMessage, reloadSkills, sessionTitle, suppressOutput, updatedInput — позволяют направлять Claude, а не только разрешать или отказывать.
  • Делайте хуки быстрыми, идемпотентными и с узкими матчерами — и проверяйте любой хук, который писали не вы, ведь он выполняется с привилегиями вашей оболочки.

Дальше​