SpyBara
Go Premium

hooks.md 2026-10-07 23:59 UTC to 2026-10-08 21:58 UTC

This page contains 575 additions and 406 deletions.

2026
Thu 1 23:59 Fri 2 22:59 Sat 3 23:57 Sun 4 23:58 Mon 5 23:58 Tue 6 23:59 Wed 7 23:59 Thu 8 22:58

Справочник по hooks

Справочник по событиям hook Claude Code, схеме конфигурации, форматам JSON входа/выхода, кодам выхода, асинхронным hooks, HTTP hooks, prompt hooks и MCP tool hooks.

Hooks — это определяемые пользователем команды оболочки, конечные точки HTTP, вызовы инструментов MCP, подсказки LLM или подагенты, которые выполняются автоматически в определённых точках жизненного цикла Claude Code. Claude Code запускает одни и те же события hook везде, где он работает: сеансы в терминале, расширения IDE, приложение Desktop и облачные сеансы. Используйте этот справочник для поиска схем событий, параметров конфигурации, форматов JSON входа/выхода и расширенных функций, таких как асинхронные hooks, HTTP hooks и MCP tool hooks.

Плагин также может регистрировать hooks как функции JavaScript, которые Claude Code вызывает в своём собственном процессе, которые могут отображаться в интерфейсе, а также реагировать на события. Плагин, который это делает, — это mod, и эти функциональные hooks рассматриваются в разделе Реагирование на события, а не здесь. Hooks на этой странице продолжают работать наряду с модами.

Жизненный цикл hook

Claude Code запускает hooks в определённых точках во время сеанса. Когда событие срабатывает и совпадает с фильтром, Claude Code передаёт JSON-контекст события вашему обработчику hook. Для command hooks входные данные поступают на stdin. Для HTTP hooks они поступают как тело POST-запроса. Ваш обработчик может затем проверить входные данные, выполнить действие и опционально вернуть решение.

События срабатывают в трёх ритмах:

  • один раз за сеанс: SessionStart и SessionEnd
  • один раз за ход: UserPromptSubmit, Stop и StopFailure
  • при каждом вызове инструмента внутри агентного цикла: PreToolUse и PostToolUse, за исключением вызовов EndConversation, которые пропускают оба
Диаграмма жизненного цикла hook, показывающая опциональный Setup, переходящий в SessionStart, затем цикл за ход, содержащий UserPromptSubmit, UserPromptExpansion для slash commands, вложенный агентный цикл (PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart/Stop, TaskCreated, TaskCompleted) и Stop или StopFailure, за которым следуют TeammateIdle, PreCompact, PostCompact и SessionEnd, с Elicitation и ElicitationResult вложенными внутри выполнения MCP tool, PermissionDenied как боковая ветвь от PermissionRequest для автоматических отказов, WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, FileChanged и DirectoryAdded как отдельные асинхронные события, PreModelSwitch как отдельное последовательное событие, которое запускается перед запрошенным переключением модели, PostModelSwitch как отдельное асинхронное событие, которое запускается после изменения модели сеанса, и MessageDisplay как событие только для отображения, которое запускается во время потоковой передачи текста сообщения помощника
<img src="https://mintcdn.com/claude-code/x7pO8l4XcvAXCoVc/images/hooks-lifecycle-dark.svg?fit=max&auto=format&n=x7pO8l4XcvAXCoVc&q=85&s=c9b3d88487335f58cce0b52e2f9e7531" className="hidden dark:block" alt="Диаграмма жизненного цикла hook, показывающая опциональный Setup, переходящий в SessionStart, затем цикл за ход, содержащий UserPromptSubmit, UserPromptExpansion для slash commands, вложенный агентный цикл (PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart/Stop, TaskCreated, TaskCompleted) и Stop или StopFailure, за которым следуют TeammateIdle, PreCompact, PostCompact и SessionEnd, с Elicitation и ElicitationResult вложенными внутри выполнения MCP tool, PermissionDenied как боковая ветвь от PermissionRequest для автоматических отказов, WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, FileChanged и DirectoryAdded как отдельные асинхронные события, PreModelSwitch как отдельное последовательное событие, которое запускается перед запрошенным переключением модели, PostModelSwitch как отдельное асинхронное событие, которое запускается после изменения модели сеанса, и MessageDisplay как событие только для отображения, которое запускается во время потоковой передачи текста сообщения помощника" width="520" height="1336" data-path="images/hooks-lifecycle-dark.svg" />

Таблица ниже суммирует, когда срабатывает каждое событие. Раздел Hook events документирует полную схему входа и параметры управления решением для каждого события.

Событие Когда оно срабатывает
SessionStart Когда сеанс начинается или возобновляется
Setup Когда вы запускаете Claude Code с --init-only, или с --init или --maintenance в режиме -p. Для одноразовой подготовки в CI или скриптах
UserPromptSubmit Когда отправляется промпт, прежде чем Claude его обработает. Также срабатывает для ходов, которые Claude Code начинает самостоятельно
UserPromptExpansion Когда команда, введённая пользователем, расширяется в запрос, прежде чем она достигнет Claude. Может заблокировать расширение
PreToolUse Перед выполнением вызова инструмента. Может заблокировать его
PermissionRequest Когда вызов инструмента требует решения о разрешении
PermissionDenied Когда автоматический режим отклоняет вызов инструмента, включая отклонения без вердикта классификатора. Используйте JSON hookSpecificOutput.retry: true, чтобы сообщить модели, что она может повторить попытку отклонённого вызова инструмента. Claude Code игнорирует retry, когда классификатор не выдал вердикт
PostToolUse После успешного выполнения вызова инструмента
PostToolUseFailure После неудачного выполнения вызова инструмента
PostToolBatch После разрешения полного пакета параллельных вызовов инструментов, перед следующим вызовом модели
Notification Когда Claude Code отправляет уведомление
MessageDisplay Во время отображения текста сообщения помощника
SubagentStart Когда порождается подагент
SubagentStop Когда подагент завершает работу
TaskCreated Когда задача создаётся через TaskCreate
TaskCompleted Когда задача отмечается как завершённая
Stop Когда Claude завершает ответ
StopFailure Когда ход завершается из-за ошибки API
TeammateIdle Когда товарищ по команде команды агентов собирается перейти в режим ожидания
InstructionsLoaded Когда файл CLAUDE.md или .claude/rules/*.md загружается в контекст. Срабатывает при запуске сеанса и когда файлы ленивой загрузки загружаются во время сеанса
ConfigChange Когда файл конфигурации изменяется во время сеанса
CwdChanged Когда рабочий каталог изменяется, например когда Claude выполняет команду cd. Полезно для реактивного управления окружением с помощью инструментов, таких как direnv
DirectoryAdded Когда рабочий каталог добавляется в середине сеанса через /add-dir или запрос управления SDK register_repo_root
FileChanged Когда наблюдаемый файл изменяется на диске. Поле matcher указывает, какие имена файлов отслеживать
WorktreeCreate Когда worktree создаётся через --worktree, isolation: "worktree", или для фонового сеанса. Заменяет поведение git по умолчанию
WorktreeRemove Когда удаляется worktree, созданный хуком WorktreeCreate
PreCompact Перед компактизацией контекста
PostCompact После завершения компактизации контекста
PreModelSwitch Перед тем как Claude Code применяет переключение модели, которое вы или клиент запросили. Может заблокировать переключение
PostModelSwitch После изменения модели сеанса, включая изменения, которые Claude Code делает самостоятельно, такие как восстановление модели при возобновлении сеанса
Elicitation Когда сервер MCP запрашивает ввод пользователя во время вызова инструмента
ElicitationResult После того как пользователь отвечает на запрос MCP, перед отправкой ответа обратно на сервер
SessionEnd Когда сеанс завершается

Как разрешается hook

Чтобы увидеть, как событие, фильтр и обработчик работают вместе, рассмотрим этот hook PreToolUse, который блокирует деструктивные команды оболочки.

Фильтр matcher сужает область до вызовов инструмента Bash, а условие if сужает её дальше до команд Bash, совпадающих с rm *, поэтому block-rm.sh запускается только когда оба фильтра совпадают:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}

Скрипт читает JSON входные данные из stdin, извлекает команду и возвращает permissionDecision со значением "deny", если она содержит rm -rf. Сохраните его в .claude/hooks/block-rm.sh в вашем проекте и сделайте его исполняемым с помощью chmod +x .claude/hooks/block-rm.sh, чтобы Claude Code мог его запустить:

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0  # no decision; normal permission flow applies
fi

Этот скрипт, как и другие примеры Bash на этой странице, которые анализируют JSON входные данные, использует jq, поэтому установите jq и убедитесь, что он находится в вашем PATH перед попыткой их использования.

Теперь предположим, что Claude Code решает запустить Bash "rm -rf /tmp/build" с конфигурацией macOS/Linux. Вот что происходит:

Диаграмма разрешения hook: срабатывает событие PreToolUse, фильтр проверяет совпадение Bash, затем условие if проверяет совпадение Bash(rm *). Если оба совпадают, команда hook запускается и возвращает permissionDecision deny, поэтому вызов инструмента блокируется и Claude Code продолжает работу. Если одна из проверок не совпадает, hook пропускается и вызов инструмента может продолжить работу. Диаграмма разрешения hook: срабатывает событие PreToolUse, фильтр проверяет совпадение Bash, затем условие if проверяет совпадение Bash(rm *). Если оба совпадают, команда hook запускается и возвращает permissionDecision deny, поэтому вызов инструмента блокируется и Claude Code продолжает работу. Если одна из проверок не совпадает, hook пропускается и вызов инструмента может продолжить работу.
1

Событие срабатывает

Событие PreToolUse срабатывает. Claude Code отправляет входные данные инструмента как JSON на stdin hook:

{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
2

Фильтр проверяет

Фильтр "Bash" совпадает с именем инструмента, поэтому эта группа hook активируется. Если вы опустите фильтр или используете "*", группа активируется при каждом возникновении события.

3

Условие if проверяет

Условие if "Bash(rm *)" совпадает, потому что rm -rf /tmp/build — это подкоманда, совпадающая с rm *, поэтому этот обработчик запускается. Если бы команда была npm test, проверка if не удалась бы и block-rm.sh никогда не запустился бы, избегая затрат на порождение процесса. Поле if опционально; без него каждый обработчик в совпадающей группе запускается.

4

Обработчик hook запускается

Скрипт проверяет полную команду и находит rm -rf, поэтому выводит решение на stdout:

{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}

Если бы команда была более безопасным вариантом rm, таким как rm file.txt, скрипт выполнил бы exit 0 вместо этого. Код выхода 0 без вывода означает, что hook не имеет решения для отчёта, поэтому вызов инструмента продолжается через нормальный поток разрешений. Hook может отклонить вызов, но молчание не одобряет его.

5

Claude Code действует на основе результата

Claude Code читает JSON решение, блокирует вызов инструмента и показывает Claude причину.

Раздел Configuration ниже документирует полную схему, и каждый раздел hook event документирует, какой входной JSON получает ваша команда и какой выход она может вернуть.

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

Hooks определяются в JSON файлах настроек. Конфигурация имеет три уровня вложенности:

  1. Выберите hook event для ответа, например PreToolUse или Stop
  2. Добавьте matcher group для фильтрации срабатывания, например "только для инструмента Bash"
  3. Определите один или несколько hook handlers для запуска при совпадении

См. Как разрешается hook выше для полного пошагового руководства с аннотированным примером.

Расположение hook

Место, где вы определяете hook, определяет его область действия:

Расположение Область действия Общий доступ
~/.claude/settings.json Все ваши проекты Нет, локально на вашей машине
.claude/settings.json Один проект Да, можно зафиксировать в репозитории
.claude/settings.local.json Один проект Нет, игнорируется git когда Claude Code сохраняет параметр в него
Управляемые параметры политики Организация Да, контролируется администратором
Plugin hooks/hooks.json Когда плагин включен Да, поставляется с плагином
Skill frontmatter Остаток сеанса после вызова skill. См. Hooks in skills and agents Да, определено в файле skill
Subagent frontmatter Пока этот subagent работает Да, определено в файле subagent

Облачные сеансы на Claude Code в веб-версии не читают ваш локальный ~/.claude/settings.json. В самостоятельно размещённой среде, Claude Code также запускает hooks, которые оператор инициализировал из ~/.claude/ хоста runner, и запускает hooks в файле управляемых параметров образа runner, когда этот файл находится среди управляемых источников, которые применяет Claude Code, что по умолчанию означает только когда ни управляемые на сервере параметры, ни доставленная MDM политика Claude Code не предоставляют управляемый уровень. См. что переносится из вашей установки для того, какие файлы настроек и плагины, и таким образом какие hooks, достигают облачного сеанса.

Для получения подробной информации о разрешении файлов настроек см. settings.

Hooks из файлов настроек, управляемых параметров политики и плагинов также запускаются внутри subagents. Когда subagent вызывает инструмент, события инструмента, такие как PreToolUse и PostToolUse, запускают те же настроенные hooks, что и в основном разговоре, и входные данные содержат поля agent_id и agent_type общих входных полей, которые идентифицируют subagent.

Администраторы могут использовать allowManagedHooksOnly в управляемых параметрах для ограничения того, какие hooks запускаются:

  • Ваши пользовательские, проектные, локальные и плагинные hooks блокируются. Hooks из плагинов, принудительно включённых в управляемых параметрах enabledPlugins, исключены
  • Claude Code также сужает ваши параметры statusLine, fileSuggestion и subagentStatusLine до управляемых параметров
  • Claude Code также отключает плагины с источником command, включая плагины, принудительно включённые в управляемых параметрах enabledPlugins, если только disableCommandPluginSources явно не установлен на false. Источники command требуют Claude Code v2.1.229 или позже
  • Claude Code также блокирует команды marketplace headersHelper если только disableCommandPluginSources явно не установлен на false, за исключением marketplace, которые сами управляемые параметры объявляют

См. что запускается под allowManagedHooksOnly.

Hook записи объединяются на уровнях параметров, а не заменяют друг друга: пользовательские, проектные и локальные параметры добавляют свои собственные hooks без удаления управляемых, и параметр disableAllHooks не может отключить управляемые hooks извне управляемых параметров.

HTTP hook allowlists применяются к hooks из каждого источника, включая управляемые параметры политики:

  • allowedHttpHookUrls: когда определено на любом уровне параметров, Claude Code запускает обработчик HTTP hook только если его URL совпадает с объединённым allowlist
  • httpHookAllowedEnvVars: когда определено, Claude Code интерполирует только переменные окружения из этого списка в заголовки hook

Matcher patterns

Поле matcher фильтрует срабатывание hooks. Способ оценки фильтра зависит от содержащихся в нём символов:

Значение фильтра Оценивается как Пример
"*", "" или опущено Совпадение со всеми срабатывает при каждом возникновении события
Только буквы, цифры, _, -, пробелы, , и | Точная строка или список точных строк, разделённых | или , с опциональным окружающим пробелом Bash совпадает только с инструментом Bash; Edit|Write и Edit, Write каждый совпадает с любым инструментом точно; code-reviewer совпадает только с этим типом агента
Содержит любой другой символ Регулярное выражение JavaScript, без привязки ^Notebook совпадает с любым инструментом, начинающимся с Notebook; mcp__memory__.* совпадает с каждым инструментом с сервера memory

Фильтр на пути регулярного выражения проверяется с помощью RegExp.prototype.test JavaScript, который успешно совпадает в любом месте значения. Edit.* совпадает как с Edit, так и с NotebookEdit; оберните шаблон в ^ и $, как в ^Edit$, когда вам нужно совпадение всей строки.

FileChanged и StopFailure используют более узкий набор точного совпадения только букв, цифр, _ и |. Дефис, пробел или запятая в фильтре для этих двух событий держит его на пути регулярного выражения, и только | разделяет альтернативы. Каждое другое событие с поддержкой фильтра в таблице ниже принимает | или ,.

Событие FileChanged не следует этим правилам при построении своего списка наблюдения. См. FileChanged.

Каждый тип события совпадает с другим полем:

Событие На что фильтр влияет Примеры значений фильтра
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied имя инструмента Bash, Edit|Write, mcp__.*
SessionStart как сеанс начался startup, resume, clear, compact, fork
Setup какой флаг CLI запустил setup init, maintenance
SessionEnd почему сеанс закончился clear, resume, logout, prompt_input_exit, other
Notification тип уведомления permission_prompt, idle_prompt, auth_success, elicitation_dialog, elicitation_url_dialog, elicitation_complete, elicitation_response, agent_needs_input, agent_completed, quota_auto_resume_fired, quota_auto_resume_stale, quota_auto_resume_disabled
SubagentStart тип агента general-purpose, Explore, Plan, пользовательские имена агентов или имена с областью плагина, такие как ^my-plugin:reviewer$
PreCompact, PostCompact что вызвало компактирование manual, auto
PreModelSwitch, PostModelSwitch каноническое имя модели, на которую переключается сеанс, как описано в PreModelSwitch claude-opus-5, claude-opus-4-6|claude-opus-5, .*opus.*
SubagentStop тип агента те же значения, что и SubagentStart
ConfigChange источник конфигурации user_settings, project_settings, local_settings, policy_settings, skills
CwdChanged поддержка фильтра отсутствует всегда срабатывает при каждом возникновении
DirectoryAdded как был добавлен каталог slash_command, register_repo_root
FileChanged буквальные имена файлов для наблюдения (см. FileChanged) .envrc|.env
StopFailure тип ошибки rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, account_on_hold, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, cloud_credential_error, unknown
InstructionsLoaded причина загрузки session_start, nested_traversal, path_glob_match, include, compact
UserPromptExpansion имя команды ваши имена skill или команд
Elicitation имя MCP сервера ваши настроенные имена MCP серверов
ElicitationResult имя MCP сервера те же значения, что и Elicitation
UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, MessageDisplay поддержка фильтра отсутствует всегда срабатывает при каждом вхождении

Совпадение StopFailure на cloud_credential_error требует Claude Code v2.1.267 или позже, первой версии, которая сообщает об ошибках загрузки учётных данных под этим значением вместо server_error или unknown.

Для большинства событий Claude Code оценивает фильтр против поля из JSON входа, который он отправляет вашему hook на stdin. Для событий инструмента это поле — tool_name. Для PreModelSwitch и PostModelSwitch, Claude Code оценивает фильтр против канонического имени, которое он выводит из to_model, как описано в PreModelSwitch. Каждый раздел hook event перечисляет полный набор значений фильтра и схему входа для этого события.

Этот пример запускает скрипт линтинга только когда Claude пишет или редактирует файл:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/lint-check.sh"
          }
        ]
      }
    ]
  }
}

Если вы добавите поле matcher к событию без поддержки фильтра, оно будет молча проигнорировано.

Для событий инструмента вы можете фильтровать более узко, установив поле if на отдельных обработчиках hook. if использует синтаксис правила разрешения для совпадения с именем инструмента и аргументами вместе, поэтому "Bash(git *)" запускается когда любая подкоманда входа Bash совпадает с git * и "Edit(*.ts)" запускается только для файлов TypeScript.

Match MCP tools

MCP server инструменты отображаются как обычные инструменты в событиях инструментов (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied), поэтому вы можете совпадать с ними так же, как с любым другим именем инструмента.

MCP инструменты следуют шаблону именования mcp__<server>__<tool>, например:

  • mcp__memory__create_entities: инструмент create entities сервера Memory
  • mcp__filesystem__read_file: инструмент read file сервера Filesystem
  • mcp__github__search_repositories: инструмент поиска сервера GitHub

Чтобы совпадать с каждым инструментом с сервера, добавьте .* к префиксу сервера. .* требуется: фильтр, такой как mcp__memory или mcp__brave-search, содержит только символы точного совпадения, поэтому он сравнивается как точная строка и не совпадает ни с одним инструментом.

  • mcp__memory__.* совпадает со всеми инструментами сервера memory
  • mcp__brave-search__.* совпадает со всеми инструментами с сервера, чьё имя содержит дефис
  • mcp__.*__write.* совпадает с любым инструментом, чьё имя начинается с write из любого сервера

Инструменты из plugin-bundled MCP server используют сегмент сервера с областью, который включает имя плагина: mcp__plugin_<plugin-name>_<server-name>__<tool>. Фильтр, написанный против голого ключа сервера, никогда не срабатывает для этих инструментов. Для плагина с именем my-plugin, который объединяет сервер под ключом db, инструмент query отображается как mcp__plugin_my-plugin_db__query, поэтому фильтр для каждого инструмента с этого сервера — mcp__plugin_my-plugin_db__.*. Используйте то же имя инструмента с областью в поле if обработчика. См. Plugin-provided MCP servers для того, как строится имя с областью.

Этот пример логирует все операции сервера memory и проверяет операции записи из любого MCP сервера:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
          }
        ]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [
          {
            "type": "command",
            "command": "/home/user/scripts/validate-mcp-write.py"
          }
        ]
      }
    ]
  }
}

Hook handler fields

Каждый объект во внутреннем массиве hooks — это hook handler: команда оболочки, конечная точка HTTP, инструмент MCP, подсказка LLM или агент, который запускается при совпадении фильтра. Есть пять типов:

  • Command hooks (type: "command"): запускают команду оболочки. Ваш скрипт получает JSON входные данные события на stdin и передаёт результаты обратно через коды выхода и stdout.
  • HTTP hooks (type: "http"): отправляют JSON входные данные события как HTTP POST запрос на URL. Конечная точка передаёт результаты обратно через тело ответа, используя тот же JSON формат выхода, что и command hooks.
  • MCP tool hooks (type: "mcp_tool"): вызывают инструмент на уже подключённом MCP сервере. Текстовый вывод инструмента обрабатывается как stdout command hook.
  • Prompt hooks (type: "prompt"): отправляют подсказку модели Claude для однооборотной оценки. Модель возвращает решение как JSON. См. Prompt-based hooks.
  • Agent hooks (type: "agent"): порождают subagent, который может использовать инструменты, такие как Read, Grep и Glob, для проверки условий перед возвратом решения. Agent hooks являются экспериментальными и могут измениться. См. Agent-based hooks.

Все совпадающие hooks запускаются параллельно. Если вы определите один и тот же обработчик в более чем одном файле настроек, он запускается один раз. Копия плагина или skill одного и того же обработчика остаётся отдельной.

Обработчики запускаются в текущем каталоге с окружением Claude Code. Если текущий каталог больше не существует, например worktree или временный каталог, который другая оболочка удалила в середине сеанса, Claude Code запускает command hooks из первого из них, который всё ещё существует: каталог, в котором сеанс начался, корень проекта, ваш домашний каталог или системный временный каталог. Claude Code записывает предупреждение, называющее резервный каталог, в debug log.

Переменная окружения $CLAUDE_CODE_REMOTE устанавливается на "true" в удалённых веб-окружениях и не устанавливается в локальном CLI. Claude Code v2.1.199 и позже устанавливает $CLAUDE_CODE_BRIDGE_SESSION_ID на ID сеанса Remote Control пока локальный сеанс имеет активное соединение Remote Control.

Common fields

Эти поля применяются ко всем типам hooks:

Поле Обязательно Описание
type да "command", "http", "mcp_tool", "prompt" или "agent"
if нет Синтаксис правил разрешений для фильтрации того, когда запускается этот хук, например "Bash(git *)" или "Edit(*.ts)". Команда хука запускается, только если вызов инструмента совпадает с шаблоном. О том, как шаблоны Bash сопоставляются с подкомандами, $() и обратными кавычками, см. таблицу сопоставления Bash. Вычисляется только для событий инструментов: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest и PermissionDenied. Для других событий хук с заданным if никогда не запускается
timeout нет Секунды перед отменой. Claude Code не применяет его на command hook, который вы запускаете с async: true. Значения по умолчанию: 600 для command, http и mcp_tool; 30 для prompt; 60 для agent. Claude Code снижает значение по умолчанию для command, http и mcp_tool до 30 на UserPromptSubmit, PreModelSwitch и PostModelSwitch, и до 10 на MessageDisplay. Hooks SessionEnd делят бюджет 1.5 секунды; если ваши параметры устанавливают более длительный timeout для каждого hook, Claude Code повышает бюджет, чтобы совпадать, до 60 секунд
statusMessage нет Пользовательское сообщение спиннера, отображаемое во время выполнения hook
once нет Если true, Claude Code удаляет hook после его первого успешного запуска. Запуск, который не удаётся, блокирует с кодом выхода 2 или истекает по времени, оставляет hook на месте, поэтому он запускается снова при следующем совпадающем событии. Только для hooks, объявленных в skill frontmatter; игнорируется в файлах настроек и agent frontmatter

Поле if содержит ровно одно правило разрешения. Нет синтаксиса &&, || или списка для объединения правил; чтобы применить несколько условий, определите отдельный обработчик hook для каждого.

В условии if для инструмента файла, шаблон каталога с одним сегментом, такой как "Edit(src/**)", совпадает только с каталогом src в рабочем каталоге и файлами под ним. Чтобы совпадать с каталогом с именем src на любой глубине, напишите "Edit(**/src/**)". До v2.1.214, "Edit(src/**)" совпадал с каталогом с именем src на любой глубине под рабочим каталогом.

Как шаблоны `if` сопоставляются с командами Bash

Для шаблонов Bash в поле if то, запустится ли команда вашего хука, зависит от формы шаблона и команды Bash, которую вызывает Claude. Начальные присваивания VAR=value удаляются перед сопоставлением.

if шаблон Bash команда Hook запускается? Почему
Bash(git *) FOO=bar git push да ведущие присваивания удаляются; git push совпадает
Bash(git *) npm test && git push да каждая подкоманда проверяется; git push совпадает
Bash(rm *) echo $(rm -rf /) да команды внутри $() и обратных кавычек проверяются; rm -rf / совпадает
Bash(rm *) echo $(date) нет ни одна подкоманда не совпадает с rm *
Bash(git push *) echo $(date) да шаблоны, которые указывают больше чем имя команды, запускают hook в любом случае на $(), обратных кавычках или $VAR

Когда Claude Code не может определить, какие команды запускает входные данные Bash, он запускает ваш hook независимо от шаблона. Поскольку фильтр if является лучшим усилием, используйте систему разрешений вместо hook для обеспечения жёсткого разрешения или отказа.

Command hook fields

В дополнение к общим полям, command hooks принимают эти поля:

Поле Обязательно Описание
command да Команда оболочки для выполнения. С args, исполняемый файл для прямого запуска. См. Exec form and shell form
args нет Список аргументов. Когда присутствует, command разрешается как исполняемый файл и запускается напрямую с args как вектор аргументов, без участия оболочки. См. Exec form and shell form
async нет Если true, запускается в фоне без блокировки. См. Run hooks in the background
asyncRewake нет Если true, запускается в фоне и пробуждает Claude при коде выхода 2. Hook stderr или stdout, если stderr пусто, показывается Claude как системное напоминание чтобы он мог реагировать на долгоживущий фоновый сбой
shell нет Оболочка для использования для этого hook. Принимает "bash" или "powershell". По умолчанию "bash", или "powershell" на Windows когда Git Bash не установлен. Установка "powershell" запускает команду через PowerShell на Windows. Не требует CLAUDE_CODE_USE_POWERSHELL_TOOL, так как hooks порождают PowerShell напрямую. Игнорируется когда установлен args
Exec form and shell form

Command hook запускается в exec form когда установлен args, и в shell form когда args опущен. Установите args всякий раз, когда hook ссылается на path placeholder, так как каждый элемент передаётся как один аргумент без кавычек. Опустите args когда вам нужны функции оболочки, такие как pipes или &&, или когда ни одна из этих проблем не применяется.

Exec form запускается когда присутствует args. Claude Code разрешает command как исполняемый файл на PATH и запускает его напрямую с args как вектор аргументов. Нет оболочки, поэтому каждый элемент args — это ровно один аргумент, написанный как есть, и path placeholders, такие как ${CLAUDE_PLUGIN_ROOT}, подставляются в command и в каждый элемент args как простые строки. Специальные символы, такие как апострофы, $ и обратные кавычки, проходят дословно, потому что нет оболочки для их интерпретации. На любой платформе не происходит никакой токенизации оболочки.

Shell form запускается когда args отсутствует. Строка command передаётся в оболочку: sh -c на macOS и Linux, Git Bash на Windows, или PowerShell когда Git Bash не установлен. Установите поле shell для явного выбора. Оболочка токенизирует строку, расширяет переменные и интерпретирует pipes, &&, redirects и globs.

Этот пример запускает Node скрипт, поставляемый с плагином. Exec form передаёт разрешённый путь скрипта как один аргумент без кавычек:

{
  "type": "command",
  "command": "node",
  "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/format.js", "--fix"]
}

Эквивалентная shell form нуждается в кавычках для обработки путей с пробелами или специальными символами:

{
  "type": "command",
  "command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/format.js --fix"
}

Обе формы поддерживают одни и те же path placeholders, и обе экспортируют их как переменные окружения CLAUDE_PROJECT_DIR, CLAUDE_PLUGIN_ROOT и CLAUDE_PLUGIN_DATA на порождённом процессе, поэтому скрипт может читать process.env.CLAUDE_PLUGIN_ROOT независимо от того, как он был запущен.

Plugin hooks дополнительно подставляют значения ${user_config.*}, только в exec form: значение подставляется в command и в каждый элемент args как простая строка, поэтому оболочка не переанализирует его.

Shell-form plugin hook, чей command ссылается на ${user_config.*}, завершается с ошибкой вместо запуска. Чтобы использовать значение опции из shell-form hook, прочитайте переменную окружения $CLAUDE_PLUGIN_OPTION_<KEY>, такую как $CLAUDE_PLUGIN_OPTION_WEBHOOK_URL для опции webhook_url, или установите args для переключения hook на exec form. До v2.1.207, shell-form plugin hook команды также подставляли ${user_config.*}.

HTTP hook fields

В дополнение к общим полям, HTTP hooks принимают эти поля:

Поле Обязательно Описание
url да URL для отправки POST запроса
headers нет Дополнительные HTTP заголовки как пары ключ-значение. Значения поддерживают интерполяцию переменных окружения с использованием синтаксиса $VAR_NAME или ${VAR_NAME}. Разрешены только переменные, указанные в allowedEnvVars
allowedEnvVars нет Список имён переменных окружения, которые могут быть интерполированы в значения заголовков. Ссылки на неуказанные переменные заменяются пустыми строками. Требуется для любой интерполяции переменных окружения

Claude Code отправляет JSON входные данные hook как тело POST запроса с Content-Type: application/json. Тело ответа использует тот же JSON формат выхода, что и command hooks.

Обработка ошибок отличается от command hooks; см. HTTP response handling.

Этот пример отправляет события PreToolUse на локальный сервис валидации, аутентифицируясь с токеном из переменной окружения MY_TOKEN:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:8080/hooks/pre-tool-use",
            "timeout": 30,
            "headers": {
              "Authorization": "Bearer $MY_TOKEN"
            },
            "allowedEnvVars": ["MY_TOKEN"]
          }
        ]
      }
    ]
  }
}

MCP tool hook fields

В дополнение к общим полям, MCP tool hooks принимают эти поля:

Поле Обязательно Описание
server да Имя настроенного MCP сервера. Для plugin-bundled server, это имя с областью plugin:<plugin-name>:<server-name>, такое как plugin:my-plugin:db, не голый ключ сервера
tool да Имя инструмента для вызова на этом сервере
input нет Аргументы, передаваемые инструменту. Строковые значения поддерживают подстановку ${path} из JSON входа hook, такую как "${tool_input.file_path}"

Этот пример вызывает инструмент security_scan на MCP сервере my_server после каждого Write или Edit, передавая путь отредактированного файла:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "mcp_tool",
            "server": "my_server",
            "tool": "security_scan",
            "input": { "file_path": "${tool_input.file_path}" }
          }
        ]
      }
    ]
  }
}
How the tool's result is read

Claude Code читает текстовое содержимое инструмента так же, как читает command-hook stdout, следуя правилу разбора под кодом выхода 0. Если инструмент возвращает isError: true, hook производит неблокирующую ошибку и выполнение продолжается.

When the server is still connecting

На событиях, где hook может блокировать или изменять результат, такие как PreToolUse или Stop, Claude Code ждёт подключения сервера перед вызовом инструмента, максимум MCP_TIMEOUT и в пределах собственного timeout hook. На наблюдательных событиях, таких как Notification или SessionEnd, он не ждёт.

Сервер, показывающий статус cached, подключается, когда hook вызывает его инструмент. Если сервер не подключён в этот момент, hook производит неблокирующую ошибку и выполнение продолжается. Hook никогда не запускает поток OAuth, поэтому аутентифицируйте сервер из /mcp сначала.

Events that fire before MCP servers are available

SessionStart при запуске, включая с --continue или --resume, и каждое событие Setup срабатывают перед доступностью MCP серверов сеанса для hooks. Claude Code пропускает их mcp_tool hooks без вызова инструмента, и debug log записывает mcp_tool hooks are not available for the 'SessionStart' hook event (no MCP client context), или то же сообщение, называющее Setup. Когда SessionStart срабатывает снова позже в сеансе, после /clear или компактирования, его mcp_tool hooks запускаются. Для всего, что сеансу нужно при запуске, используйте type: "command" hook на SessionStart вместо этого.

Prompt and agent hook fields

В дополнение к общим полям, prompt и agent hooks принимают эти поля:

Поле Обязательно Описание
prompt да Текст подсказки для отправки модели. Используйте $ARGUMENTS как заполнитель для JSON входа hook. Экранируйте обратной косой чертой для включения буквального текста: \$1.00 отображается как $1.00
model нет Модель для использования при оценке. По умолчанию модель, которую Claude Code использует для фоновой функциональности

Reference scripts by path

Используйте эти заполнители для ссылки на скрипты hook относительно корня проекта или плагина, независимо от рабочего каталога при запуске hook:

  • ${CLAUDE_PROJECT_DIR}: корень проекта, где сеанс начался. Claude Code также устанавливает эту переменную в окружении stdio MCP серверов и plugin LSP серверов.
  • ${CLAUDE_PLUGIN_ROOT}: каталог установки плагина, для скриптов, поставляемых с плагином. См. plugin environment variables для того, как путь ведёт себя при обновлениях.
  • ${CLAUDE_PLUGIN_DATA}: каталог постоянных данных плагина, для зависимостей и состояния, которые должны пережить обновления плагина.

Предпочитайте exec form для любого hook, который ссылается на path placeholder. В shell form оберните каждый заполнитель в двойные кавычки.

Этот пример использует ${CLAUDE_PROJECT_DIR} для запуска проверки стиля из каталога .claude/hooks/ проекта после любого вызова инструмента Write или Edit:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh",
"args": []
}
]
}
]
}
}

Hooks in skills and agents

В дополнение к файлам настроек и плагинам, hooks могут быть определены непосредственно в skills и subagents с использованием frontmatter, в том же формате конфигурации, что и hooks на основе настроек. Как долго Claude Code их регистрирует, зависит от компонента:

  • Subagent hooks: Claude Code запускает их только пока этот subagent работает и удаляет их, когда он завершается. Claude Code преобразует hook Stop здесь в SubagentStop, событие, которое срабатывает при завершении subagent.
  • Skill hooks: Claude Code регистрирует их, когда вы или Claude вызываете skill, и продолжает запускать их для остатка сеанса, на ходах после собственного хода skill. Чтобы Claude Code удалил hook после его первого успешного запуска вместо этого, установите once: true на нём.

Этот skill определяет hook PreToolUse, который запускает скрипт проверки безопасности перед каждой командой Bash:

---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/security-check.sh"
---

Subagents используют тот же формат в своём YAML frontmatter.

Frontmatter hooks в project skill следуют тому же правилу доверия рабочей области, что и hooks в файлах настроек. Claude Code регистрирует их, когда вы или Claude вызываете skill, включая в запуск -p в папке, которую вы не доверяли.

Frontmatter hooks в project subagent запускаются только после того, как вы примете диалог доверия рабочей области для папки, из которой пришёл файл агента. Сеанс -p не считается принятием. Что запускается перед тем, как вы доверяете папке сравнивает это с правилом файла настроек, и страница subagents перечисляет какие области исключены. До v2.1.218, эти hooks могли запускаться из папок, которым вы не доверяли.

Меню `/hooks`

Введите /hooks в Claude Code, чтобы открыть браузер настроенных хуков, доступный только для чтения. В списке для каждого хука указано, откуда он взят: пользовательские настройки, настройки проекта, локальные настройки, плагин или текущая сессия.

Выберите хук, чтобы увидеть полный текст того, что он запускает, и где он определён, например путь к его файлу настроек или имя его плагина.

Чтобы просмотреть все события хуков, включая те, для которых хуки не настроены, выберите All events в конце списка.

Отключение или удаление hooks

Чтобы удалить хук, определённый в файле настроек, удалите его запись из этого файла.

Чтобы временно отключить все hooks без их удаления, установите "disableAllHooks": true в файле настроек. Claude Code читает значение, оставшееся после применения приоритета параметров, поэтому "disableAllHooks": false в .claude/settings.json проекта переопределяет true в ваших пользовательских параметрах. Чтобы отключить hooks для одного запуска, независимо от того, что говорят параметры проекта, передайте --settings '{"disableAllHooks": true}', что имеет приоритет над параметрами проекта и локальными параметрами. Нет способа отключить отдельный hook, сохраняя его в конфигурации.

Параметр disableAllHooks соблюдает иерархию управляемых параметров. Если администратор настроил hooks через управляемые параметры политики, disableAllHooks, установленный в пользовательских, проектных или локальных параметрах, не может отключить эти управляемые hooks. Только disableAllHooks, установленный на уровне управляемых параметров, может отключить управляемые hooks. Для полного охвата каждого уровня см. disableAllHooks.

Прямые редактирования hooks в файлах настроек обычно захватываются автоматически наблюдателем файлов.

Входные и выходные данные хуков

Командные хуки получают данные JSON через stdin и сообщают результаты через коды выхода, stdout и stderr. HTTP-хуки получают тот же JSON в теле POST-запроса и сообщают результаты через тело HTTP-ответа. В этом разделе описаны поля и поведение, общие для всех событий. Раздел каждого события в Событиях хуков содержит его собственную схему входных данных и параметры управления решениями.

В macOS и Linux командные хуки выполняются в собственной сессии без управляющего терминала. Процесс хука и любые дочерние процессы не могут открыть /dev/tty или отправлять escape-последовательности напрямую в интерфейс Claude Code. В Windows /dev/tty отсутствует.

Чтобы показать сообщение пользователю на любой платформе, верните systemMessage в выводе JSON. Некоторые события отбрасывают его или доставляют в другое место, и раздел каждого события об этом сообщает. Чтобы вызвать уведомление на рабочем столе, задать заголовок окна или подать звуковой сигнал, верните вместо этого terminalSequence.

Общие поля входных данных

События хуков получают эти поля в формате JSON в дополнение к специфичным для события полям, описанным в разделе каждого события хука. Для командных хуков этот JSON поступает через stdin. Для HTTP-хуков он поступает в теле POST-запроса.

Поле Описание
session_id Идентификатор текущей сессии
prompt_id UUID, идентифицирующий промпт пользователя, который обрабатывается в данный момент. Совпадает с атрибутом prompt.id в событиях OpenTelemetry, поэтому вы можете сопоставить вывод хука с телеметрией для отдельного промпта. Отсутствует до первого ввода пользователя
transcript_path Путь к JSON диалога. Файл транскрипта записывается асинхронно и может отставать от диалога в памяти, поэтому в момент срабатывания хука он может ещё не содержать самые последние сообщения текущего хода. Хукам, которым нужен итоговый текст ассистента текущего хода, следует использовать last_assistant_message в Stop и SubagentStop вместо чтения транскрипта
cwd Текущий рабочий каталог на момент вызова хука
scratchpad_dir Путь к каталогу scratchpad сессии, где Claude хранит временные рабочие файлы. Отсутствует, если у сессии нет scratchpad или временный каталог недоступен. Требуется Claude Code v2.1.257 или новее
permission_mode Текущий режим разрешений: "default", "plan", "acceptEdits", "auto", "dontAsk" или "bypassPermissions". Режим с меткой Manual передаётся как "default", а не как "manual", поэтому скрипты, проверяющие "default", продолжают работать. Не все события получают это поле. Смотрите пример JSON в разделе каждого события хука
effort Объект с полем level, содержащим уровень effort, действующий при выполнении хука: "low", "medium", "high", "xhigh" или "max". Если вы задали уровень, который активная модель не поддерживает, level сообщает уровень, с которым фактически работал Claude Code; в разделе Настройка уровня effort описано, как выбирается этот уровень. Объект совпадает с полем effort строки состояния. Присутствует для событий, срабатывающих в контексте использования инструментов, таких как PreToolUse, PostToolUse, Stop и SubagentStop, если текущая модель поддерживает параметр effort. Уровень также доступен командам хуков и инструменту Bash в виде переменной окружения $CLAUDE_EFFORT.
hook_event_name Имя сработавшего события

При запуске с --agent или внутри субагента включаются два дополнительных поля:

Поле Описание
agent_id Уникальный идентификатор субагента. Присутствует только тогда, когда хук срабатывает внутри вызова субагента. Используйте его, чтобы отличать вызовы хуков субагента от вызовов основного потока.
agent_type Имя агента (например, "Explore" или "security-reviewer"). Присутствует, если сессия использует --agent или хук срабатывает внутри субагента. Для субагентов тип субагента имеет приоритет над значением --agent сессии. Смотрите SubagentStart, чтобы узнать, какие значения сообщают пользовательские субагенты и субагенты плагинов и как написать matcher для имени с областью действия плагина.

Только хуки SessionStart могут получать поле model, и Claude Code не всегда его включает. Хуки PreModelSwitch и PostModelSwitch вместо этого получают from_model и to_model, поэтому используйте хук PostModelSwitch, чтобы отслеживать модель при её смене в ходе сессии.

Переменной окружения $CLAUDE_MODEL не существует. Хук может прочитать $ANTHROPIC_MODEL, если вы задали её в своей оболочке, но это значение не меняется, когда вы переключаете модели с помощью /model в ходе сессии.

Процесс хука наследует родительское окружение, за исключением переменных экспортёра OTEL_*, которые Claude Code удаляет из каждого запускаемого им подпроцесса, и, если CLAUDE_CODE_SUBPROCESS_ENV_SCRUB установлена в 1, переменных, которые он при этом удаляет. В сессии, которая получает конфигурацию HIPAA, Claude Code также удаляет учётные данные Anthropic из окружения хука.

Например, хук PreToolUse для команды Bash получает через stdin следующее:

{
  "session_id": "abc123",
  "prompt_id": "550e8400-e29b-41d4-a716-446655440000",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/home/user/my-project",
  "scratchpad_dir": "/tmp/claude-1000/-home-user-my-project/abc123/scratchpad",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite",
    "timeout": 120000,
    "run_in_background": false
  },
  "tool_use_id": "toolu_01ABC123..."
}

Поля tool_name, tool_input и tool_use_id специфичны для события. Раздел каждого события хука описывает дополнительные поля для этого события.

Вывод через код выхода

Код выхода вашей команды хука сообщает Claude Code, следует ли продолжить действие, заблокировать его или проигнорировать. Код выхода не действует сам по себе. Claude Code читает поля вывода JSON из stdout при любом коде выхода, а не только при 0, и для событий, использующих стандартную модель решений, разобранный объект, прошедший проверку схемы, вступает в силу наряду с кодом. Блокировка при выходе с кодом 2 — единственный результат, который JSON не может переопределить.

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

Код выхода 0

Выход с кодом 0 означает успех и является предполагаемым кодом выхода, когда вы выводите JSON для структурированного управления.

Для большинства событий Claude Code записывает stdout в отладочный лог и не показывает его в транскрипте. Исключения — UserPromptSubmit, UserPromptExpansion, SessionStart и PostModelSwitch, для которых Claude Code добавляет обычный текст из stdout как контекст, который Claude может видеть и учитывать.

Читает ли Claude Code ваш stdout как вывод JSON или как обычный текст, зависит от того, с чего он начинается и чем заканчивается, без учёта окружающих пробельных символов:

  • Начинается с { и заканчивается }: Claude Code разбирает его как JSON. Если вывод состоит из двух или более строк, каждая из которых по отдельности разбирается как JSON, и ни одна строка не является объектом вывода JSON, задающим какое-либо поле, Claude Code обрабатывает весь вывод как обычный текст. Если одна из этих строк задаёт поле, весь вывод считается ошибкой разбора, описанной ниже.
  • Начинается с {, но не заканчивается }: Claude Code обрабатывает его как обычный текст.
  • Начинается с чего-либо другого: Claude Code обрабатывает его как обычный текст, включая массив JSON или строку JSON в кавычках.

Для событий, использующих стандартную модель решений, выход с кодом 0 с разобранным объектом, не прошедшим проверку схемы, является неблокирующей ошибкой: действие продолжается, а в транскрипте отображается уведомление <hook name> hook error с сообщением о проверке. То же самое происходит при любом коде выхода, кроме 2, тогда как выход с кодом 2 по-прежнему блокирует.

Для событий, использующих стандартную модель решений, если Claude Code пытается разобрать ваш stdout как JSON и не может, он сообщает о неблокирующей ошибке при любом коде выхода, кроме 2. В транскрипте отображается уведомление <hook name> hook error с сообщением о разборе. Для событий, которые добавляют обычный текст из stdout как контекст, Claude Code не добавляет этот текст. До версии v2.1.248 Claude Code обрабатывал такой stdout как обычный текст.

Stderr хука, завершившегося с кодом 0, попадает только в отладочный лог, никогда не в транскрипт, и Claude его никогда не видит. Чтобы прочитать его самостоятельно, включите отладочное логирование. Чтобы передать предупреждение Claude из хука PostToolUse или PostToolUseFailure, вместо этого выйдите с кодом 2, чтобы Claude увидел stderr, хотя инструмент уже выполнился.

Код выхода 2

Выход с кодом 2 означает блокирующую ошибку. Для событий, которые могут блокировать, выход с кодом 2 блокирует независимо от того, выводите ли вы JSON: даже permissionDecision со значением "allow" в JSON не может его переопределить. Claude Code по-прежнему читает любой корректный вывод JSON из stdout. Для Elicitation и ElicitationResult поле hookSpecificOutput хука, завершившегося с кодом 2, игнорируется.

Сообщением о блокировке служит причина из блокирующего решения в вашем JSON, если оно есть, а в противном случае — текст вашего stderr. Действие блокировки зависит от события: PreToolUse блокирует вызов инструмента, UserPromptSubmit отклоняет промпт и так далее. В разделе Поведение кода выхода 2 для каждого события перечислен эффект для каждого события, а раздел каждого события указывает, куда попадает сообщение.

Хук, который завершается с кодом 2 и при этом выводит JSON, не прошедший проверку схемы вывода JSON, всё равно блокирует: Claude Code использует stderr как причину блокировки и записывает ошибку проверки в отладочный лог. До версии v2.1.214 Claude Code обрабатывал такое сочетание как неблокирующую ошибку, и действие продолжалось.

Этот скрипт блокирует команды rm, завершаясь с кодом 2, и оставляет все остальные команды обычному процессу разрешений:

#!/bin/bash
# Reads JSON input from stdin, checks the command
input=$(cat)
command=$(jq -r '.tool_input.command' <<<"$input")

if [[ "$command" == rm* ]]; then
  echo "Blocked: rm commands are not allowed" >&2
  exit 2  # Blocking error: tool call is prevented
fi

exit 0  # No decision: the normal permission flow applies

Другие коды выхода

Любой другой код выхода сам по себе не блокирует для большинства событий хуков. Что произойдёт, зависит от вашего stdout:

  • При разобранном объекте, прошедшем проверку схемы, для событий, использующих стандартную модель решений, Claude Code игнорирует код выхода, и результат определяет только JSON:
    • Учитывается каждое поле, которое поддерживает событие, включая permissionDecision, additionalContext, updatedInput и systemMessage, и хук не считается ошибкой.
    • В разделе Управление решениями перечислены поля решений для каждого события; универсальные поля, такие как systemMessage, описаны в таблице Вывод JSON.
  • При разобранном объекте, не прошедшем проверку схемы, для событий, использующих стандартную модель решений, это та же неблокирующая ошибка, что и при выходе с кодом 0: действие продолжается, а уведомление <hook name> hook error содержит сообщение о проверке.
  • При stdout, который Claude Code пытается разобрать как JSON и не может, Claude Code сообщает о той же неблокирующей ошибке, что и при выходе с кодом 0, для событий, использующих стандартную модель решений. Действие продолжается, а уведомление содержит сообщение о разборе.
  • При stdout, который Claude Code обрабатывает как обычный текст, или при пустом stdout это неблокирующая ошибка для большинства событий хуков: действие продолжается, а в транскрипте отображается уведомление <hook name> hook error, за которым следует первая строка stderr с префиксом Failed with non-blocking status code:. Чтобы получить полный stderr, включите отладочное логирование.

События вне стандартной модели решений сохраняют собственные строки в таблице по событиям: WorktreeCreate прерывает создание при любом ненулевом коде выхода независимо от того, что содержит ваш JSON, а события, полностью отбрасывающие вывод хука, такие как StopFailure, игнорируют ваш JSON при любом коде выхода, за исключением полей с побочными эффектами, таких как terminalSequence, которые всё равно срабатывают.

Хук, который не удаётся запустить, попадает в ту же категорию неблокирующих ошибок. Если путь к скрипту не существует или файл не является исполняемым, оболочка завершается с кодом вроде 127, и вы видите то же уведомление с сообщением интерпретатора, например Failed with non-blocking status code: /bin/sh: /path/to/hook.sh: No such file or directory. Для большинства событий хуков действие продолжается. Настраивая хук для политики, следите за этим уведомлением при его первом запуске: опечатка в пути в settings.json незаметно отключает проверку.

Таймауты

За исключением командного хука, запущенного с async: true, Claude Code отменяет хук command, http или mcp_tool, достигший своего timeout, и отбрасывает вывод хука, поэтому для большинства событий хук, превысивший таймаут, не выносит никакого решения.

Для PreModelSwitch хук, отменённый по таймауту, блокирует переключение модели. Для PreToolUse два семейства хуков ведут себя по-разному:

Поведение кода выхода 2 для каждого события

Код выхода 2 — это способ, которым хук сигнализирует «стоп, не делай этого». Эффект зависит от события, потому что одни события представляют действия, которые можно заблокировать (например, вызов инструмента, который ещё не произошёл), а другие — то, что уже произошло или не может быть предотвращено.

Событие хука Может блокировать? Что происходит при выходе с кодом 2
PreToolUse Да Блокирует вызов инструмента
PermissionRequest Нет Код выхода 2 не учитывается для этого события, и процесс разрешений продолжается без изменений. Вместо этого отклоняйте через объект decision
UserPromptSubmit Да Блокирует промпт, поэтому он никогда не доходит до Claude. Смотрите Что остаётся после заблокированного промпта
UserPromptExpansion Да Блокирует раскрытие
Stop Да Не даёт Claude остановиться, диалог продолжается
SubagentStop Да Не даёт субагенту остановиться
TeammateIdle Да Не даёт участнику команды перейти в режим ожидания, поэтому он продолжает работу
TaskCreated Да Откатывает создание задачи
TaskCompleted Да Не даёт отметить задачу как выполненную
ConfigChange Да Блокирует вступление изменения конфигурации в силу (кроме policy_settings)
StopFailure Нет Вывод и код выхода игнорируются, кроме terminalSequence
PostToolUse Нет Показывает stderr Claude; инструмент уже выполнился
PostToolUseFailure Нет Показывает stderr Claude; инструмент уже завершился с ошибкой
PostToolBatch Да Останавливает агентный цикл перед следующим вызовом модели
PermissionDenied Нет Код выхода и stderr игнорируются, потому что отказ уже произошёл. Используйте JSON hookSpecificOutput.retry: true, чтобы сообщить модели, что она может повторить попытку; Claude Code игнорирует retry: true для отказов без вердикта
Notification Нет Код выхода и stderr игнорируются
SubagentStart Нет Показывает stderr только пользователю
SessionStart Нет Показывает stderr только пользователю
Setup Нет Код выхода и stderr игнорируются
SessionEnd Нет Показывает stderr только пользователю
CwdChanged Нет Показывает stderr только пользователю
DirectoryAdded Нет Stderr попадает в отладочный лог; каталог уже добавлен
FileChanged Нет Показывает stderr только пользователю
PreCompact Да Блокирует сжатие контекста
PostCompact Нет Показывает stderr только пользователю
PreModelSwitch Да Блокирует переключение модели и показывает stderr пользователю
PostModelSwitch Нет Показывает stderr только пользователю; модель уже переключена
Elicitation Да Отклоняет запрос, и диалоговое окно не появляется
ElicitationResult Да Блокирует ответ (действие становится отказом)
WorktreeCreate Да Любой ненулевой код выхода приводит к ошибке создания worktree
WorktreeRemove Да Любой ненулевой код выхода приводит к ошибке удаления worktree, если каталог после этого всё ещё существует. Смотрите WorktreeRemove, чтобы узнать, что происходит с каталогом
InstructionsLoaded Нет Код выхода игнорируется
MessageDisplay Нет Отображается исходный текст

Для SessionStart, SubagentStart и PostModelSwitch Claude Code отображает stderr при коде выхода 2 в транскрипте как уведомление <hook name> hook error — так же, как отображает неблокирующую ошибку. Claude его не видит, и сессия или субагент продолжают работу. Для SubagentStart уведомление появляется в собственном транскрипте субагента, а не в родительском диалоге.

Обработка HTTP-ответов

HTTP-хуки используют коды состояния HTTP и тела ответов вместо кодов выхода и stdout. Приведённые ниже результаты применяются к большинству событий; событие с собственным контрактом обработки сбоев в таблице по событиям, например WorktreeCreate, применяет этот контракт и к HTTP-хуку, завершившемуся сбоем:

  • 2xx с пустым телом: успех, эквивалентно коду выхода 0 без вывода
  • 2xx с телом в виде объекта JSON: разбирается по той же схеме вывода JSON, что и для командных хуков. Тело, не прошедшее проверку схемы, является неблокирующей ошибкой
  • 2xx с любым другим телом, например обычным текстом: неблокирующая ошибка, обрабатывается так же, как статус не 2xx. Claude Code не добавляет текст в контекст Claude
  • Статус не 2xx: неблокирующая ошибка, выполнение продолжается
  • Сбой соединения: неблокирующая ошибка, выполнение продолжается
  • Таймаут: хук отменяется, как описано в разделе Таймауты

В отличие от командных хуков, HTTP-хуки не могут сигнализировать о блокирующей ошибке только через коды состояния. Чтобы заблокировать вызов инструмента или отклонить разрешение, верните ответ 2xx с телом JSON, содержащим соответствующие поля решения.

Вывод JSON

Коды выхода позволяют только блокировать или ничего не сообщать, а вывод JSON даёт более тонкое управление. Вместо выхода с кодом 2 для блокировки завершитесь с кодом 0 и выведите объект JSON в stdout. Claude Code читает определённые поля из этого JSON для управления поведением, включая управление решениями для блокировки, разрешения или передачи решения пользователю.

Stdout вашего хука должен содержать только объект JSON. Если профиль вашей оболочки выводит текст при запуске, это может помешать разбору JSON. Смотрите JSON хука не действует в руководстве по устранению неполадок.

Строки additionalContext, systemMessage и initialUserMessage хука, а также его обычный stdout ограничены 10 000 символов:

  • Область действия: Claude Code измеряет каждую строку отдельно, даже если для одного события выполняется несколько хуков. Для вывода JSON каждое поле измеряется отдельно; обычный stdout измеряется целиком.
  • Превышение лимита: Claude Code сохраняет вывод в файл в каталоге сессии и заменяет его путём к файлу и превью, содержащим до 2 000 первых символов. Большой корректный результат Bash обрабатывается так же, как описано в разделе Ограничения вывода. В отличие от этого ограничения Bash, у данного лимита нет настройки или переменной окружения, позволяющей его увеличить.
  • Чтение файла: Claude Code не просит Claude прочитать файл, поэтому держите всё, что Claude должен видеть всегда, в пределах лимита.

Объект JSON поддерживает три вида полей:

  • Универсальные поля, такие как continue, перечислены в таблице ниже. Их принимает каждое событие, но некоторые события отбрасывают их или доставляют systemMessage не в транскрипт, а в другое место. Раздел каждого события об этом сообщает. terminalSequence тоже работает для этих событий, с исключениями, перечисленными в разделе Вывод уведомлений терминала.
  • decision и reason верхнего уровня используются некоторыми событиями для блокировки или предоставления обратной связи.
  • hookSpecificOutput — вложенный объект для событий, которым требуется более развитое управление. Он требует поле hookEventName, установленное в имя события.
Поле По умолчанию Описание
continue true Если false, Claude полностью прекращает обработку после выполнения хука. Имеет приоритет над любыми полями решений, специфичными для события
stopReason нет Сообщение, показываемое пользователю, когда continue равно false. Оно остаётся в диалоге, поэтому Claude видит его, если диалог продолжается
suppressOutput false Не действует: Claude Code принимает поле, но не реагирует на него. Stdout успешного хука никогда не показывается в транскрипте и записывается в отладочный лог
systemMessage нет Предупреждающее сообщение, показываемое пользователю. В выводе Agent SDK и --output-format stream-json оно может поступать как SDKInformationalMessage
terminalSequence нет Escape-последовательность терминала, которую Claude Code выводит от вашего имени, например уведомление на рабочем столе, заголовок окна или звуковой сигнал. Ограничено OSC 0/1/2/9/99/777 и BEL. Если значение содержит что-либо вне списка разрешённых последовательностей, поле игнорируется. Используйте его вместо записи в /dev/tty, который недоступен хукам

Чтобы полностью остановить Claude:

{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }

Для хуков PreToolUse и PostToolUse остановка применяется, даже если вызов инструмента завершается ошибкой или выполняется, пока Claude ещё передаёт ответ в потоковом режиме.

Вывод уведомлений терминала

Хуки выполняются без управляющего терминала, поэтому запись escape-последовательностей напрямую в /dev/tty не работает. Вместо этого верните escape-последовательность в поле terminalSequence, и Claude Code выведет её за вас через собственный путь записи в терминал. Это исключает состояние гонки, работает внутри tmux и GNU screen, а также в Windows, где /dev/tty отсутствует.

Поле принимает строку из одной или нескольких escape-последовательностей из списка разрешённых:

  • OSC 0, 1, 2: заголовки окна и значка
  • OSC 9: уведомления iTerm2, ConEmu, Windows Terminal и WezTerm, включая индикатор прогресса на панели задач 9;4
  • OSC 99: уведомления Kitty
  • OSC 777: уведомления urxvt, Ghostty и Warp
  • Одиночный BEL

Последовательности могут завершаться символом BEL или ST. Всё, что не входит в список разрешённых последовательностей, включая последовательности CSI для курсора и цвета, последовательности OSC для палитры, гиперссылки OSC 8, запись в буфер обмена OSC 52 и OSC 1337, отклоняется, а поле игнорируется.

Claude Code сам записывает последовательность при обработке вывода вашего хука, поэтому поле работает для событий, которые отбрасывают systemMessage и continue, таких как Notification и StopFailure. У него есть два ограничения:

  • Claude Code записывает последовательность только в интерактивной сессии и только пока его интерфейс находится на экране. В неинтерактивном режиме с флагом -p и в Agent SDK он игнорирует это поле.
  • Командный хук WorktreeCreate не может возвращать JSON, потому что Claude Code читает его stdout как путь к worktree. HTTP-хук WorktreeCreate возвращает JSON и может включать это поле.

В примере ниже хук Notification вызывает уведомление на рабочем столе. Escape-последовательность строится с помощью восьмеричных escape-кодов printf, чтобы управляющие байты никогда не появлялись в командной строке оболочки, а jq -n --arg формирует вывод JSON, чтобы кавычки, обратные слеши и переводы строк в тексте уведомления корректно экранировались:

#!/bin/bash
# Notification hook: ping the desktop when Claude Code needs attention.
input=$(cat)
title="Claude Code"
body=$(jq -r '.message // "Needs your attention"' <<<"$input")
seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'

Структура { "terminalSequence": "..." } одинакова для любой оболочки или языка.

Добавление контекста для Claude

Поле additionalContext передаёт строку из вашего хука в контекстное окно Claude. Claude Code оборачивает строку в системное напоминание и вставляет его в диалог в точке срабатывания хука. Claude читает напоминание при следующем запросе к модели, но оно не отображается в интерфейсе как сообщение чата.

Возвращайте additionalContext внутри hookSpecificOutput вместе с именем события:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
  }
}

Место появления напоминания зависит от события:

Когда несколько хуков возвращают additionalContext для одного события, Claude получает все значения.

Если значение превышает 10 000 символов, Claude Code записывает текст в файл в каталоге сессии и вместо этого передаёт Claude путь к файлу с превью, содержащим до 2 000 первых символов. Claude может прочитать файл, но Claude Code не просит его об этом.

Используйте additionalContext для информации, которую Claude следует знать о текущем состоянии вашего окружения или об только что выполненной операции:

  • Состояние окружения: текущая ветка, цель развёртывания или активные флаги функций
  • Условные правила проекта: какая команда тестирования применима к только что отредактированному файлу, какие каталоги доступны только для чтения в этом worktree
  • Внешние данные: открытые задачи, назначенные вам, недавние результаты CI, содержимое, полученное из внутреннего сервиса

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

Пишите текст в виде фактических утверждений, а не императивных системных инструкций. Формулировки вроде «The deployment target is production» или «This repo uses bun test» воспринимаются как информация о проекте. Текст, оформленный как внеполосные системные команды, может активировать защиту Claude от промпт-инъекций, из-за чего Claude покажет этот текст вам вместо того, чтобы воспринять его как контекст.

Claude Code сохраняет внедрённый текст в транскрипте сессии. Для событий в середине сессии, таких как PostToolUse или UserPromptSubmit, при возобновлении с --continue или --resume Claude Code воспроизводит сохранённый текст, а не запускает хук повторно для прошлых ходов, поэтому такие значения, как временные метки или SHA коммитов, устаревают. Хуки SessionStart при возобновлении запускаются снова с source, установленным в "resume", или в "fork", если вы добавили --fork-session, поэтому они могут обновить свой контекст.

Управление решениями

Не каждое событие поддерживает блокировку или управление поведением через JSON. События, которые это поддерживают, используют разные наборы полей для выражения решения. Используйте эту таблицу как краткий справочник перед написанием хука:

События Шаблон решения Ключевые поля
UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact decision верхнего уровня decision: "block", reason. Stop и SubagentStop также принимают hookSpecificOutput.additionalContext для обратной связи без ошибки, при которой диалог продолжается
TeammateIdle, TaskCompleted Код выхода или continue: false Код выхода 2 блокирует действие с обратной связью через stderr. JSON {"continue": false, "stopReason": "..."} также полностью останавливает участника команды, аналогично поведению хука Stop; TaskCompleted игнорирует его, если событие вызвано инструментом TaskUpdate
TaskCreated Код выхода или decision верхнего уровня Код выхода 2 или decision: "block" отменяет задачу и возвращает сообщение Claude. continue: false игнорируется
PreToolUse hookSpecificOutput permissionDecision (allow/deny/ask/defer), permissionDecisionReason
PreModelSwitch hookSpecificOutput или decision верхнего уровня permissionDecision (allow/deny/ask), permissionDecisionReason. decision: "block" также отменяет переключение
PermissionRequest hookSpecificOutput decision.behavior (allow/deny)
PermissionDenied hookSpecificOutput retry: true сообщает модели, что она может повторить попытку отклонённого вызова инструмента; Claude Code игнорирует его для отказов без вердикта
WorktreeCreate возврат пути Командный хук выводит путь в stdout; HTTP-хук возвращает hookSpecificOutput.worktreePath. Сбой хука или отсутствие пути прерывает создание
WorktreeRemove Код выхода Любой ненулевой код выхода приводит к ошибке удаления, если каталог после этого всё ещё существует. Вывод JSON отбрасывается
Elicitation, ElicitationResult hookSpecificOutput или decision верхнего уровня action (accept/decline/cancel), content (значения полей формы). decision: "block" также отклоняет запрос
MessageDisplay hookSpecificOutput displayContent заменяет отображаемый на экране текст. Только для отображения: транскрипт и то, что видит Claude, сохраняют оригинал
SessionStart, SubagentStart, PostModelSwitch Только контекст hookSpecificOutput.additionalContext добавляет контекст для Claude. SessionStart также принимает initialUserMessage, watchPaths, sessionTitle и reloadSkills. Без блокировки и управления решениями
Setup, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, DirectoryAdded, FileChanged Нет Без управления решениями. Используются для побочных эффектов, таких как логирование или очистка

Некоторые события также могут переписывать содержимое, а не только разрешать или блокировать его:

Для сценариев редактирования или преобразования данных перехватывайте исходящие входные данные инструментов в PreToolUse, а входящие результаты инструментов — в PostToolUse.

Вот примеры каждого шаблона в действии:

Единственное значение для decision — "block". Чтобы разрешить продолжение действия, не указывайте decision в вашем JSON или завершитесь с кодом 0 вообще без JSON:

{
"decision": "block",
"reason": "Test suite must pass before proceeding"
}

Расширенные примеры, включая проверку команд Bash, фильтрацию промптов и скрипты автоматического одобрения, смотрите в разделе Что можно автоматизировать руководства и в эталонной реализации валидатора команд Bash.

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

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

SessionStart

Выполняется, когда Claude Code начинает новую сессию или возобновляет существующую. Полезно для загрузки контекста разработки, например существующих задач или недавних изменений в вашей кодовой базе, или для настройки переменных окружения. Для статического контекста, которому не требуется скрипт, используйте вместо этого CLAUDE.md.

SessionStart выполняется в каждой сессии, поэтому такие хуки должны работать быстро. Поддерживаются только хуки type: "command" и type: "mcp_tool". О том, когда выполняются хуки mcp_tool, см. Поля хуков инструментов MCP.

Значение matcher соответствует тому, как была запущена сессия:

Matcher Когда срабатывает
startup Новая сессия
resume --resume, --continue или /resume
clear /clear
compact Автоматическое или ручное сжатие контекста
fork Новая сессия, ответвлённая от существующей: --fork-session вместе с --resume или --continue, фоновая копия /fork, /branch или диалог, который вы переводите в фон

До версии v2.1.214 ответвлённые сессии сообщали источник "resume".

Когда вы запускаете интерактивную сессию, возобновляете диалог при запуске с помощью --continue или --resume или выполняете /clear, хуки SessionStart выполняются в фоне. Вы можете сразу начать печатать, а возобновлённый диалог появляется, не дожидаясь хуков. Первый ответ Claude всё равно ждёт завершения хуков, чтобы их контекст дошёл до Claude.

Когда вы переключаетесь между диалогами с помощью /resume внутри сессии, переключение, наоборот, ждёт завершения хуков. Если вы выполните /clear или переключитесь на другой диалог, пока фоновые хуки ещё выполняются, ничего из того, что они вернут, не применяется к сессии.

То же ожидание действует и при запуске, включая возобновлённую сессию: промпт, отправленный, пока хуки SessionStart ещё выполняются, не дойдёт до Claude, пока они не завершатся.

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

Входные данные SessionStart

Помимо общих входных полей, хуки SessionStart получают source и, необязательно, model, agent_type и session_title:

Поле Описание
source Как началась сессия: "startup" для новых сессий, "resume" для возобновлённых сессий, "clear" после /clear, "compact" после сжатия контекста или "fork" для новой сессии, ответвлённой от существующей
model Идентификатор активной модели. Может отсутствовать, например после /clear или когда сессия восстановлена через восстановление диалога, поэтому проверяйте наличие поля перед его чтением
agent_type Имя агента; присутствует, когда вы запускаете Claude Code с claude --agent <name>
session_title Пользовательское название сессии; присутствует, если оно задано, например через --name, /rename, вывод sessionTitle хука или renameSession() в Agent SDK. Хук, который выдаёт sessionTitle, может сначала проверить это поле, чтобы не перезаписать существующее пользовательское название

У сессии, которой вы не дали имя, всё равно может быть сгенерированное название. Такое название не является пользовательским и не появляется в session_title.

Когда source равен "resume" или "fork" и транскрипт содержит хотя бы один ответ Claude, хуки SessionStart также получают четыре поля, перечисленные ниже. Ваш хук может использовать их, чтобы до первого запроса сообщить, во что обойдётся возобновление устаревшего диалога, например в systemMessage. Для этих полей требуется Claude Code v2.1.251 или новее.

Поле Описание
seconds_since_last_response Реальное время в секундах с момента последнего ответа в возобновлённом транскрипте
context_tokens Токены, которые первый запрос возобновлённой сессии повторно отправляет в качестве промпта
prompt_cache_likely_expired true, когда последний ответ старше срока жизни кэша промптов сессии или когда последующее сжатие контекста заменило кэшированный диалог
estimated_cache_write_usd Оценочная стоимость в долларах США записи context_tokens в кэш промптов на модели сессии, без учёта ответа

В этом примере показаны входные данные для сессии, возобновлённой через 90 минут после её последнего ответа:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionStart",
  "source": "resume",
  "model": "claude-opus-5",
  "seconds_since_last_response": 5400,
  "context_tokens": 182340,
  "prompt_cache_likely_expired": true,
  "estimated_cache_write_usd": 1.1396
}

Управление решениями SessionStart

Claude Code добавляет в контекст Claude stdout, который он обрабатывает как обычный текст. Помимо полей вывода JSON, доступных всем хукам, вы можете возвращать следующие поля, специфичные для события:

Поле Описание
additionalContext Строка, добавляемая в контекст Claude в начале диалога, до первого промпта. О том, как доставляется текст и что в него помещать, см. Добавление контекста для Claude
initialUserMessage Строка, используемая как первое пользовательское сообщение сессии. Применяется в неинтерактивном режиме с флагом -p, где она становится первым ходом, даже если промпт не передан. Если промпт передан, он следует за ней как следующий ход. В отличие от additionalContext, который прикрепляется к существующему ходу, это поле создаёт ход
sessionTitle Задаёт название сессии, с тем же эффектом, что и /rename. Используйте, чтобы автоматически именовать сессии по папке запуска, ветке git или имени worktree. Применяется, когда source равен "startup", "resume" или "fork"; игнорируется для "clear" и "compact"
watchPaths Массив абсолютных путей для отслеживания событий FileChanged во время этой сессии
reloadSkills Логическое значение. При true Claude Code повторно сканирует каталоги скиллов и команд после завершения хуков SessionStart, чтобы скиллы, установленные хуком, были доступны в той же сессии, начиная с первого промпта
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2",
    "sessionTitle": "auth-refactor"
  }
}

Поскольку для этого события обычный stdout и так доходит до Claude, хук, который только загружает контекст, может выводить его прямо в stdout, не формируя JSON. Используйте форму JSON, когда нужно совместить контекст с другими полями, например sessionTitle.

Используйте reloadSkills, когда хук SessionStart устанавливает или обновляет скиллы. Обнаружение скиллов обычно выполняется до завершения хуков SessionStart, поэтому файлы, которые хук записывает в ~/.claude/skills/ или .claude/skills/, иначе появились бы только в следующей сессии. Этот пример синхронизирует общий репозиторий скиллов и запрашивает повторное сканирование:

#!/bin/bash

git -C ~/.claude/skills/team-skills pull --quiet 2>/dev/null || \
  git clone --quiet https://git.example.com/your-org/team-skills.git ~/.claude/skills/team-skills

echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "reloadSkills": true}}'

URL репозитория здесь — заглушка; замените его на собственный репозиторий скиллов. С заглушкой клонирование завершается ошибкой и выводит сообщение fatal: в stderr. Stderr хука SessionStart, завершившегося с кодом 0, носит лишь информационный характер, поэтому запрос reloadSkills всё равно применяется.

Сохранение переменных окружения

Хуки SessionStart имеют доступ к переменной окружения CLAUDE_ENV_FILE, которая содержит путь к файлу, где можно сохранить переменные окружения для последующих команд Bash.

Чтобы задать отдельные переменные окружения, запишите инструкции export в CLAUDE_ENV_FILE. Используйте добавление (>>), чтобы сохранить переменные, заданные другими хуками:

#!/bin/bash

if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
  echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
  echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi

exit 0

Чтобы зафиксировать все изменения окружения, внесённые командами настройки, сравните экспортированные переменные до и после:

#!/bin/bash

ENV_BEFORE=$(export -p | sort)

# Run your setup commands that modify the environment
source ~/.nvm/nvm.sh
nvm use 20

if [ -n "$CLAUDE_ENV_FILE" ]; then
  ENV_AFTER=$(export -p | sort)
  comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi

exit 0

Setup

Срабатывает только при запуске Claude Code с --init-only либо с --init или --maintenance в неинтерактивном режиме с флагом -p. При обычном запуске не срабатывает. Используйте его для одноразовой установки зависимостей или плановой очистки, которую вы явно запускаете из CI или скриптов, отдельно от обычного запуска сессии. Для инициализации на уровне каждой сессии используйте вместо этого SessionStart.

Значение matcher соответствует флагу CLI, который вызвал хук:

Matcher Когда срабатывает
init claude --init-only или claude -p --init
maintenance claude -p --maintenance

Когда вы выполняете claude --init-only, Claude Code запускает хуки Setup и хуки SessionStart с matcher startup, а затем завершает работу, не начиная диалог.

Когда вы начинаете или продолжаете диалог с -p, нужно также передать промпт — в качестве аргумента или через stdin. Промпт можно не передавать, если хук SessionStart предоставляет initialUserMessage или если вы возобновляете сессию с отложенным вызовом инструмента.

При успешном выполнении --init-only ничего не выводит в терминал. Чтобы убедиться, что хуки выполнились, запустите claude --debug-file <path> --init-only, заменив <path> на расположение файла лога, и проверьте в логе записи хуков Setup и SessionStart.

Поскольку Setup срабатывает не при каждом запуске, плагин, которому нужна установленная зависимость, не может полагаться только на Setup. Практичный подход — проверять наличие зависимости при первом использовании и устанавливать её при отсутствии, например хуком или скиллом, который проверяет наличие ${CLAUDE_PLUGIN_DATA}/node_modules и выполняет npm install, если его нет. О том, где хранить установленные зависимости, см. постоянный каталог данных. Если вы распространяете плагин через маркетплейс, этот подход может не понадобиться: Claude Code автоматически устанавливает подходящие зависимости пакетов Node.js при кэшировании плагина.

Входные данные Setup

Помимо общих входных полей, хуки Setup получают поле trigger со значением "init" или "maintenance":

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Setup",
  "trigger": "init"
}

Управление решениями Setup

Хуки Setup не могут блокировать; выполнение продолжается при любом коде выхода. При любом коде выхода Claude Code отбрасывает поля вывода JSON хука Setup, такие как systemMessage, continue и hookSpecificOutput.additionalContext. С -p stdout, stderr и код выхода хука Setup появляются в выводе запуска только как события hook_response, когда вы запускаете с --output-format stream-json --verbose.

Хуки Setup имеют доступ к CLAUDE_ENV_FILE. Переменные, записанные в этот файл, сохраняются для последующих команд Bash в сессии, как и в хуках SessionStart. Для Setup выполняются только хуки type: "command". Хук type: "mcp_tool" для Setup всегда пропускается, как описано в разделе Поля хуков инструментов MCP.

InstructionsLoaded

Срабатывает, когда файл CLAUDE.md или .claude/rules/*.md загружается в контекст. Это событие срабатывает при запуске сессии для файлов, загружаемых сразу, и повторно позже, когда файлы загружаются отложенно, например когда Claude обращается к подкаталогу, содержащему вложенный CLAUDE.md, или когда срабатывают условные правила с frontmatter paths:. Хук не поддерживает блокировку или управление решениями. Он выполняется асинхронно в целях наблюдаемости.

Это событие не срабатывает, когда Claude читает AGENTS.md напрямую через настройку Project instructions. Оно срабатывает, когда CLAUDE.md импортирует ваш AGENTS.md — с load_reason, равным include, как и для любого другого импортируемого файла, — а также когда CLAUDE.md является символической ссылкой на него, как при обычной загрузке CLAUDE.md.

Matcher сопоставляется с load_reason. Например, используйте "matcher": "session_start", чтобы срабатывать только для файлов, загруженных при запуске сессии, или "matcher": "path_glob_match|nested_traversal", чтобы срабатывать только при отложенной загрузке.

Входные данные InstructionsLoaded

Помимо общих входных полей, хуки InstructionsLoaded получают следующие поля:

Поле Описание
file_path Абсолютный путь к загруженному файлу инструкций
memory_type Область действия файла: "User", "Project", "Local" или "Managed"
load_reason Почему файл был загружен: "session_start", "nested_traversal", "path_glob_match", "include" или "compact". Значение "compact" используется, когда файлы инструкций повторно загружаются после сжатия контекста
globs Glob-шаблоны путей из frontmatter paths: файла, если они есть. Присутствует только при загрузках path_glob_match
trigger_file_path Путь к файлу, обращение к которому вызвало эту загрузку, для отложенных загрузок
parent_file_path Путь к родительскому файлу инструкций, который включил этот, для загрузок include
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "InstructionsLoaded",
  "file_path": "/Users/my-project/CLAUDE.md",
  "memory_type": "Project",
  "load_reason": "session_start"
}

Управление решениями InstructionsLoaded

Хуки InstructionsLoaded не имеют управления решениями. Они не могут блокировать или изменять загрузку инструкций. Claude Code отбрасывает их поля вывода JSON, такие как systemMessage и continue. Используйте это событие для журнала аудита, отслеживания соответствия требованиям или наблюдаемости.

UserPromptSubmit

Выполняется при отправке промпта, до того как Claude его обработает. Это позволяет добавлять дополнительный контекст на основе промпта или диалога, проверять промпты или блокировать определённые типы промптов.

Хуки UserPromptSubmit срабатывают не только на промпты, которые вы вводите. Claude Code также запускает их, когда:

Хуки UserPromptSubmit имеют таймаут по умолчанию 30 секунд для типов command, http и mcp_tool — меньше, чем стандартные 600 секунд для этих типов в большинстве других событий. Поскольку этот хук выполняется перед каждым промптом и блокирует обработку моделью до своего завершения, зависший хук останавливает сессию. Если вашему хуку нужно больше времени, задайте поле timeout в записи хука.

За исключением командного хука, который вы запускаете с async: true, хук UserPromptSubmit типа command, HTTP или MCP tool, достигший таймаута, отменяется, а его вывод, включая additionalContext, отбрасывается. Промпт всё равно доходит до Claude, но без этого контекста. В транскрипте отображается уведомление с именем хука, сработавшим таймаутом и сообщением о том, что вывод был отброшен.

Хук обратного вызова Agent SDK для UserPromptSubmit, достигший таймаута, блокирует промпт с сообщением, в котором названы хук и таймаут, поскольку обратный вызов в этом месте может выступать в роли шлюза политики, который не должен при сбое пропускать всё подряд. Сессия продолжается. До версии v2.1.208 таймаут обратного вызова для этого события завершал ход с ошибкой выполнения.

Входные данные UserPromptSubmit

Помимо общих входных полей, хуки UserPromptSubmit получают поле prompt, содержащее отправленный текст. Вставленное содержимое, свёрнутое в заполнитель [Pasted text #N], поступает развёрнутым на своём месте. В сессиях, где Claude Code помечает вставленный текст для Claude, это развёрнутое содержимое находится между строкой <pasted_content id="…"> и строкой </pasted_content id="…">, поэтому учитывайте эти строки, если ваш хук разбирает промпт.

Хуки UserPromptSubmit также получают session_title, когда у сессии есть пользовательское название, с тем же значением, что и у поля session_title в SessionStart.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "Write a function to calculate the factorial of a number"
}

Управление решениями UserPromptSubmit

Хуки UserPromptSubmit могут управлять тем, будет ли обработан отправленный промпт, и добавлять контекст. Доступны все поля вывода JSON.

Есть два способа добавить контекст в диалог при коде выхода 0:

  • Обычный текст в stdout: Claude Code добавляет в контекст Claude stdout, который он обрабатывает как обычный текст
  • JSON с additionalContext: используйте формат JSON ниже для более тонкого управления. Поле additionalContext добавляется как контекст

Ни один из каналов не создаёт видимой записи в транскрипте. Обычный stdout и значение additionalContext внедряются каждый как системное напоминание, начинающееся с имени хука; Claude читает оба. Чтобы убедиться в доставке, проверьте лог отладки.

Чтобы заблокировать промпт, верните объект JSON с decision, равным "block":

Поле Описание
decision "block" останавливает промпт до того, как он дойдёт до Claude. Не указывайте, чтобы разрешить обработку промпта
reason Показывается пользователю, когда decision равен "block". Не добавляется в контекст
additionalContext Строка, добавляемая в контекст Claude вместе с отправленным промптом. См. Добавление контекста для Claude
sessionTitle Задаёт название сессии. Используйте, чтобы автоматически именовать сессии на основе содержимого промпта
suppressOriginalPrompt Если true и хук блокирует промпт, текст промпта не включается в сообщение о блокировке. См. Что остаётся после заблокированного промпта

Хук, который блокирует, завершаясь с кодом 2, обрабатывается так же, как reason: сообщение о блокировке показывает пользователю текст из stderr, и он не добавляется в контекст.

{
  "decision": "block",
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "My additional context here",
    "sessionTitle": "My session title",
    "suppressOriginalPrompt": true
  }
}

Что остаётся после заблокированного промпта

Заблокированный промпт никогда не доходит до Claude, но его текст удаляется не отовсюду. По умолчанию сообщение о блокировке, показываемое пользователю, заканчивается строкой Original prompt:, за которой следует отправленный текст, и Claude Code записывает это сообщение в файл транскрипта сессии на диске. Чтобы исключить текст из сообщения, выведите JSON с "suppressOriginalPrompt": true внутри hookSpecificOutput. Это работает независимо от того, блокирует ли хук через decision: "block" или завершением с кодом 2.

suppressOriginalPrompt изменяет только сообщение о блокировке. Отправленный текст всё равно может оказаться в локальных файлах, таких как транскрипт сессии и история промптов, поэтому блокирующий хук — не способ уберечь секрет от записи на диск. Чтобы ограничить или удалить эти файлы, см. Хранение в открытом виде и Очистка локальных данных.

UserPromptExpansion

Выполняется, когда введённая пользователем команда разворачивается в промпт до того, как дойти до Claude. Используйте его, чтобы блокировать прямой вызов определённых команд, внедрять контекст для конкретного скилла или логировать, какие команды вызывают пользователи. Например, хук с matcher deploy может блокировать /deploy, если нет файла одобрения, а хук с matcher для скилла ревью может добавлять чек-лист ревью команды в качестве additionalContext.

Это событие покрывает путь, который не покрывает PreToolUse: хук PreToolUse, сопоставленный с инструментом Skill, срабатывает только когда Claude вызывает инструмент, а прямой ввод /skillname обходит PreToolUse. UserPromptExpansion срабатывает на этом прямом пути.

Сопоставляется с command_name. Оставьте matcher пустым, чтобы срабатывать на каждую команду промптового типа.

Входные данные UserPromptExpansion

Помимо общих входных полей, хуки UserPromptExpansion получают expansion_type, command_name, command_args, command_source и исходную строку prompt. Поле expansion_type равно slash_command для скиллов и пользовательских команд или mcp_prompt для промптов MCP-серверов.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../00893aaf.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "UserPromptExpansion",
  "expansion_type": "slash_command",
  "command_name": "example-skill",
  "command_args": "arg1 arg2",
  "command_source": "plugin",
  "prompt": "/example-skill arg1 arg2"
}

Управление решениями UserPromptExpansion

Хуки UserPromptExpansion могут блокировать разворачивание или добавлять контекст. Доступны все поля вывода JSON.

Поле Описание
decision "block" не даёт команде развернуться. Не указывайте, чтобы разрешить продолжение
reason Показывается пользователю, когда decision равен "block"
additionalContext Строка, добавляемая в контекст Claude вместе с развёрнутым промптом. См. Добавление контекста для Claude

Хук, который блокирует, завершаясь с кодом 2, обрабатывается так же, как reason: сообщение о блокировке показывает пользователю текст из stderr.

{
  "decision": "block",
  "reason": "This slash command is not available",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptExpansion",
    "additionalContext": "Additional context for this expansion"
  }
}

MessageDisplay

Выполняется во время потокового вывода сообщения ассистента на экран. Claude Code отображает сообщение порциями: каждый раз, когда пакет новых завершённых строк готов к отрисовке, хук выполняется один раз с этими строками, и Claude Code отрисовывает на их месте текст замены, возвращённый хуком. Длинное сообщение порождает несколько вызовов; короткое может породить только один.

Используйте MessageDisplay, чтобы:

  • удалять разметку markdown для минималистичного отображения
  • преобразовывать текст, который приложение на Agent SDK показывает своим пользователям
  • скрывать API-ключи или внутренние имена хостов в ответах Claude

Claude Code удерживает каждый пакет, пока ваш хук не вернёт результат, поэтому хук должен работать быстро. Если хук завершается ошибкой или по таймауту, Claude Code отображает исходный текст. Таймаут по умолчанию для этого события — 10 секунд; если вашему хуку нужно больше времени, задайте поле timeout в записи хука.

MessageDisplay влияет только на отображение: текст замены изменяет лишь то, что отрисовывается на экране. Транскрипт и то, что видит Claude, сохраняют исходный текст, поэтому Claude никогда не видит замену, а подробный режим показывает исходный текст. Хук получает только текст сообщений ассистента, поэтому результаты инструментов и вводимый вами текст отображаются без изменений.

MessageDisplay не поддерживает matcher и срабатывает для каждого сообщения ассистента, которое выводит текст потоком; сообщения без текста, например ответы, состоящие только из вызовов инструментов, его не вызывают.

В неинтерактивных запусках, включая запросы Agent SDK и claude -p, MessageDisplay выполняется один раз на сообщение ассистента, а не один раз на пакет строк. Единственный вызов приходит после завершения сообщения и содержит полный текст сообщения: index равен 0, final равен true, а delta содержит всё сообщение. Хук, собирающий текст delta для каждого сообщения, получает один и тот же общий текст в обоих режимах.

Входные данные MessageDisplay

Помимо общих входных полей, хуки MessageDisplay получают идентификаторы хода и сообщения, позицию этого вызова внутри сообщения и новый текст в delta. Границы пакетов зависят от того, как поступает поток текста, поэтому используйте index и final для отслеживания продвижения по сообщению, а не рассчитывайте на определённую группировку строк.

Поле Описание
turn_id UUID текущего хода
message_id UUID отображаемого сообщения ассистента. Одинаков для всех пакетов одного сообщения. Это не идентификатор API msg_…, поэтому его нельзя сопоставить с идентификаторами сообщений в транскрипте
index Индекс этого пакета внутри сообщения, начиная с нуля
final true для последнего пакета сообщения. У каждого сообщения ровно один финальный пакет
delta Новые завершённые строки с момента предыдущего пакета, включая завершающие символы новой строки. Всегда целые строки, кроме финального пакета, который может заканчиваться посреди строки. В интерактивных запусках delta финального пакета пуста, если сообщение заканчивается символом новой строки, поэтому считайте сигналом конца сообщения final, а не непустую delta. В запусках Agent SDK и claude -p единственный вызов содержит всё сообщение
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "MessageDisplay",
  "turn_id": "0c9e6a2f-7d41-4f4e-9a15-3f4f7c2b8d10",
  "message_id": "5b2a9c8e-1f63-4d8a-b7c4-9e0d2a6f1c3b",
  "index": 0,
  "final": false,
  "delta": "Here is the plan:\n"
}

Вывод MessageDisplay

Помимо полей вывода JSON, доступных всем хукам, хуки MessageDisplay могут возвращать displayContent, чтобы заменить delta на экране:

Поле Описание
displayContent Текст, отображаемый вместо delta. Не указывайте, чтобы отобразить исходный текст

Хуки MessageDisplay не имеют управления решениями. Они не могут заблокировать сообщение или изменить то, что хранится в транскрипте или отправляется Claude. Claude Code использует displayContent из их вывода JSON и отбрасывает systemMessage и continue.

Этот пример удаляет форматирование markdown из ответов Claude для отображения обычным текстом. Скрипт читает каждый пакет из stdin, удаляет из delta маркеры полужирного начертания и обратные кавычки встроенного кода и возвращает результат в виде displayContent.

Зарегистрируйте командный хук для события в файле настроек:

{
"hooks": {
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.sh",
"args": []
}
]
}
]
}
}

Сохраните этот скрипт в .claude/hooks/plain-display.sh в вашем проекте и сделайте его исполняемым с помощью chmod +x:

#!/bin/bash
jq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'

Пакеты без разметки markdown проходят без изменений. Если скрипт завершается ошибкой, например из-за отсутствия jq, Claude Code отображает исходный текст и отмечает сбой только в отладочном выводе, а не в сессии.

PreToolUse

Выполняется после того, как Claude сформирует параметры инструмента, и до обработки вызова инструмента. Сопоставляется с любым именем инструмента, кроме EndConversation: со встроенными инструментами, такими как Bash, PowerShell, Edit, Write, Read, Glob, Grep, Agent, Workflow, WebFetch, WebSearch, AskUserQuestion и ExitPlanMode, а также с любыми именами инструментов MCP.

Чтобы запускать хук при изменении конкретного файла на диске, независимо от того, что его записало, используйте FileChanged вместо сопоставления инструментов редактирования файлов по имени. В отличие от PreToolUse, Claude Code запускает хуки FileChanged после изменения, и у них нет управления решениями, поэтому они не могут заблокировать запись.

Используйте управление решениями PreToolUse, чтобы разрешить, запретить, запросить подтверждение или отложить вызов инструмента.

Хук обратного вызова Agent SDK для PreToolUse, превысивший свой таймаут, блокирует вызов инструмента, и Claude получает результат с ошибкой, в котором указан таймаут. Явный запрет, возвращённый другим хуком, по-прежнему имеет приоритет.

Входные данные PreToolUse

Помимо общих входных полей, хуки PreToolUse получают tool_name, tool_input и tool_use_id.

Для инструмента MCP входные данные также содержат mcp_server — объект с name сервера и полем source, указывающим, откуда взято определение сервера. Значения source включают plugin, sdk и области действия конфигурации, такие как user и project. В McpServerProvenance в справочнике Agent SDK перечислены все значения и указано, как обращаться с незнакомым значением. Принимайте решения о доверии на основе source, а не name или префикса имени инструмента mcp__<server>__. Для поля mcp_server требуется Claude Code v2.1.274 или новее.

Для файловых инструментов Write, Edit и Read значение tool_input.file_path всегда абсолютное:

  • Claude Code разворачивает ~ и относительные пути до запуска хуков, поэтому хук, сопоставляющий пути, нельзя обойти через ~ или относительное написание того же пути
  • В Windows путь приходит с разделителями-обратными слешами, даже если ваш хук выполняется в Git Bash, где $PWD выглядит как /c/project
  • Сравнение, записанное с прямыми слешами, например проверка /src/, никогда не совпадёт с путём с обратными слешами, и вызов инструмента продолжится так, будто хуку нечего было блокировать
  • Нормализуйте разделители перед сравнением: FILE_PATH="${FILE_PATH//\\//}" в Bash или file_path.replace("\\", "/") в Python, а затем сопоставляйте сегмент пути, например /src/, а не привязывайтесь к началу через ^, поскольку путь абсолютный

Вызов Write в Windows передаёт:

{
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "C:\\project\\src\\index.ts",
    "content": "..."
  },
  ...
}

Поля tool_input зависят от инструмента:

Bash

Выполняет shell-команды.

Поле Тип Пример Описание
command string "npm test" Shell-команда для выполнения
description string "Run test suite" Необязательное описание того, что делает команда
timeout number 120000 Необязательный таймаут в миллисекундах. Значения выше максимума уменьшаются до максимума, а не отклоняются
run_in_background boolean false Выполнять ли команду в фоне

Когда команда Bash изменяет файлы в репозитории Git, Claude Code может записывать, что изменилось. Он записывает изменения во всех режимах разрешений, если запись включена настройкой bashEditDiffEnabled; в описании этой настройки указано, какие файлы могут её задавать. В противном случае он записывает их только в авторежиме и режиме bypassPermissions, и только когда Claude Code направляет Claude редактировать файлы через Bash. Задайте bashEditDiffEnabled значение false, чтобы отключить запись. Фоновые команды и команды только для чтения не содержат diff.

Затем ваш хук PostToolUse получает изменённые файлы в tool_response.bashEditDiff. Список охватывает то, что изменилось в репозитории во время выполнения команды. Файлы, игнорируемые Git, и файлы в подмодулях не перечисляются. Требуется Claude Code v2.1.269 или новее.

changedFiles и files перечисляют, что изменила команда; остальные поля сообщают, насколько полон и надёжен этот список.

Поле Тип Пример Описание
changedFiles array ["/path/to/src/app.ts"] Абсолютные пути файлов, изменённых командой, не более 200. Присутствует, когда files содержит diff или moreFiles больше нуля
files array [{"filePath": "/path/to/src/app.ts", "hunks": [...]}] Diff не более 5 изменённых файлов, для отображения. created или deleted равно true для файла, который команда добавила или удалила
moreFiles number 2 Количество изменённых файлов без diff в files
unavailable boolean true Задаётся, когда diff неполон или его не удалось получить
skipped boolean true Задаётся для команды Git, которая перемещает рабочее дерево, например git checkout или git stash, поэтому Claude Code не получает diff
shared boolean true Задаётся, когда другой вызов инструмента Bash, например вызов субагента, выполнялся в том же репозитории в то же время, поэтому некоторые перечисленные изменения могут относиться к той команде
PowerShell

Выполняет команды PowerShell. Доступность по платформам см. в описании инструмента PowerShell.

Поля совпадают с инструментом Bash, строка команды находится в command:

Поле Тип Пример Описание
command string "Get-ChildItem -Recurse" Команда PowerShell для выполнения
description string "List files recursively" Необязательное описание того, что делает команда
timeout number 120000 Необязательный таймаут в миллисекундах
run_in_background boolean false Выполнять ли команду в фоне

В хуках, которые проверяют shell-команды, используйте matcher Bash|PowerShell, чтобы охватить оба инструмента:

  • В Windows везде, где включён инструмент PowerShell, Claude считает PowerShell основной оболочкой и направляет shell-команды через неё.
  • В Windows без Git Bash инструмент включается автоматически, и Claude Code вообще не регистрирует инструмент Bash.
  • Хук, сопоставленный только с Bash, там никогда не сработает.
Write

Создаёт или перезаписывает файл.

Поле Тип Пример Описание
file_path string "/path/to/file.txt" Абсолютный путь к файлу для записи
content string "file content" Содержимое для записи в файл
Edit

Заменяет строку в существующем файле.

Поле Тип Пример Описание
file_path string "/path/to/file.txt" Абсолютный путь к файлу для редактирования
old_string string "original text" Текст для поиска и замены
new_string string "replacement text" Текст замены
replace_all boolean false Заменять ли все вхождения
Read

Читает содержимое файла.

Поле Тип Пример Описание
file_path string "/path/to/file.txt" Абсолютный путь к файлу для чтения
offset number 10 Необязательный номер строки, с которой начинать чтение
limit number 50 Необязательное количество строк для чтения
Glob

Находит файлы, соответствующие glob-шаблону.

Поле Тип Пример Описание
pattern string "**/*.ts" Glob-шаблон для сопоставления файлов
path string "/path/to/dir" Необязательный каталог для поиска. По умолчанию — текущий рабочий каталог
Grep

Ищет по содержимому файлов с помощью регулярных выражений.

Поле Тип Пример Описание
pattern string "TODO.*fix" Шаблон регулярного выражения для поиска
path string "/path/to/dir" Необязательный файл или каталог для поиска
glob string "*.ts" Необязательный glob-шаблон для фильтрации файлов
output_mode string "content" "content", "files_with_matches" или "count". По умолчанию "files_with_matches"
-i boolean true Поиск без учёта регистра
multiline boolean false Включить многострочное сопоставление
WebFetch

Загружает и обрабатывает веб-содержимое.

Поле Тип Пример Описание
url string "https://example.com/api" URL, с которого загружается содержимое
prompt string "Extract the API endpoints" Промпт, применяемый к загруженному содержимому
WebSearch

Выполняет поиск в интернете.

Поле Тип Пример Описание
query string "react hooks best practices" Поисковый запрос
allowed_domains array ["docs.example.com"] Необязательно: включать только результаты с этих доменов
blocked_domains array ["spam.example.com"] Необязательно: исключать результаты с этих доменов
Agent

Запускает субагента.

Поле Тип Пример Описание
prompt string "Find all API endpoints" Задача, которую должен выполнить агент
description string "Find API endpoints" Краткое описание задачи
subagent_type string "Explore" Тип используемого специализированного агента
model string "sonnet" Необязательный псевдоним модели для переопределения модели по умолчанию

Когда вызов Agent на переднем плане завершается, ваш хук PostToolUse получает результат субагента и телеметрию запуска в tool_response. Читайте эти поля, чтобы изучить запуск; для сводок по токенам и стоимости по всем субагентам используйте счётчики токенов и стоимости с фильтром query_source "subagent", поскольку totalTokens и usage охватывают только последний запрос:

Поле Тип Пример Описание
status string "completed" "completed" для субагентов на переднем плане, "async_launched" для фоновых субагентов. Субагенты по умолчанию выполняются в фоне, поэтому вызов Agent без run_in_background также даёт "async_launched"
agentId string "a4d2c8f1e0b3a297" Идентификатор запуска субагента
content array [{"type": "text", "text": "Found 12 endpoints..."}] Финальные текстовые блоки субагента или, для субагента, чей отчёт передаётся через SubagentHandback, вместо них краткая заметка об этой передаче
resolvedModel string "claude-sonnet-4-5" Модель, на которой субагент начал работу; может отличаться от запрошенной модели
modelsUsed array ["claude-sonnet-4-5", "claude-haiku-4-5"] Использованные модели по порядку, с объединением последовательных повторов; задаётся только когда модель сменилась во время запуска. Требуется Claude Code v2.1.212 или новее
totalTokens number 12450 Количество токенов из последнего запроса API субагента: входные, выходные и кэш-токены вместе. Это не итог за весь запуск
totalDurationMs number 48211 Реальная длительность запуска субагента
totalToolUseCount number 7 Количество вызовов инструментов, сделанных субагентом
usage object {"input_tokens": 8320, ...} Разбивка токенов по типам для последнего запроса API: input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens

В Claude Code v2.1.271 или новее субагент, работающий с инструментом SubagentHandback, который Claude Code предоставляет в авторежиме, передаёт свой отчёт через этот инструмент, а не возвращает его текстом. Поле content его результата completed тогда содержит краткую заметку об этой передаче, а не сам отчёт. Чтобы прочитать отчёт, сопоставьте хук PreToolUse или PostToolUse с SubagentHandback и прочитайте tool_input.message.

Для фоновых субагентов инструмент возвращает результат, когда задача переходит в фон, поэтому tool_response не содержит полей использования: фоновый запуск возвращается немедленно, а задача на переднем плане, которую Claude Code переводит в фон во время выполнения, возвращается в момент этого перехода. Он содержит status: "async_launched", agentId, description, prompt, outputFile и resolvedModel.

В ответе completed поле resolvedModel называет модель, на которой субагент начал работу; она может отличаться от значения model в tool_input, например когда применяется availableModels или другое переопределение. В ответе async_launched поле resolvedModel называет модель, использовавшуюся в момент перевода агента в фон, поэтому смена модели, произошедшая до перевода в фон, отражается там. Для modelsUsed и поведения resolvedModel на момент перевода в фон требуется Claude Code v2.1.212 или новее.

AskUserQuestion

Задаёт пользователю от одного до четырёх вопросов с вариантами ответа.

Поле Тип Пример Описание
questions array [{"question": "Which framework?", "header": "Framework", "options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}], "multiSelect": false}] Вопросы для показа, каждый со строкой question, кратким header, массивом options и необязательным флагом multiSelect
answers object {"Which framework?": "React"} Необязательно. Сопоставляет текст вопроса с меткой выбранного варианта. В ответах с множественным выбором метки объединяются через запятые. Claude не задаёт это поле; передайте его через updatedInput, чтобы отвечать программно
ExitPlanMode

Представляет план и просит пользователя утвердить его, прежде чем Claude выйдет из режима планирования. Claude записывает план в файл на диске перед вызовом инструмента, поэтому буквальный tool_input от модели обычно пуст. Claude Code внедряет содержимое плана и путь к файлу перед передачей входных данных хукам.

Поле Тип Пример Описание
plan string "## Refactor auth\n1. Extract..." Содержимое плана в Markdown. Внедряется из файла плана на диске
planFilePath string "/Users/.../plans/refactor-auth.md" Путь к файлу плана. Внедряется
allowedPrompts array [{"tool": "Bash", "prompt": "run tests"}] Устаревшее. Claude Code принимает это поле, но игнорирует его. До версии v2.1.205 оно содержало разрешения на основе промптов, которые Claude запрашивал для реализации плана

В PostToolUse поле tool_response — это объект с полями plan и filePath, содержащими утверждённый план, а также внутренними флагами состояния. Читайте содержимое плана из tool_response.plan, а не перечитывайте файл с диска.

Управление решениями PreToolUse

Хуки PreToolUse могут управлять тем, будет ли выполнен вызов инструмента. В отличие от других хуков, использующих поле decision верхнего уровня, PreToolUse возвращает своё решение внутри объекта hookSpecificOutput. Это даёт более богатые возможности управления: четыре исхода (разрешить, запретить, запросить подтверждение или отложить), а также возможность изменить входные данные инструмента перед выполнением.

Поле Описание
permissionDecision "allow" пропускает запрос разрешения, за исключением действий, которые не одобряются автоматически ни в одном режиме, а также AskUserQuestion и ExitPlanMode, которым нужно updatedInput в паре с ним. "deny" предотвращает вызов инструмента. "ask" просит пользователя подтвердить. "defer" корректно завершает работу, чтобы инструмент можно было возобновить позже. Правила запрета и подтверждения всё равно применяются независимо от того, что вернул хук
permissionDecisionReason Для "ask" показывается пользователю в запросе разрешения. Когда Claude Code отклоняет вызов в запуске -p, где никто не может ответить на этот запрос, Claude вместо этого читает причину в результате инструмента. Для "deny" показывается Claude. Для "allow" и "defer" записывается только в лог отладки
updatedInput Изменяет входные параметры инструмента перед выполнением. Заменяет весь объект входных данных, поэтому включайте неизменённые поля вместе с изменёнными. Claude Code проверяет правила разрешений и пригодность к автоматическому переводу в фон команды Bash по входным данным, которые возвращает ваш хук, а не по тем, что отправил Claude. Сочетайте с "allow" для автоматического одобрения или с "ask", чтобы показать пользователю изменённые входные данные. Для "defer" игнорируется
additionalContext Строка, добавляемая в контекст Claude вместе с результатом инструмента. Игнорируется, когда permissionDecision равен "defer". См. Добавление контекста для Claude

Когда несколько хуков PreToolUse возвращают разные решения, приоритет такой: deny > defer > ask > allow.

Хук, который блокирует, завершаясь с кодом 2, обрабатывается так же, как "deny": Claude видит сообщение из stderr как причину отказа.

Когда хук возвращает "ask", запрос разрешения, показываемый пользователю, содержит метку, указывающую, откуда взялся хук: [settings] для хука из любого файла настроек или из frontmatter агента, [plugin:<name>] для хука плагина или [skill] для хука из frontmatter скилла. Это помогает пользователям понять, какой источник конфигурации запрашивает подтверждение.

"ask" от хука также принудительно вызывает запрос разрешения в авторежиме: классификатор по-прежнему может отклонить вызов инструмента, но не может одобрить его молча. До версии v2.1.211 классификатор мог одобрить команду Bash, выполняемую вне песочницы, не показывая запрос, затребованный хуком; при этом классификатор всё равно применял к этой команде собственные правила безопасности, а "deny" от хука всегда соблюдался.

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "My reason here",
    "updatedInput": {
      "field_to_modify": "new value"
    },
    "additionalContext": "Current environment: production. Proceed with caution."
  }
}

Инструменты, требующие взаимодействия с пользователем

AskUserQuestion и ExitPlanMode требуют взаимодействия с пользователем. В неинтерактивном режиме с флагом -p Claude Code предлагает их только когда у запуска есть хост разрешений, который принимает запрос, например обратный вызов canUseTool в Agent SDK.

Хук PreToolUse удовлетворяет этому требованию, если делает следующее:

  1. Читает входные данные инструмента из stdin
  2. Собирает ответ через ваш собственный интерфейс
  3. Возвращает permissionDecision: "allow" вместе с updatedInput, содержащим ответ, чтобы инструмент выполнился без запроса

Одного возврата "allow" для этих инструментов недостаточно.

Для AskUserQuestion верните исходный массив questions и добавьте объект answers, сопоставляющий текст каждого вопроса с выбранным ответом. Этот вывод отвечает на один вопрос вариантом React:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "updatedInput": {
      "questions": [
        {
          "question": "Which framework?",
          "header": "Framework",
          "options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}],
          "multiSelect": false
        }
      ],
      "answers": {"Which framework?": "React"}
    }
  }
}

Инструмент MCP, который сервер помечает с помощью _meta["anthropic/requiresUserInteraction"], устроен строже: хук не может пропустить его запрос подтверждения с помощью "allow", с updatedInput или без него, поскольку Claude Code не может убедиться, что хук провёл взаимодействие, необходимое инструменту.

Отложить вызов инструмента на потом

"defer" предназначен для интеграций, которые запускают claude -p как подпроцесс и читают его вывод JSON, например приложения на Agent SDK или пользовательского интерфейса, построенного поверх Claude Code. Он позволяет вызывающему процессу приостановить Claude на вызове инструмента, собрать входные данные через собственный интерфейс и продолжить с того же места. Claude Code учитывает это значение только в неинтерактивном режиме с флагом -p. В интерактивных сессиях он записывает в лог предупреждение и игнорирует результат хука.

Типичный случай — инструмент AskUserQuestion: Claude хочет что-то спросить у пользователя, но нет терминала, в котором можно ответить. Запуск -p предлагает AskUserQuestion только когда у него есть хост разрешений, например инструмент MCP, передаваемый через --permission-prompt-tool, поэтому начинайте запуск с ним. Цикл работает так:

  1. Claude вызывает AskUserQuestion. Срабатывает хук PreToolUse.
  2. Хук возвращает permissionDecision: "defer". Инструмент не выполняется. Процесс завершается с stop_reason: "tool_deferred", а ожидающий вызов инструмента сохраняется в транскрипте.
  3. Вызывающий процесс читает deferred_tool_use из результата SDK, показывает вопрос в собственном интерфейсе и ждёт ответа.
  4. Вызывающий процесс выполняет claude -p --resume <session-id> с тем же хостом разрешений. Тот же вызов инструмента снова вызывает PreToolUse.
  5. Хук возвращает permissionDecision: "allow" с ответом в updatedInput. Инструмент выполняется, и Claude продолжает работу.

Поле deferred_tool_use содержит id, name и input инструмента. input — это параметры, которые Claude сформировал для вызова инструмента, зафиксированные до выполнения:

{
  "type": "result",
  "subtype": "success",
  "stop_reason": "tool_deferred",
  "session_id": "abc123",
  "deferred_tool_use": {
    "id": "toolu_01abc",
    "name": "AskUserQuestion",
    "input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}], "multiSelect": false }] }
  }
}

Ограничений по таймауту или количеству повторных попыток нет. Сессия хранится на диске, пока вы её не возобновите, с учётом очистки по сроку хранения cleanupPeriodDays, которая по умолчанию удаляет файлы сессий через 30 дней в соответствии с правилами очистки по сроку хранения. Если ответ не готов к моменту возобновления, хук может снова вернуть "defer", и процесс завершится так же. Вызывающий процесс сам решает, когда выйти из цикла, в итоге вернув из хука "allow" или "deny".

"defer" работает, только когда Claude делает в ходе один вызов инструмента. Если Claude делает несколько вызовов инструментов одновременно, "defer" игнорируется с предупреждением, и инструмент проходит обычный процесс проверки разрешений. Это ограничение существует потому, что при возобновлении можно повторно выполнить только один инструмент: нельзя отложить один вызов из пакета, не оставив остальные неразрешёнными.

Если отложенный инструмент больше недоступен при возобновлении, процесс завершается с stop_reason: "tool_deferred_unavailable" и is_error: true до срабатывания хука. Это происходит, когда MCP-сервер, предоставлявший инструмент, не подключён в возобновлённой сессии. Данные deferred_tool_use всё равно включаются, чтобы вы могли определить, какой инструмент пропал.

PermissionRequest

Выполняется, когда Claude Code собирается запросить у вас разрешение на использование инструмента. В сессиях, которые не могут показать запрос, например у фоновых субагентов в неинтерактивном режиме, Claude Code всё равно запускает эти хуки, и если ни один хук не возвращает решение, он отклоняет вызов инструмента. Для вызова, который доходит до --permission-prompt-tool или обратного вызова canUseTool в Agent SDK, хуки выполняются параллельно с вашим хостом, и применяется решение того, кто решит первым. Используйте управление решениями PermissionRequest, чтобы разрешать или запрещать от имени пользователя.

Используйте это событие, когда вам нужен сигнал в тот момент, когда Claude запрашивает разрешение на использование инструмента. Claude Code запускает хук Notification с типом permission_prompt только после того, как запрос прождёт около шести секунд.

Claude Code не запускает хуки PermissionRequest для сетевого запроса команды в песочнице. Чтобы получить сигнал об этом запросе, используйте тип уведомления permission_prompt.

Сопоставляется с именем инструмента, с теми же значениями, что и PreToolUse.

Входные данные PermissionRequest

Хуки PermissionRequest получают поля tool_name и tool_input, как хуки PreToolUse, но без tool_use_id. Для инструмента MCP они также получают объект mcp_server. Необязательный массив permission_suggestions содержит обновления разрешений, которые Claude Code предлагает для этого запроса, например добавление правила разрешения или изменение режима разрешений.

Массив permission_suggestions не является точным списком вариантов, которые вы видите, поскольку каждое диалоговое окно разрешения формирует собственные варианты. Некоторые диалоговые окна, например для редактирования файлов, вообще не читают этот массив и выводят варианты из самого запроса. Диалоговое окно, которое его читает, всё равно может скрыть вариант, предложение для которого остаётся в массиве, например когда allowManagedPermissionRulesOnly скрывает варианты сохранения правил. Оно также может предлагать варианты без соответствующей записи предложения, например Yes, and switch to auto mode, который меняет режим разрешений напрямую, а не через обновление разрешений.

Хуки PreToolUse выполняются перед каждым вызовом инструмента, независимо от того, требуется ли разрешение. Хуки PermissionRequest выполняются только когда Claude Code собирается запросить у вас разрешение или когда он иначе автоматически отклонил бы вызов, который не может показать запрос. Ни одно из этих событий не срабатывает для EndConversation.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PermissionRequest",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf node_modules",
    "description": "Remove node_modules directory"
  },
  "permission_suggestions": [
    {
      "type": "addRules",
      "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
      "behavior": "allow",
      "destination": "localSettings"
    }
  ]
}

Управление решениями PermissionRequest

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

Поле Описание
behavior "allow" предоставляет разрешение, "deny" отклоняет его. Правила запрета и подтверждения всё равно применяются, поэтому хук, вернувший "allow", не переопределяет подходящее правило запрета
updatedInput Только для "allow": изменяет входные параметры инструмента перед выполнением. Заменяет весь объект входных данных, поэтому включайте неизменённые поля вместе с изменёнными. Изменённые входные данные повторно проверяются по правилам запрета и подтверждения
updatedPermissions Только для "allow": массив записей обновления разрешений для применения, например добавление правила разрешения или изменение режима разрешений сессии
message Только для "deny": сообщает Claude, почему в разрешении отказано
interrupt Только для "deny": если true, останавливает Claude

Хук, который завершается с кодом 2 без объекта decision, оставляет процесс проверки разрешений без изменений, а его stderr отбрасывается. Предоставить или отклонить запрос может только объект decision.

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": {
        "command": "npm run lint"
      }
    }
  }
}

Записи обновления разрешений

Выходное поле updatedPermissions и входное поле permission_suggestions используют один и тот же массив объектов-записей. У каждой записи есть поле type, которое определяет остальные её поля, и поле destination, которое определяет, куда записывается изменение.

type Поля Действие
addRules rules, behavior, destination Добавляет правила разрешений. rules — это массив объектов {toolName, ruleContent?}. Опустите ruleContent, чтобы правило соответствовало всему инструменту. behavior принимает значение "allow", "deny" или "ask"
replaceRules rules, behavior, destination Заменяет все правила с указанным behavior в destination на переданные rules
removeRules rules, behavior, destination Удаляет совпадающие правила с указанным behavior
setMode mode, destination Изменяет режим разрешений. Допустимые режимы: default, auto, acceptEdits, dontAsk, bypassPermissions, plan, а также manual как псевдоним для default
addDirectories directories, destination Добавляет рабочие каталоги. directories — это массив строк с путями
removeDirectories directories, destination Удаляет рабочие каталоги

Поле destination в каждой записи определяет, останется ли изменение только в памяти или будет сохранено в файл настроек.

destination Куда записывается
session только в памяти, отбрасывается при завершении сессии
localSettings .claude/settings.local.json
projectSettings .claude/settings.json
userSettings ~/.claude/settings.json

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

PostToolUse

Выполняется сразу после успешного завершения инструмента.

Сопоставляется по имени инструмента, значения те же, что и для PreToolUse.

Используйте более широкое сопоставление, когда имя инструмента не подходит в качестве фильтра:

  • Чтобы запускать хук после успешного завершения любого инструмента, опустите matcher или задайте ему значение "*". Тогда ваш хук может сам определить, что изменилось, например выполнив git status --porcelain, который также выводит неотслеживаемые файлы, пропускаемые git diff. Для неудавшихся вызовов инструментов добавьте тот же хук в PostToolUseFailure.
  • Чтобы запускать хук при изменении определённого файла на диске, независимо от того, что его записало, используйте FileChanged. Claude Code не запускает хук PostToolUse с matcher Edit|Write, когда тот же файл перезаписывает команда Bash или процесс вне Claude Code.

Входные данные PostToolUse

Хуки PostToolUse срабатывают после того, как инструмент уже успешно выполнился. Входные данные включают как tool_input — аргументы, переданные инструменту, — так и tool_response — возвращённый им результат. Точная схема обоих полей зависит от инструмента. Пути в tool_input для файловых инструментов приходят в том же формате, что и для PreToolUse: всегда абсолютные, с нативными разделителями платформы, то есть с обратными слешами в Windows. Для инструмента MCP входные данные также содержат объект mcp_server.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  },
  "tool_response": {
    "filePath": "/path/to/file.txt",
    "type": "create"
  },
  "tool_use_id": "toolu_01ABC123...",
  "duration_ms": 12
}
Поле Описание
duration_ms Необязательное. Время выполнения инструмента в миллисекундах. Не включает время, проведённое в запросах разрешения и хуках PreToolUse

Управление решениями PostToolUse

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

Поле Описание
decision "block" добавляет reason рядом с результатом инструмента. Claude по-прежнему видит исходный вывод; чтобы заменить его, используйте updatedToolOutput
reason Пояснение, показываемое Claude, когда decision равно "block"
additionalContext Строка, добавляемая в контекст Claude вместе с результатом инструмента. См. Добавление контекста для Claude
classifierContext Короткая заметка о результате этого вызова для классификатора авторежима, а не для Claude. См. Аннотирование результата для классификатора авторежима. Требуется Claude Code v2.1.236 или новее
updatedToolOutput Заменяет вывод инструмента переданным значением до того, как он будет отправлен Claude. Значение должно соответствовать форме вывода инструмента
updatedMCPToolOutput Заменяет вывод только для инструментов MCP. Предпочтительнее использовать updatedToolOutput, который работает для всех инструментов

В примере ниже заменяется вывод вызова Bash. Значение замены соответствует форме вывода инструмента Bash:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Additional information for Claude",
    "updatedToolOutput": {
      "stdout": "[redacted]",
      "stderr": "",
      "interrupted": false,
      "isImage": false
    }
  }
}

Аннотирование результата для классификатора авторежима

Верните classifierContext, чтобы отправить короткую заметку о результате вызова инструмента классификатору авторежима, а не Claude. Классификатор никогда не получает сами результаты инструментов, поэтому это поле — поддерживаемый способ сообщить ему что-либо о том, что вернул вызов, прежде чем он будет проверять последующие действия. Для этого поля требуется Claude Code v2.1.236 или новее.

В примере ниже классификатору сообщается, откуда получен вывод запроса:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "classifierContext": "This query ran against the staging database, not production."
  }
}

Какой вес классификатор придаёт заметке, зависит от того, где вы настроили хук:

  • Хуки, настроенные в Claude Code: для хуков из файлов настроек, плагинов, скиллов и frontmatter агентов классификатор рассматривает заметку как непроверенный контекст, предоставленный приложением. Заметка никогда не устанавливает намерение пользователя, и если в ней утверждается, что вы что-то одобрили или запросили, классификатор сверяет это утверждение с вашими собственными сообщениями в диалоге
  • Внутрипроцессные обратные вызовы Agent SDK: когда приложение, встраивающее Claude Code, регистрирует хук как обратный вызов TypeScript SDK и возвращает заметку во время активной сессии, классификатор может рассматривать переданное в заметке утверждение пользователя как намерение пользователя. Такое утверждение может удовлетворить требование согласия, которое классификатор принял бы из отправленного вами сообщения, но оно никогда не снимает блокировку, которую не смогло бы снять и ваше собственное сообщение. После возобновления сессии Claude Code рассматривает восстановленные заметки как непроверенный контекст. Когда хуки из обеих групп аннотируют один и тот же вызов, классификатор рассматривает объединённую заметку как непроверенную

Claude Code применяет следующие ограничения при доставке заметки:

  • Длина: Claude Code ограничивает заметки для одного вызова инструмента 2000 символами и обрезает остальное. Ограничение общее для всех хуков, отвечающих на этот вызов
  • Только синхронные ответы: Claude Code игнорирует это поле в ответе хука, который выполняется в фоне, потому что такой ответ приходит уже после того, как Claude Code зафиксирует результат инструмента
  • Вызовы, которые классификатор не фиксирует: транскрипт классификатора опускает поиск только для чтения, такой как чтение файлов и поиск. Claude Code отбрасывает заметку, прикреплённую к одному из таких вызовов
  • Взаимодействие с перезаписью: когда заметка описывает вывод, который вы заменяете с помощью updatedToolOutput, возвращайте оба поля в одном и том же ответе хука. Claude Code отбрасывает заметку, если эта перезапись отклонена или её заменяет перезапись другого хука. Claude Code доставляет заметку, возвращённую без перезаписи, даже если другой хук перезаписывает вывод

PostToolUseFailure

Выполняется, когда инструмент, начавший выполнение, завершается с ошибкой: инструмент выбросил ошибку или инструмент MCP вернул результат с ошибкой. Используйте его, чтобы логировать сбои, отправлять оповещения или давать Claude корректирующую обратную связь.

Сопоставляется по имени инструмента, значения те же, что и для PreToolUse.

Входные данные PostToolUseFailure

Хуки PostToolUseFailure получают те же поля tool_name и tool_input, что и PostToolUse, а также информацию об ошибке в виде полей верхнего уровня. Для инструмента MCP они также получают объект mcp_server. Например, неудавшаяся команда npm test может передать:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUseFailure",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite"
  },
  "tool_use_id": "toolu_01ABC123...",
  "error": "Exit code 1\nError: Cannot find module 'express'",
  "is_interrupt": false,
  "duration_ms": 4187
}
Поле Описание
error Строка с описанием того, что пошло не так. Формат зависит от инструмента, завершившегося с ошибкой
is_interrupt Необязательное логическое значение. True, когда сбой дошёл до Claude Code как прерывание, а не как ошибка, о которой сообщил инструмент. Отмена работающего инструмента не вызывает этот хук; вместо этого результат инструмента содержит сообщение о прерывании
duration_ms Необязательное. Время выполнения инструмента в миллисекундах. Не включает время, проведённое в запросах разрешения и хуках PreToolUse

Строка error, как правило, совпадает с текстом, который Claude получает в качестве результата неудавшегося инструмента. Её формат зависит от инструмента и типа сбоя. Ориентируйте логику хука на tool_name, is_interrupt и первую строку Exit code N; остальную часть строки рассматривайте как текст для отображения, а не как стабильный формат.

  • Для Bash и PowerShell команда, которая выполнилась и завершилась, даёт первую строку Exit code N, а затем весь вывод команды одним блоком, в котором stdout и stderr перемешаны
  • Данные события также могут содержать просто сообщение о сбое без строки с кодом выхода, если Claude Code не смог запустить сам процесс оболочки
  • Claude Code обрезает середину длинных строк, вставляя маркер ... [N characters truncated] ..., и может добавлять собственные строки, например Command timed out after 2m 0s

Управление решениями PostToolUseFailure

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

Поле Описание
additionalContext Строка, добавляемая в контекст Claude вместе с ошибкой. См. Добавление контекста для Claude
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUseFailure",
    "additionalContext": "Additional information about the failure for Claude"
  }
}

PostToolBatch

Выполняется один раз после того, как все вызовы инструментов в пакете завершены, перед тем как Claude Code отправит следующий запрос модели. PostToolUse срабатывает один раз для каждого инструмента, а значит, срабатывает параллельно, когда Claude делает параллельные вызовы инструментов. PostToolBatch срабатывает ровно один раз с полным пакетом, поэтому это подходящее место для внедрения контекста, который зависит от набора выполненных инструментов, а не от какого-то одного инструмента. Для этого события нет matcher.

Входные данные PostToolBatch

Помимо общих входных полей, хуки PostToolBatch получают tool_calls — массив, описывающий каждый вызов инструмента в пакете:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolBatch",
  "tool_calls": [
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/accounts.py"},
      "tool_use_id": "toolu_01...",
      "tool_response": "1\tfrom __future__ import annotations\n2\t..."
    },
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/transactions.py"},
      "tool_use_id": "toolu_02...",
      "tool_response": "1\tfrom __future__ import annotations\n2\t..."
    }
  ]
}

tool_response содержит то же содержимое, которое модель получает в соответствующем блоке tool_result. Значение — это сериализованная строка или массив блоков содержимого, точно в том виде, в каком его выдал инструмент. Для Read это означает текст с префиксами номеров строк, а не сырое содержимое файла. Ответы могут быть большими, поэтому разбирайте только нужные вам поля.

Управление решениями PostToolBatch

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

Поле Описание
additionalContext Строка контекста, внедряемая один раз перед следующим вызовом модели. Подробности доставки, что в неё помещать и как возобновлённые сессии обрабатывают прошлые значения, см. в разделе Добавление контекста для Claude
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolBatch",
    "additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
  }
}

Возврат decision: "block" или continue: false останавливает агентный цикл перед следующим вызовом модели. Сообщение о блокировке берётся из JSON-поля reason или stopReason либо из stderr при коде выхода 2. Вы видите его как предупреждение в транскрипте, и оно остаётся в диалоге, поэтому Claude видит его, когда диалог продолжается.

PermissionDenied

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

Сопоставляется по имени инструмента, значения те же, что и для PreToolUse.

Входные данные PermissionDenied

Помимо общих входных полей, хуки PermissionDenied получают tool_name, tool_input, tool_use_id и reason. Для инструмента MCP они также получают объект mcp_server.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "auto",
  "hook_event_name": "PermissionDenied",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf /tmp/build",
    "description": "Clean build directory"
  },
  "tool_use_id": "toolu_01ABC123...",
  "reason": "[Irreversible Local Destruction]"
}
Поле Описание
reason Причина отказа. Для вердикта классификатора в большинстве сессий в ней указывается сработавшее правило в квадратных скобках, например [Data Exfiltration]; другие формы см. в разделе Просмотр отказов. Для отказа без вердикта она начинается с Auto mode could not evaluate this action and is blocking it for safety. Для отказа из-за недоступности модели классификатора это фиксированный текст Classifier unavailable

Управление решениями PermissionDenied

Хуки PermissionDenied могут сообщить модели, что она может повторить попытку отклонённого вызова инструмента. Верните JSON-объект с hookSpecificOutput.retry, равным true:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionDenied",
    "retry": true
  }
}

Когда retry равно true, Claude Code добавляет в диалог сообщение, сообщающее модели, что она может повторить попытку вызова инструмента. Сам отказ Claude Code не отменяет. Если ваш хук не возвращает JSON или возвращает retry: false, отказ остаётся в силе, и модель получает исходное сообщение об отклонении.

Claude Code игнорирует retry: true, когда классификатор не вынес вердикта по действию: его ответ не удалось разобрать или проверка безопасности, отдельная от авторежима, отклонила собственный запрос классификатора. Для таких отказов Claude Code уже сообщает модели в сообщении об отклонении, следует ли повторить попытку позже или двигаться дальше.

Notification

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

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

Matcher Когда срабатывает
permission_prompt Claude нужно, чтобы вы подтвердили использование инструмента или сетевой запрос команды в песочнице, и запрос ожидает около шести секунд
idle_prompt Claude закончил отвечать около 60 секунд назад, и с тех пор вы ничего не вводили
auth_success Аутентификация завершена
elicitation_dialog MCP-сервер открывает форму запроса данных, и вы ничего не вводили около шести секунд
elicitation_url_dialog MCP-сервер просит вас открыть URL в браузере, и вы ничего не вводили около шести секунд
elicitation_complete MCP-сервер сообщает, что запрос данных в режиме URL завершён
elicitation_response Ответ на запрос данных MCP отправлен обратно серверу
agent_needs_input Фоновая сессия начинает ждать вашего ввода, пока в терминале открыт вид агентов. Также срабатывает, когда терминальная сессия показывает вам вопрос о настройке терминала участника команды агентов или уведомление авторежима о плате за запросы классификатора, и вы ничего не вводили около шести секунд
agent_completed Фоновая сессия завершается или завершается со сбоем. Срабатывает только пока в терминале открыт вид агентов
quota_auto_resume_fired Claude Code продолжает вашу задачу после того, как её приостановил лимит использования claude.ai: в момент сброса или раньше, если какое-то ваше действие в Claude Code во время ожидания, например добавление кредитов использования, повышение тарифа или смена модели, снова делает использование доступным, с исключением для настройки модели
quota_auto_resume_stale Лимит использования claude.ai сбросился, пока ваш компьютер находился в спящем режиме более 30 минут. Claude Code ждёт, пока вы нажмёте Enter, вместо того чтобы продолжить. После более короткого сна он продолжает работу и вместо этого вызывает quota_auto_resume_fired
quota_auto_resume_disabled Claude Code прекращает ожидание сброса лимита использования claude.ai, не продолжая вашу задачу: autoContinueAtUsageLimit отключена или сброс сместился более чем на 24 часа вперёд во время ожидания, начатого Claude Code самостоятельно, продолженная задача снова и снова упиралась в лимит или продолжение было заблокировано до того, как дошло до модели. Не срабатывает, когда вы нажимаете Esc или Ctrl+C или выбираете Don't continue automatically

Для типов quota_auto_resume_fired, quota_auto_resume_stale и quota_auto_resume_disabled требуется Claude Code v2.1.234 или новее.

В терминальных сессиях для permission_prompt при сетевом запросе команды в песочнице требуется Claude Code v2.1.246 или новее.

Для agent_needs_input при вопросе о настройке терминала участника команды требуется Claude Code v2.1.248 или новее.

Claude Code иначе рассчитывает время для permission_prompt в сессиях, где он отправляет запросы разрешения в обратный вызов canUseTool Agent SDK — именно так Claude Desktop и расширение VS Code размещают Claude Code:

  • Ожидайте permission_prompt примерно через шесть секунд после того, как Claude запросит разрешение. Claude Code не откладывает его, пока вы вводите текст.
  • Если вы или хук PermissionRequest ответите раньше, Claude Code не запускает permission_prompt.
  • Задайте CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS значение 1, чтобы отключить permission_prompt в этих сессиях.

До v2.1.233 permission_prompt в этих сессиях не срабатывал.

Используйте отдельные matcher, чтобы запускать разные обработчики в зависимости от типа уведомления. Эта конфигурация запускает скрипт оповещения о разрешениях, когда Claude нужно подтверждение разрешения, и другое уведомление, когда Claude простаивает:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/permission-alert.sh"
          }
        ]
      },
      {
        "matcher": "idle_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/idle-notification.sh"
          }
        ]
      }
    ]
  }
}

Входные данные Notification

Помимо общих входных полей, хуки Notification получают message с текстом уведомления, необязательный title и notification_type, указывающий, какой тип сработал.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Notification",
  "message": "Claude needs your permission",
  "title": "Permission needed",
  "notification_type": "permission_prompt"
}

Хуки Notification не могут блокировать или изменять уведомления. Claude Code отбрасывает их поля systemMessage и continue, но по-прежнему выводит terminalSequence, на чём основан пример с уведомлением рабочего стола. Хуки Notification предназначены для побочных действий, например для пересылки уведомления во внешний сервис.

SubagentStart

Выполняется, когда Claude запускает субагента с помощью инструмента Agent, когда Claude возобновляет субагента, а также каждый раз, когда внутрипроцессный участник команды агентов обрабатывает новое сообщение. Поддерживает matcher для фильтрации по имени типа агента. Для встроенных агентов это имя агента, например general-purpose, Explore или Plan. Для пользовательских субагентов это поле name из frontmatter агента, а не имя файла.

Для субагентов, поставляемых плагином, тип агента — это идентификатор в области плагина, например my-plugin:reviewer, а не просто имя из frontmatter. Двоеточие переводит имя в области плагина на путь регулярных выражений, поэтому для точного совпадения закрепите matcher с помощью ^ и $: ^my-plugin:reviewer$.

Входные данные SubagentStart

Помимо общих входных полей, хуки SubagentStart получают agent_id с уникальным идентификатором субагента и agent_type с именем агента, по которому фильтрует matcher.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SubagentStart",
  "agent_id": "agent-abc123",
  "agent_type": "Explore"
}

Хуки SubagentStart не могут блокировать создание субагента, но могут внедрять контекст в субагента. Помимо выходных полей JSON, доступных всем хукам, вы можете вернуть:

Поле Описание
additionalContext Строка, добавляемая в контекст субагента в начале его диалога, перед его первым промптом. См. Добавление контекста для Claude
{
  "hookSpecificOutput": {
    "hookEventName": "SubagentStart",
    "additionalContext": "Follow security guidelines for this task"
  }
}

Когда хук снова запускается для того же субагента, Claude Code внедряет возвращённый контекст только в том случае, если контекст субагента ещё не содержит копию из предыдущего запуска. Копия, внедрённая при запуске, остаётся на месте, сохраняя кэш промптов субагента нетронутым. После того как автосжатие отбросит эту копию, Claude Code снова внедряет контекст следующего запуска.

SubagentStop

Выполняется, когда субагент Claude Code закончил отвечать. Сопоставляется по типу агента, значения те же, что и для SubagentStart.

Входные данные SubagentStop

Помимо общих входных полей, хуки SubagentStop получают stop_hook_active, agent_id, agent_type, agent_transcript_path и last_assistant_message. Поле agent_type — это значение, используемое для фильтрации matcher. transcript_path — это транскрипт основной сессии, а agent_transcript_path — собственный транскрипт субагента, хранящийся во вложенной папке subagents/. Поле last_assistant_message содержит текстовое содержимое финального ответа субагента, поэтому хуки могут получить к нему доступ без разбора файла транскрипта.

Не каждое событие SubagentStop исходит от субагента, запущенного Claude. Claude Code также запускает внутренних агентов для некоторых собственных функций, таких как предложения промптов и побочные вопросы /btw, и SubagentStop срабатывает и при завершении одного из них. Для таких событий agent_type — это имя агента, от имени которого работает сама сессия, например заданное с помощью --agent или настройки agent, и пустая строка, если сессия работает без него.

matcher, в котором указаны типы агентов, не соответствует пустому agent_type. Хук, у которого matcher опущен, равен "" или "*" либо является регулярным выражением, соответствующим пустой строке, запускается и для событий с пустым agent_type.

В Claude Code v2.1.271 или новее субагент, работающий с инструментом SubagentHandback, доставляет свой отчёт через этот инструмент перед остановкой. Поле last_assistant_message в этом случае содержит заключительный текст субагента, если он есть, и это не доставленный отчёт. Отчёт — это входное поле message этого вызова, которое хук PreToolUse или PostToolUse с matcher SubagentHandback получает как tool_input.message.

Хуки SubagentStop также получают массивы background_tasks и session_crons, описанные в разделе Входные данные Stop. Оба массива относятся к родительской сессии, а не к субагенту.

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../abc123.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "SubagentStop",
  "stop_hook_active": false,
  "agent_id": "def456",
  "agent_type": "Explore",
  "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
  "last_assistant_message": "Analysis complete. Found 3 potential issues...",
  "background_tasks": [],
  "session_crons": []
}

Хуки SubagentStop используют тот же формат управления решениями, что и хуки Stop, включая hookSpecificOutput.additionalContext с hookEventName, равным "SubagentStop", для обратной связи без ошибки, которая продолжает работу субагента. Возврат decision: "block" с reason продолжает работу субагента и передаёт ему reason в качестве следующей инструкции. Хук, который блокирует, завершаясь с кодом 2, передаёт своё сообщение из stderr тем же способом. Чтобы внедрить контекст в родительскую сессию после возврата субагента, используйте вместо этого хук PostToolUse для инструмента Agent.

TaskCreated

Выполняется, когда задача создаётся с помощью инструмента TaskCreate. Используйте его, чтобы обеспечивать соблюдение соглашений об именовании, требовать описания задач или предотвращать создание определённых задач. В сессии без инструментов Task это событие не срабатывает.

Хуки TaskCreated не поддерживают matcher и срабатывают при каждом возникновении события.

Входные данные TaskCreated

Помимо общих входных полей, хуки TaskCreated получают task_id, task_subject и, при наличии, task_description, teammate_name и team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "TaskCreated",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "session-a1b2c3d4"
}
Поле Описание
task_id Идентификатор создаваемой задачи
task_subject Заголовок задачи
task_description Подробное описание задачи. Может отсутствовать
teammate_name Имя участника команды, создающего задачу. Может отсутствовать
team_name Устаревшее. Имя команды, производное от сессии; будет удалено в одном из будущих выпусков
agent_id В этом событии общее входное поле идентифицирует субагента или внутрипроцессного участника команды, создающего задачу. Может отсутствовать. Требуется Claude Code v2.1.290 или новее

Управление решениями TaskCreated

Хук TaskCreated может заблокировать создание двумя способами. В любом случае Claude Code удаляет задачу и возвращает ваше сообщение Claude в качестве ошибки инструмента. Claude Code игнорирует continue: false от этого события, и Claude продолжает работу.

  • Код выхода 2: Claude Code возвращает текст из stderr в качестве сообщения.
  • JSON {"decision": "block", "reason": "..."}: Claude Code возвращает reason в качестве сообщения.

В этом примере блокируются задачи, заголовки которых не соответствуют требуемому формату:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
  echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
  exit 2
fi

exit 0

TaskCompleted

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

Хуки TaskCompleted не поддерживают matcher и срабатывают при каждом возникновении события.

Входные данные TaskCompleted

Помимо общих входных полей, хуки TaskCompleted получают task_id, task_subject и, при наличии, task_description, teammate_name и team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TaskCompleted",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "session-a1b2c3d4"
}
Поле Описание
task_id Идентификатор завершаемой задачи
task_subject Заголовок задачи
task_description Подробное описание задачи. Может отсутствовать
teammate_name Имя участника команды, завершающего задачу. Может отсутствовать
team_name Устаревшее. Имя команды, производное от сессии; будет удалено в одном из будущих выпусков
agent_id В этом событии общее входное поле идентифицирует субагента или внутрипроцессного участника команды, завершающего задачу. Может отсутствовать. Требуется Claude Code v2.1.290 или новее

Управление решениями TaskCompleted

Хуки TaskCompleted поддерживают два способа управления завершением задачи:

  • Код выхода 2: задача не помечается как выполненная, а сообщение из stderr передаётся модели в качестве обратной связи.
  • JSON {"continue": false, "stopReason": "..."}: когда событие вызвано завершением хода участника команды, полностью останавливает участника команды, аналогично поведению хука Stop. stopReason показывается пользователю. Когда событие вызвано инструментом TaskUpdate, Claude Code игнорирует continue: false; код выхода 2 по-прежнему блокирует завершение.

В этом примере запускаются тесты и блокируется завершение задачи, если они не проходят:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

# Run the test suite
if ! npm test 2>&1; then
  echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
  exit 2
fi

exit 0

Stop

Выполняется, когда основной агент Claude Code закончил отвечать. Не выполняется, если остановка произошла из-за прерывания пользователем. Ошибки API вместо этого вызывают StopFailure.

Входные данные Stop

Помимо общих входных полей, хуки Stop получают stop_hook_active, last_assistant_message, background_tasks и session_crons. Поле stop_hook_active равно true, когда Claude Code уже продолжает работу в результате хука остановки. Проверяйте это значение или обрабатывайте транскрипт, чтобы избежать блокировки по условию, которое никогда не будет выполнено.

Claude Code применяет ограничение в 8 последовательных продолжений: после того как хуки остановки продолжили ход восемь раз подряд, Claude Code переопределяет следующую блокировку и завершает ход. Счётчик последовательных продолжений сбрасывается каждый раз, когда Claude вызывает инструмент. Чтобы повысить ограничение, задайте CLAUDE_CODE_STOP_HOOK_BLOCK_CAP.

Поле last_assistant_message содержит текстовое содержимое финального ответа Claude, поэтому хуки могут получить к нему доступ без разбора файла транскрипта. Для хуков, работающих с только что завершённым ходом, например хуков чтения вслух или уведомлений, используйте это поле, а не чтение transcript_path: не во всех версиях гарантируется, что файл транскрипта содержит финальное сообщение в момент Stop.

Массивы background_tasks и session_crons позволяют хукам отличать ситуацию «сессия завершена» от ситуации «сессия приостановлена в ожидании фоновой работы, которая её снова разбудит». Оба массива присутствуют, когда реестр задач доступен, и пусты, когда ничего не выполняется и не запланировано.

Каждая запись в background_tasks описывает одну выполняемую задачу и использует следующие поля:

Поле Описание
id Идентификатор задачи
type Понятная метка типа задачи, например shell, subagent, monitor, workflow, teammate, cloud session или MCP task. Каждая метка указывает, какая функция Claude Code создала задачу. Для нераспознанных типов используется исходный дискриминант
status Текущий статус задачи
description Описание в свободной форме, ограниченное 1000 символами, с маркером … [+N chars] внутри строки при обрезке
command Командная строка оболочки, ограниченная 1000 символами. Присутствует только для задач shell
agent_type Имя типа субагента. Присутствует только для задач subagent
server Имя MCP-сервера. Присутствует только для задач monitor и MCP task
tool Имя инструмента MCP. Присутствует только для задач monitor и MCP task
name Имя workflow. Присутствует только для задач workflow

Каждая запись в session_crons описывает одно запланированное пробуждение в рамках сессии, полученное из CronCreate, ScheduleWakeup и /loop:

Поле Описание
id Идентификатор задачи cron
schedule Выражение cron, например 0 9 * * 1-5
recurring false для одноразовых пробуждений, расписание которых задаёт одно время срабатывания, true для задач, срабатывающих повторно при каждом совпадении
prompt Промпт, отправляемый при срабатывании cron, ограниченный 1000 символами с тем же маркером … [+N chars]

В этом примере показаны входные данные Stop с одной выполняемой задачей оболочки и одним повторяющимся cron:

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "Stop",
  "stop_hook_active": true,
  "last_assistant_message": "I've completed the refactoring. Here's a summary...",
  "background_tasks": [
    {
      "id": "task-001",
      "type": "shell",
      "status": "running",
      "description": "tail logs",
      "command": "tail -f /var/log/syslog"
    }
  ],
  "session_crons": [
    {
      "id": "cron-001",
      "schedule": "0 9 * * 1-5",
      "recurring": true,
      "prompt": "check the build"
    }
  ]
}

Управление решениями Stop

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

Поле Описание
decision "block" не даёт Claude остановиться. Опустите, чтобы разрешить Claude остановиться
reason Обязательно, когда decision равно "block". Сообщает Claude, почему ему следует продолжить
hookSpecificOutput.additionalContext Обратная связь для Claude, не являющаяся ошибкой. Диалог продолжается, чтобы Claude мог на неё отреагировать, но, в отличие от decision: "block", в транскрипте она отображается как обратная связь хука, а не как ошибка хука

Хук, который блокирует, завершаясь с кодом 2, обрабатывается так же, как reason: Claude получает сообщение из stderr в качестве пояснения, почему ему следует продолжить.

{
  "decision": "block",
  "reason": "Must be provided when Claude is blocked from stopping"
}

Используйте additionalContext, когда хук работает как задумано и даёт Claude указания, например «запусти набор тестов перед завершением». Он продолжает диалог с теми же защитами от зацикливания, что и decision: "block", а именно входным полем stop_hook_active и ограничением в 8 последовательных продолжений, но транскрипт помечает его как Stop hook feedback, и уведомление об ошибке хука не показывается:

{
  "hookSpecificOutput": {
    "hookEventName": "Stop",
    "additionalContext": "Please run the test suite before finishing"
  }
}

StopFailure

Выполняется вместо Stop, когда ход завершается из-за ошибки API. Claude Code игнорирует вывод и код выхода хука, за исключением terminalSequence. Используйте его, чтобы логировать сбои, отправлять оповещения или выполнять действия по восстановлению, когда Claude не может завершить ответ из-за ограничений частоты запросов, проблем с аутентификацией или других ошибок API.

Входные данные StopFailure

Помимо общих входных полей, хуки StopFailure получают error, необязательное error_details и необязательное last_assistant_message. Поле error определяет тип ошибки и используется для фильтрации matcher.

Поле Описание
error Тип ошибки: rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, account_on_hold, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, cloud_credential_error или unknown
error_details Дополнительные сведения об ошибке, если они доступны
last_assistant_message Отрисованный текст ошибки, показанный в диалоге. В отличие от Stop и SubagentStop, где это поле содержит разговорный вывод Claude, для StopFailure оно содержит саму строку ошибки API, например "API Error: Rate limit reached"
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "StopFailure",
  "error": "rate_limit",
  "error_details": "429 Too Many Requests",
  "last_assistant_message": "API Error: Rate limit reached"
}

У хуков StopFailure нет управления решениями. Они выполняются только для уведомлений и логирования.

TeammateIdle

Выполняется, когда участник команды агентов вот-вот перейдёт в режим простоя после завершения своего хода. Используйте его, чтобы обеспечивать контроль качества до того, как участник команды прекратит работу, например требовать прохождения проверок линтера или проверять наличие выходных файлов.

Хуки TeammateIdle не поддерживают matcher и срабатывают при каждом возникновении события.

Входные данные TeammateIdle

Помимо общих входных полей, хуки TeammateIdle получают teammate_name и team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TeammateIdle",
  "teammate_name": "researcher",
  "team_name": "session-a1b2c3d4"
}
Поле Описание
teammate_name Имя участника команды, который вот-вот перейдёт в режим простоя
team_name Устаревшее. Имя команды, производное от сессии; будет удалено в одном из будущих выпусков
agent_id В этом событии общее входное поле идентифицирует внутрипроцессного участника команды, который вот-вот перейдёт в режим простоя. Может отсутствовать. Требуется Claude Code v2.1.290 или новее

Управление решениями TeammateIdle

Хуки TeammateIdle поддерживают два способа управления поведением участника команды:

  • Код выхода 2: участник команды получает сообщение из stderr в качестве обратной связи и продолжает работу вместо перехода в режим простоя.
  • JSON {"continue": false, "stopReason": "..."}: полностью останавливает участника команды, аналогично поведению хука Stop. stopReason показывается пользователю.

В этом примере проверяется наличие артефакта сборки, прежде чем разрешить участнику команды перейти в режим простоя:

#!/bin/bash

if [ ! -f "./dist/output.js" ]; then
  echo "Build artifact missing. Run the build before stopping." >&2
  exit 2
fi

exit 0

ConfigChange

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

Claude Code запускает хуки ConfigChange, когда изменяется файл настроек, файл управляемой политики или файл скилла. Для управляемой политики он запускает их только при изменении managed-settings.json или файла в managed-settings.d/. Настройки, управляемые сервером, и изменения управляемых настроек macOS или политики реестра Windows он применяет без запуска хуков. В WSL с wslInheritsWindowsSettings он также применяет изменённый файл управляемых настроек на стороне Windows при опросе политики без запуска хуков.

Matcher фильтрует по источнику конфигурации:

Matcher Когда срабатывает
user_settings Изменяется ~/.claude/settings.json
project_settings Изменяется .claude/settings.json
local_settings Изменяется .claude/settings.local.json
policy_settings Изменяется managed-settings.json или файл в managed-settings.d/
skills Изменяется файл скилла в .claude/skills/

В этом примере все изменения конфигурации записываются в журнал для аудита безопасности:

{
  "hooks": {
    "ConfigChange": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/audit-config-change.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

Входные данные ConfigChange

Помимо общих входных полей, хуки ConfigChange получают source и, при наличии, file_path. Поле source указывает, какой тип конфигурации изменился, а file_path содержит путь к конкретному изменённому файлу.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "ConfigChange",
  "source": "project_settings",
  "file_path": "/Users/.../my-project/.claude/settings.json"
}

Управление решениями ConfigChange

Хуки ConfigChange могут блокировать вступление изменений конфигурации в силу. Используйте код выхода 2 или JSON-поле decision, чтобы предотвратить изменение. При блокировке новые настройки не применяются к текущей сессии.

Поле Описание
decision "block" предотвращает применение изменения конфигурации. Опустите, чтобы разрешить изменение
reason Принимается, но никогда не показывается
{
  "decision": "block",
  "reason": "Configuration changes to project settings require admin approval"
}

Изменения policy_settings нельзя заблокировать. Хуки по-прежнему срабатывают для источников policy_settings, когда изменяется файл управляемых настроек на компьютере, поэтому вы можете использовать их для записи этих изменений в журнал, но любое решение о блокировке игнорируется. Это гарантирует, что настройки, управляемые организацией, всегда вступают в силу. Claude Code не запускает хуки ConfigChange, когда настройки, управляемые сервером, поступают или обновляются.

Claude Code учитывает решение о блокировке из JSON-вывода хука ConfigChange и отбрасывает systemMessage и continue. Заблокированное изменение не выводит никакого сообщения ни вам, ни Claude, независимо от того, блокируете ли вы с помощью reason или через stderr с кодом выхода 2. Claude Code лишь записывает строку в отладочный лог.

CwdChanged

Выполняется, когда shell-команда в основном диалоге изменяет рабочий каталог, например когда Claude выполняет команду cd. Используйте его, чтобы реагировать на смену каталога: перезагружать переменные окружения, активировать наборы инструментов для конкретного проекта или автоматически запускать скрипты настройки. Работает в паре с FileChanged для таких инструментов, как direnv, которые управляют окружением для каждого каталога.

Хуки CwdChanged имеют доступ к CLAUDE_ENV_FILE. Переменные, записанные в этот файл, сохраняются для последующих команд Bash до следующего события CwdChanged, когда Claude Code их очищает.

CwdChanged не поддерживает matcher и срабатывает при каждом возникновении события.

Входные данные CwdChanged

Помимо общих входных полей, хуки CwdChanged получают old_cwd и new_cwd.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project/src",
  "hook_event_name": "CwdChanged",
  "old_cwd": "/Users/my-project",
  "new_cwd": "/Users/my-project/src"
}

Вывод CwdChanged

Помимо выходных полей JSON, доступных всем хукам, хуки CwdChanged могут возвращать watchPaths, чтобы динамически задавать, за какими путями к файлам следит FileChanged:

Поле Описание
watchPaths Массив абсолютных путей. Заменяет текущий динамический список наблюдения. За путями из вашей конфигурации matcher наблюдение ведётся всегда. Возврат пустого массива очищает динамический список, что типично при входе в новый каталог

У хуков CwdChanged нет управления решениями. Они не могут заблокировать смену каталога.

Claude Code считывает watchPaths и systemMessage из их JSON-вывода и отбрасывает continue. В интерактивных сессиях он показывает systemMessage как короткое уведомление в терминале. Сообщение не попадает в поток сообщений SDK.

DirectoryAdded

Выполняется после того, как вы добавляете рабочий каталог посреди сессии с помощью команды /add-dir или после того, как клиент SDK добавляет его с помощью управляющего запроса register_repo_root. Используйте его, чтобы подготовить только что добавленный репозиторий, например установить его зависимости.

Claude Code не вызывает это событие, когда:

  • Вы передаёте каталог с помощью флага запуска --add-dir; такие каталоги покрывает SessionStart
  • Вы добавляете каталог на вкладке Workspace в /permissions
  • Вы добавляете каталог, который уже является рабочим каталогом или находится внутри него

Claude Code вызывает DirectoryAdded после обновления состояния песочницы и разрешений, поэтому инструменты в песочнице уже видят новый каталог, когда запускается ваш хук. Сами команды хуков выполняются вне песочницы.

Claude Code не ждёт хук: добавление завершается немедленно, а хук выполняется в фоне с таймаутом по умолчанию 600 секунд.

Matcher фильтрует по способу добавления каталога:

Matcher Когда срабатывает
slash_command Вы добавляете каталог с помощью /add-dir
register_repo_root Клиент SDK добавляет каталог с помощью управляющего запроса register_repo_root

Входные данные DirectoryAdded

Помимо общих входных полей, хуки DirectoryAdded получают directory и source.

Поле Описание
directory Абсолютный путь к добавленному каталогу
source Способ добавления каталога: "slash_command" для /add-dir или "register_repo_root" для управляющего запроса SDK
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "DirectoryAdded",
  "directory": "/Users/my-other-repo",
  "source": "slash_command"
}

У хуков DirectoryAdded нет управления решениями. Они не могут заблокировать добавление, которое уже завершено к моменту запуска хука. Claude Code отбрасывает поле continue из их JSON-вывода, а остальное выводит по-разному в зависимости от источника:

  • slash_command: Claude Code передаёт systemMessage хука Claude в качестве контекста на следующем ходе диалога, а не показывает его вам. Количество неудавшихся хуков отображается в транскрипте. Полный вывод сбоев записывается в отладочный лог
  • register_repo_root: Claude Code записывает вывод systemMessage и вывод сбоев только в отладочный лог

FileChanged

Выполняется, когда отслеживаемый файл изменяется на диске. Claude Code обнаруживает изменения с помощью наблюдателя файловой системы, а не путём проверки вызовов инструментов, поэтому он запускает хук независимо от того, что изменило файл: вызов инструмента Edit или Write, скрипт, который Claude запускает с помощью Bash, или процесс полностью вне Claude Code. Типичное применение — перезагрузка переменных окружения при изменении файлов конфигурации проекта.

matcher для этого события выполняет две роли:

  • Построение списка наблюдения: значение разбивается по |, и каждый сегмент регистрируется как буквальное имя файла в рабочем каталоге, поэтому ".envrc|.env" отслеживает ровно эти два файла. Шаблоны регулярных выражений здесь бесполезны: значение вроде ^\.env будет отслеживать файл с буквальным именем ^\.env.
  • Фильтрация запускаемых хуков: когда отслеживаемый файл изменяется, то же значение фильтрует, какие группы хуков запускаются, по стандартным правилам matcher относительно базового имени изменённого файла.

В этом примере нормализуются окончания строк в data.csv после любого изменения, включая перезапись файла командой Bash или внешним скриптом:

{
  "hooks": {
    "FileChanged": [
      {
        "matcher": "data.csv",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/normalize-line-endings.sh"
          }
        ]
      }
    ]
  }
}

Хук считывает абсолютный путь к изменённому файлу из поля file_path входных данных JSON в stdin. Его проверка с помощью grep ищет то же, что удаляет perl, — CR в конце строки, поэтому запуск после нормализации завершается, не трогая файл. Менее строгая проверка приводит к бесконечному циклу, потому что perl -i перезаписывает файл, даже если ничего не заменяет, а Claude Code снова запускает хук после каждой перезаписи. Сохраните этот скрипт в /path/to/normalize-line-endings.sh и сделайте его исполняемым:

#!/bin/bash
FILE=$(jq -r .file_path)
if grep -q $'\r$' "$FILE"; then
  perl -pi -e 's/\r$//' "$FILE"
fi

Чтобы убедиться, что хук работает, попросите Claude дописать строку с CRLF в data.csv с помощью команды Bash. Claude Code запустит хук, и в итоге файл будет содержать окончания строк LF.

Чтобы отслеживать файлы, которые нельзя назвать заранее, возвращайте из хука watchPaths для динамического обновления списка наблюдения. Claude Code запускает наблюдателя только тогда, когда что-то называет файл для отслеживания, поэтому заполните список группой FileChanged, matcher которой называет хотя бы один файл, или хуком SessionStart либо CwdChanged, который возвращает watchPaths. Matcher по-прежнему фильтрует, какие группы хуков запускаются при изменении отслеживаемого файла, поэтому для группы, обрабатывающей динамические пути, опустите matcher: такой matcher соответствует каждому отслеживаемому файлу и ничего не добавляет в список наблюдения. Matcher "*" также соответствует каждому файлу, но Claude Code регистрирует его в списке наблюдения, как и любое другое значение, — как буквальный файл с именем *.

Хуки FileChanged имеют доступ к CLAUDE_ENV_FILE. Переменные, записанные в этот файл, сохраняются для последующих команд Bash до следующего события CwdChanged, когда Claude Code их очищает.

Входные данные FileChanged

Помимо общих входных полей, хуки FileChanged получают file_path и event.

Поле Описание
file_path Абсолютный путь к изменённому файлу
event Что произошло: "change" для изменённого файла, "add" для созданного файла или "unlink" для удалённого файла
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "FileChanged",
  "file_path": "/Users/my-project/.envrc",
  "event": "change"
}

Вывод FileChanged

Помимо выходных полей JSON, доступных всем хукам, хуки FileChanged могут возвращать watchPaths, чтобы динамически обновлять, за какими путями к файлам ведётся наблюдение:

Поле Описание
watchPaths Массив абсолютных путей. Заменяет текущий динамический список наблюдения. За путями из вашей конфигурации matcher наблюдение ведётся всегда. Используйте это, когда ваш скрипт хука обнаруживает дополнительные файлы для отслеживания на основе изменённого файла

У хуков FileChanged нет управления решениями. Они не могут предотвратить изменение файла.

Claude Code считывает watchPaths и systemMessage из их JSON-вывода и отбрасывает continue. В интерактивных сессиях он показывает systemMessage как короткое уведомление в терминале. Сообщение не попадает в поток сообщений SDK.

WorktreeCreate

Запускается при создании worktree — будь то через claude --worktree, для субагента, использующего isolation: "worktree", или для фоновой сессии, которую Claude Code изолирует в собственном worktree. По умолчанию Claude Code создаёт изолированную рабочую копию с помощью git worktree. Настройка хука WorktreeCreate заменяет это стандартное поведение Git, позволяя использовать другую систему контроля версий, например SVN, Perforce или Mercurial.

Поскольку хук полностью заменяет стандартное поведение, .worktreeinclude не обрабатывается. Если вам нужно скопировать в новый worktree локальные файлы конфигурации, например .env, сделайте это внутри скрипта хука.

Хук должен вернуть путь к каталогу созданного worktree. Claude Code использует этот путь как рабочий каталог изолированной сессии. О том, как каждый тип хука возвращает путь, см. в разделе Вывод WorktreeCreate.

Claude Code учитывает успешность выполнения хука и возвращённый путь, а systemMessage и continue отбрасывает.

Этот пример создаёт рабочую копию SVN и выводит путь, который будет использовать Claude Code. Замените URL репозитория на свой:

{
  "hooks": {
    "WorktreeCreate": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
          }
        ]
      }
    ]
  }
}

Хук считывает name worktree из входных данных JSON в stdin, извлекает свежую копию в новый каталог и выводит путь к каталогу. Именно echo в последней строке Claude Code считывает как путь к worktree. Перенаправляйте любой другой вывод в stderr, чтобы он не мешал пути.

Входные данные WorktreeCreate

Помимо общих входных полей, хуки WorktreeCreate получают поле name. Это идентификатор-слаг нового worktree, заданный пользователем или сгенерированный автоматически, например bold-oak-a3f2.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "WorktreeCreate",
  "name": "feature-auth"
}

Вывод WorktreeCreate

Хуки WorktreeCreate не используют стандартную модель решений «разрешить/заблокировать». Вместо этого результат определяется успехом или неудачей хука. Хук должен вернуть путь к каталогу созданного worktree:

  • Командные хуки (type: "command"): выведите путь последней непустой строкой stdout. Claude Code удаляет управляющие последовательности ANSI перед чтением этой строки, поэтому баннеры запуска оболочки, выведенные до вашего echo, игнорируются. Перенаправляйте любой другой вывод хука в stderr.
  • HTTP-хуки (type: "http"): верните { "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } } в теле ответа.

Если хук завершается с ошибкой или не выдаёт путь, создание worktree завершается ошибкой.

Claude Code разрешает относительный путь относительно каталога, в котором выполнялся хук, сворачивая в нём любые сегменты . или ... Если полученный путь не является каталогом, в который Claude Code может перейти, сессия выводит ошибку с указанием пути и завершается с кодом выхода 1.

Claude Code отклоняет абсолютный путь, содержащий сегменты . или .., а также любой путь, проходящий через символическую ссылку ниже корня репозитория, поскольку символическая ссылка, закоммиченная в репозиторий, может перенаправить worktree за его пределы. В ошибке указывается отклонённый компонент. Возвращайте нормализованный путь, который не проходит через символическую ссылку внутри репозитория. До v2.1.216 создание worktree следовало пути хука без такой проверки.

WorktreeRemove

Запускается, когда Claude Code очищает worktree, созданный вашим хуком WorktreeCreate. Событие срабатывает, когда:

  • Вы выходите из интерактивной сессии в worktree и соглашаетесь удалить worktree, когда Claude Code спрашивает об этом
  • Вы выходите из интерактивной сессии в worktree, которой не дали имя, Claude Code не находит изменённых или неотслеживаемых файлов и удаляет worktree, не спрашивая вас
  • Вы удаляете фоновую сессию, которая работает в этом worktree

Claude Code ищет изменённые или неотслеживаемые файлы с помощью Git, поэтому не находит их в worktree, который не является Git-checkout и не находится внутри него, даже если в каталоге есть незакоммиченная работа. Проверяйте наличие такой работы в хуке WorktreeRemove, прежде чем он что-либо удалит.

Для worktree на основе Git Claude Code выполняет очистку автоматически с помощью git worktree remove. Если вы настроили хук WorktreeCreate, используйте вместе с ним хук WorktreeRemove, чтобы управлять очисткой создаваемых им worktree:

  • Нет хука WorktreeRemove: когда Claude Code удаляет worktree при выходе из сессии в worktree, в качестве резервного варианта он выполняет git worktree remove --force для пути, возвращённого вашим хуком WorktreeCreate, поэтому worktree, который распознаёт Git, удаляется. Worktree, который Git не распознаёт, например созданный вашим хуком с помощью системы контроля версий, отличной от Git, остаётся на диске. О том, что удаление фоновой сессии делает с worktree, созданным хуком, см. правила удаления в agent view.
  • Хук завершается с кодом 0: worktree считается удалённым. Claude Code больше ничего не считывает из хука, поэтому убедитесь, что ваш хук удалил каталог.
  • Хук завершается с ненулевым кодом: удаление завершается неудачей, если каталог по пути worktree_path после этого всё ещё существует, и worktree остаётся на диске без резервного варианта через Git. Хук, который удалил каталог перед завершением с ненулевым кодом, считается успешно удалившим worktree. О том, как сообщается об ошибке, см. в разделе Входные данные WorktreeRemove.

Claude Code никогда не удаляет ветку, принадлежащую worktree, созданному хуком, поскольку знает только путь, возвращённый вашим хуком WorktreeCreate. Если ваш хук WorktreeCreate создаёт ветку, удаляйте её в хуке WorktreeRemove.

Claude Code отбрасывает поля вывода JSON хука WorktreeRemove, такие как systemMessage и continue.

При удалении фоновой сессии Claude Code проверяет сохранённый путь worktree перед запуском хука и отклоняет путь, который является символической ссылкой или проходит через неё ниже корня репозитория. Для worktree, который всё ещё содержит файлы, хук запускается только при подтверждении удаления в agent view; для такого worktree claude rm вместо этого сохраняет сессию и worktree. До v2.1.216 хук запускался для сохранённого пути без этих проверок.

Claude Code передаёт путь, возвращённый WorktreeCreate, как worktree_path во входных данных хука. Этот пример считывает этот путь и удаляет каталог:

{
  "hooks": {
    "WorktreeRemove": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
          }
        ]
      }
    ]
  }
}

Входные данные WorktreeRemove

Помимо общих входных полей, хуки WorktreeRemove получают поле worktree_path — абсолютный путь к удаляемому worktree.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "WorktreeRemove",
  "worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
}

Результат определяется кодом выхода хука WorktreeRemove. Когда хук завершается с ненулевым кодом и каталог по пути worktree_path после этого всё ещё существует, удаление завершается неудачей:

  • Worktree остаётся на диске, а команда хука и его stderr попадают в лог отладки.
  • Если вы удаляли фоновую сессию, сессия тоже остаётся. Сообщение об отказе в agent view сообщает, как завершился хук, например exited 1, цитирует начало его stderr и указывает, удалит ли повторное удаление сессии каталог в любом случае.

PreCompact

Запускается перед тем, как Claude Code выполнит операцию сжатия контекста.

Значение matcher указывает, было ли сжатие запущено вручную или автоматически:

Matcher Когда срабатывает
manual /compact
auto Автосжатие, когда диалог достигает окна автосжатия

Завершитесь с кодом 2, чтобы заблокировать сжатие. Для ручного /compact сообщение из stderr показывается пользователю. Также можно заблокировать сжатие, вернув JSON с "decision": "block".

Блокировка автоматического сжатия действует по-разному в зависимости от момента срабатывания. Если сжатие было запущено заранее, до достижения лимита контекста, Claude Code пропускает его, и диалог продолжается без сжатия. Если сжатие было запущено для восстановления после ошибки лимита контекста, уже возвращённой API, исходная ошибка отображается, и текущий запрос завершается неудачей.

Claude Code отбрасывает поля systemMessage и continue хука PreCompact.

Входные данные PreCompact

Помимо общих входных полей, хуки PreCompact получают trigger и custom_instructions. Для manual поле custom_instructions содержит то, что пользователь передаёт в /compact, и равно null, если он ничего не передаёт. Для auto поле custom_instructions равно null.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreCompact",
  "trigger": "manual",
  "custom_instructions": null
}

PostCompact

Запускается после того, как Claude Code завершает операцию сжатия контекста. Используйте это событие, чтобы реагировать на новое сжатое состояние, например чтобы записать сгенерированную сводку в лог или обновить внешнее состояние. Claude Code отбрасывает поля systemMessage и continue хука PostCompact.

Применяются те же значения matcher, что и для PreCompact:

Matcher Когда срабатывает
manual После /compact
auto После автосжатия, когда диалог достигает окна автосжатия

Входные данные PostCompact

Помимо общих входных полей, хуки PostCompact получают trigger и compact_summary. Поле compact_summary содержит сводку диалога, сгенерированную операцией сжатия.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PostCompact",
  "trigger": "manual",
  "compact_summary": "Summary of the compacted conversation..."
}

Хуки PostCompact не управляют решениями. Они не могут повлиять на результат сжатия, но могут выполнять последующие задачи.

PreModelSwitch

Запускается до того, как Claude Code применит смену модели, запрошенную вами или клиентом. Используйте его, чтобы заблокировать смену, потребовать подтверждения или показать, во что обойдётся смена, до того как она произойдёт.

PreModelSwitch требует Claude Code v2.1.251 или новее. Claude Code запускает его для следующих запросов:

  • /model <name> и окно выбора /model
  • Окно выбора модели Option+P или Alt+P
  • Настройка Model в /config
  • Включение быстрого режима, когда это меняет модель сессии
  • Запрос set_model или смена модели в запросе apply_flag_settings от хоста Agent SDK или Remote Control

Claude Code не запускает хуки PreModelSwitch для смен, которые выполняет самостоятельно, например при автоматическом переключении на резервную модель или восстановлении модели при возобновлении сессии. Такие изменения доходят только до PostModelSwitch.

Claude Code сравнивает matcher с каноническим именем модели, на которую переключается сессия, игнорируя суффикс [1m]. Псевдоним, например opus, идентификатор модели с датой и идентификатор конкретного провайдера, например идентификатор модели Amazon Bedrock, — все соответствуют одному каноническому имени, к которому они разрешаются, поэтому claude-opus-5 охватывает все варианты написания Opus 5.

Когда Claude Code не может определить каноническое имя целевой модели, например для пользовательского идентификатора модели, который знает только ваш LLM-шлюз, он запускает все хуки PreModelSwitch независимо от matcher. Поэтому блокирующий хук должен проверять to_model из своих входных данных, а не полагаться только на matcher.

Записывайте matcher как точное имя, список через |, например claude-opus-4-6|claude-opus-5, или регулярное выражение, например .*opus.*. В этом примере используется matcher с точным именем, а также проверяется to_model из входных данных хука, поэтому он отклоняет переключение на Opus 4.6, завершаясь с кодом 2, и пропускает любую другую целевую модель:

Команда проверяет to_model с помощью jq:

{
"hooks": {
"PreModelSwitch": [
{
"matcher": "claude-opus-4-6",
"hooks": [
{
"type": "command",
"command": "jq -e '.to_model | test(\"opus-4-6\")' > /dev/null && { echo 'Opus 4.6 is retired for this project. Use a newer model.' >&2; exit 2; }; exit 0"
}
]
}
]
}
}

Чтобы убедиться, что хук работает, выполните /model claude-opus-4-6 в сессии, использующей другую модель. Claude Code сохранит текущую модель и сообщит, что хук PreModelSwitch заблокировал смену, указав ваше сообщение в качестве причины.

Входные данные PreModelSwitch

Помимо общих входных полей, хуки PreModelSwitch получают поля из этой таблицы. Последние пять описывают стоимость повторной отправки диалога новой модели, чтобы хук мог показать эту сумму до смены.

Поле Тип Описание
from_model string Идентификатор модели, с которой происходит смена
to_model string Идентификатор модели, на которую происходит смена. Matcher сравнивается с каноническим именем этой модели
requested_model string или null Модель, указанная в запросе: псевдоним, например opus, полный идентификатор модели или null, если запрос был на модель по умолчанию
source string Откуда пришёл запрос: "command" для /model <name>, настройки Model в /config или включения быстрого режима; "picker" для окна выбора модели; "sdk" для запроса set_model или смены модели в запросе apply_flag_settings от хоста Agent SDK или Remote Control
context_tokens number Токены, которые следующий запрос повторно отправит в качестве промпта: сумма входных токенов, токенов чтения кэша, создания кэша и выходных токенов последнего ответа в основном диалоге. 0 до первого ответа
prompt_cache_warm boolean Вероятно ли, что кэш промптов текущей модели всё ещё «тёплый», то есть смена приведёт к его потере
cache_ttl string Время жизни кэша промптов, которое Claude Code запрашивает для этой сессии: "5m" или "1h"
estimated_cache_write_usd number Оценочная стоимость в долларах США записи context_tokens в кэш промптов на to_model по тарифу cache_ttl, без учёта следующего ответа. Серверу может не понадобиться повторно кэшировать весь контекст, поэтому рассматривайте это значение как оценку
pricing string Как Claude Code рассчитал estimated_cache_write_usd: "configured" — по собственным тарифам вашей организации, если она их настроила, "catalog" — по прейскурантной цене, или "default", когда цена to_model неизвестна и Claude Code принял тариф по умолчанию

В этом примере показаны входные данные для /model opus в сессии, использующей Sonnet 5:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreModelSwitch",
  "from_model": "claude-sonnet-5",
  "to_model": "claude-opus-5",
  "requested_model": "opus",
  "source": "command",
  "context_tokens": 182340,
  "prompt_cache_warm": true,
  "cache_ttl": "5m",
  "estimated_cache_write_usd": 1.1396,
  "pricing": "catalog"
}

Управление решениями PreModelSwitch

Хуки PreModelSwitch могут отменить смену, попросить пользователя подтвердить её или позволить ей выполниться. Код выхода 2 или decision: "block" верхнего уровня отменяет смену.

Для более тонкого управления возвращайте permissionDecision и permissionDecisionReason в объекте hookSpecificOutput, как для PreToolUse. PreModelSwitch принимает "allow", "deny" и "ask". Он не принимает "defer", updatedInput или additionalContext. В таблице ниже описаны оба поля:

Поле Описание
permissionDecision "allow" выполняет смену и пропускает подтверждение, которое Claude Code показывает, пока кэш промптов «тёплый». "deny" отменяет смену. "ask" просит пользователя подтвердить её
permissionDecisionReason Для "deny" показывается пользователю как причина блокировки смены или возвращается как ошибка для запроса set_model. Для "ask" показывается в запросе подтверждения. Игнорируется для "allow"

Показать запрос "ask" может только /model в интерактивной сессии. На всех остальных интерфейсах, включая неинтерактивный режим с флагом -p, /config и запросы set_model, Claude Code рассматривает "ask" как отказ.

Этот пример просит пользователя подтвердить смену и приводит количество токенов из context_tokens:

{
  "hookSpecificOutput": {
    "hookEventName": "PreModelSwitch",
    "permissionDecision": "ask",
    "permissionDecisionReason": "Switching now re-sends about 180k tokens to the new model. Continue?"
  }
}

Когда несколько хуков PreModelSwitch возвращают разные решения, приоритет таков: deny > ask > allow.

Claude Code показывает пользователю любое systemMessage, возвращённое вашим хуком, независимо от решения, поэтому хук, сообщающий о стоимости, может вернуть {"systemMessage": "..."} и завершиться с кодом 0.

Хук PreModelSwitch, который не ответил до истечения таймаута, блокирует смену. Для PreToolUse, напротив, командный хук с истёкшим таймаутом позволяет вызову инструмента продолжиться. Таймаут по умолчанию для этого события — 30 секунд. PreModelSwitch запускает только хуки command, http и mcp_tool, поэтому значения по умолчанию для prompt и agent не применяются.

Хук, который завершается с кодом, отличным от 0 или 2, и не выводит JSON-решение, не блокирует смену: Claude Code показывает его stderr и применяет смену, как описано в разделе Другие коды выхода.

PostModelSwitch

Запускается после смены модели сессии. Используйте его, чтобы давать Claude указания для конкретной модели, не редактируя каждый CLAUDE.md, например инструкцию для всей организации, действующую для определённых моделей.

PostModelSwitch требует Claude Code v2.1.251 или новее. Он не может блокировать, поскольку модель уже сменилась. Claude Code запускает хуки PostModelSwitch после любого из следующих изменений:

Claude Code не запускает хуки PostModelSwitch, когда ход обслуживает модель из цепочки резервных моделей, поскольку такая замена длится один ход и не меняет модель сессии.

Matcher подчиняется тем же правилам, что и для PreModelSwitch: Claude Code сравнивает его с каноническим именем модели, на которую переключилась сессия.

Этот пример добавляет указания всякий раз, когда модель сессии меняется на любую модель Opus:

{
  "hooks": {
    "PostModelSwitch": [
      {
        "matcher": ".*opus.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'On Opus, delegate implementation work to subagents and keep this conversation for planning and review.'"
          }
        ]
      }
    ]
  }
}

Чтобы убедиться, что хук работает, переключитесь на модель Opus в сессии, использующей другую модель, например выполните /model opus в сессии Sonnet, а затем спросите Claude, какие указания у него есть относительно текущей модели.

Входные данные PostModelSwitch

Хуки PostModelSwitch получают те же поля, что и PreModelSwitch, с hook_event_name, равным "PostModelSwitch", и двумя дополнительными значениями source: "auto" для автоматического переключения на резервную модель или другого изменения, которое Claude Code выполнил самостоятельно, и "resume" для модели, восстановленной при возобновлении сессии.

requested_model равно null, когда source равно "auto". Когда source равно "resume", это сохранённая настройка модели, которую восстановил Claude Code.

Управление решениями PostModelSwitch

Claude Code берёт обычный текстовый stdout вашего хука при коде выхода 0 или additionalContext из вывода JSON и передаёт его Claude вместе со следующим запросом после смены. Помимо полей вывода JSON, доступных всем хукам, можно вернуть:

Поле Описание
additionalContext Строка, добавляемая в контекст Claude со следующим запросом. См. Добавление контекста для Claude

Если хук не завершился в течение пяти секунд после отправки следующего промпта, Claude Code отправляет этот запрос без вывода и прикрепляет вывод к следующему запросу. Если модель меняется несколько раз до следующего запроса, Claude Code передаёт только вывод для целевой модели последней смены.

SessionEnd

Запускается при завершении сессии Claude Code. Полезен для задач очистки, записи статистики сессии в лог или сохранения состояния сессии. Поддерживает matcher для фильтрации по причине выхода.

Поле reason во входных данных хука указывает, почему сессия завершилась:

Причина Описание
clear Сессия очищена командой /clear
resume Сессия переключена через интерактивную команду /resume
logout Пользователь вышел из системы
prompt_input_exit Пользователь вышел, когда было видно поле ввода промпта
other Другие причины выхода
bypass_permissions_disabled Удалено в v2.1.234; Claude Code его не отправляет. Уберите его из своих matcher для SessionEnd

Входные данные SessionEnd

Помимо общих входных полей, хуки SessionEnd получают поле reason, указывающее, почему сессия завершилась. Все значения см. в таблице причин выше.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionEnd",
  "reason": "other"
}

Хуки SessionEnd не управляют решениями. Они не могут заблокировать завершение сессии, но могут выполнять задачи очистки. Claude Code отбрасывает их поля вывода JSON, такие как systemMessage.

Таймаут по умолчанию для хуков SessionEnd — 1,5 секунды. Он применяется, когда вы выходите, выполняете /clear или переключаете сессии с помощью интерактивной команды /resume. Дать хуку больше времени можно двумя способами:

  • timeout для отдельного хука: задайте timeout в конфигурации этого хука. Общий лимит времени автоматически увеличивается до наибольшего значения timeout отдельного хука в ваших файлах настроек, но не более 60 секунд. Если вы увеличиваете лимит таким способом, хук без собственного timeout по-прежнему использует значение по умолчанию. Таймауты, заданные для хуков, предоставляемых плагинами, не увеличивают лимит.
  • CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS: задайте эту переменную окружения в миллисекундах, чтобы явно переопределить лимит. Заданное значение также становится таймаутом для каждого хука без собственного timeout.

Этот пример устанавливает лимит в 5 секунд:

CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude

До v2.1.268 CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS увеличивала только общий лимит, а хук без собственного timeout всё равно прерывался через 1,5 секунды.

Elicitation

Запускается, когда MCP-сервер запрашивает ввод пользователя в процессе выполнения задачи. По умолчанию Claude Code показывает интерактивное диалоговое окно, в котором пользователь может ответить. Хуки могут перехватить этот запрос и ответить программно, полностью пропустив диалоговое окно.

Полный пример хука с записью в настройках и скриптом см. в разделе Ответ на запрос формы из скрипта.

Поле matcher сопоставляется с именем MCP-сервера.

Входные данные Elicitation

Помимо общих входных полей, хуки Elicitation получают поля mcp_server_name, message и необязательные поля mode, url, elicitation_id и requested_schema.

Для запроса в режиме формы — самого распространённого случая:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Elicitation",
  "mcp_server_name": "my-mcp-server",
  "message": "Please provide your credentials",
  "mode": "form",
  "requested_schema": {
    "type": "object",
    "properties": {
      "username": { "type": "string", "title": "Username" }
    }
  }
}

Для запроса в режиме URL, используемого для аутентификации через браузер:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Elicitation",
  "mcp_server_name": "my-mcp-server",
  "message": "Please authenticate",
  "mode": "url",
  "url": "https://auth.example.com/login"
}

Вывод Elicitation

Хук Elicitation может ответить на запрос за пользователя, отклонить или отменить его либо оставить его диалоговому окну. Чтобы ответить, отклонить или отменить, завершитесь с кодом 0 и выведите объект hookSpecificOutput с action. Сервер получает ваш ответ, и диалоговое окно не появляется. Каждая строка этой таблицы показывает, что вернуть для одного исхода и что получает MCP-сервер:

Чтобы Верните Сервер получает
Ответить за пользователя "action": "accept" со значениями полей формы в content accept с вашим content
Отклонить запрос "action": "decline" decline
Отменить запрос "action": "cancel" cancel
Оставить запрос пользователю Нет вывода, код выхода 0 Ответ пользователя из диалогового окна

Этот вывод отвечает на запрос в режиме формы, показанный в разделе Входные данные Elicitation. Ключи в content — это имена свойств из requested_schema этого запроса:

{
  "hookSpecificOutput": {
    "hookEventName": "Elicitation",
    "action": "accept",
    "content": {
      "username": "alice"
    }
  }
}

Этот вывод отклоняет запрос:

{
  "hookSpecificOutput": {
    "hookEventName": "Elicitation",
    "action": "decline"
  }
}

В диалоговом окне выбор Decline отправляет decline, а нажатие Esc отправляет cancel, поэтому возвращайте то, что вы хотите, чтобы увидел сервер.

Для запроса в режиме URL хук, возвращающий accept, пропускает диалоговое окно, поэтому URL так и не открывается.

Claude Code отбрасывает reason, systemMessage и continue из вывода JSON хука Elicitation, какое бы action вы ни вернули.

Другие способы отклонить запрос elicitation

Ваш хук также может отклонить запрос следующими способами. Сервер получает тот же decline, что и при "action": "decline":

  • Завершение с кодом 2: Claude Code игнорирует hookSpecificOutput, выведенный тем же хуком
  • Вывод "decision": "block" верхнего уровня: блокировка переопределяет action в том же выводе

Когда одному запросу соответствует несколько хуков, отклонение от одного из них переопределяет accept или cancel от другого.

Этот скрипт отклоняет запросы в режиме URL и оставляет запросы форм диалоговому окну:

#!/bin/bash
if [ "$(jq -r '.mode')" = "url" ]; then
  exit 2
fi

Ни пользователь, ни сервер не видят, почему ваш хук отклонил запрос, поскольку Claude Code не показывает ваш stderr или ваш reason.

Claude Code игнорировал decision верхнего уровня от хуков Elicitation и ElicitationResult начиная с v2.1.105 и до исправления в v2.1.284.

Ответ на запрос формы из скрипта

Этот пример отвечает за пользователя на один повторяющийся вопрос. MCP-сервер с именем issue-tracker запрашивает в форме ключ проекта, а хук подставляет DOCS. Скрипт принимает запрос, когда project_key — единственное поле формы. Для любого другого запроса он ничего не выводит, поэтому появляется диалоговое окно.

Зарегистрируйте командный хук для события в файле настроек, указав имя сервера в качестве matcher:

{
"hooks": {
"Elicitation": [
{
"matcher": "issue-tracker",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/answer-project-key.sh",
"args": []
}
]
}
]
}
}

Сохраните этот скрипт в файл .claude/hooks/answer-project-key.sh в вашем проекте и сделайте его исполняемым с помощью chmod +x:

#!/bin/bash
input=$(cat)
fields=$(jq -c '.requested_schema.properties // {} | keys' <<<"$input")

if [ "$fields" = '["project_key"]' ]; then
jq -n '{hookSpecificOutput: {hookEventName: "Elicitation", action: "accept", content: {project_key: "DOCS"}}}'
fi

Чтобы убедиться, что хук работает, запустите Claude Code командой claude --debug и дайте Claude задачу, при которой сервер запросит ключ проекта. Диалоговое окно не появится, а в логе отладки будет строка, заканчивающаяся на Elicitation resolved by hook: {"action":"accept","content":{"project_key":"DOCS"}}.

ElicitationResult

Запускается после того, как пользователь ответил на запрос elicitation от MCP. Хуки могут просматривать, изменять или блокировать ответ до его отправки обратно MCP-серверу.

Когда хук Elicitation отвечает на запрос, Claude Code отправляет этот ответ серверу без запуска хуков ElicitationResult.

Поле matcher сопоставляется с именем MCP-сервера.

Входные данные ElicitationResult

Помимо общих входных полей, хуки ElicitationResult получают поля mcp_server_name, action и необязательные поля mode, elicitation_id и content.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "ElicitationResult",
  "mcp_server_name": "my-mcp-server",
  "action": "accept",
  "content": { "username": "alice" },
  "mode": "form"
}

Вывод ElicitationResult

Хук ElicitationResult может пропустить ответ пользователя, изменить его значения или заблокировать его. Чтобы изменить или заблокировать ответ, завершитесь с кодом 0 и выведите объект hookSpecificOutput с action. Каждая строка этой таблицы показывает, что вернуть для одного исхода и что получает MCP-сервер:

Чтобы Верните Сервер получает
Пропустить ответ Нет вывода, код выхода 0 Ответ пользователя без изменений
Изменить отправленные значения "action": "accept" с новыми значениями в content accept с вашим content вместо значений пользователя
Заблокировать ответ "action": "decline" decline без значений пользователя
Отменить запрос "action": "cancel" cancel вместе со значениями, отправленными пользователем. Чтобы скрыть их, верните "decline"

Этот вывод изменяет ответ, показанный в разделе Входные данные ElicitationResult, так что сервер получает alice@example.com там, где пользователь отправил alice:

{
  "hookSpecificOutput": {
    "hookEventName": "ElicitationResult",
    "action": "accept",
    "content": {
      "username": "alice@example.com"
    }
  }
}

Ваш content заменяет весь объект content пользователя, поэтому включайте поля, которые вы не меняете. Возвращайте вместе с ним action, поскольку Claude Code игнорирует hookSpecificOutput без action.

Хуки ElicitationResult также запускаются, когда пользователь отклоняет или отменяет запрос, и ваше action заменяет его действие. Прежде чем вернуть accept, проверьте, что action во входных данных равно accept, иначе ваш хук превратит отклонённый запрос в принятый. Этот скрипт вносит то же изменение, когда пользователь принял запрос, сохраняет остальные поля и в противном случае ничего не выводит:

#!/bin/bash
input=$(cat)

if [ "$(jq -r '.action' <<<"$input")" = "accept" ]; then
  jq '{hookSpecificOutput: {hookEventName: "ElicitationResult", action: "accept", content: (.content + {username: (.content.username + "@example.com")})}}' <<<"$input"
fi

Этот вывод блокирует ответ:

{
  "hookSpecificOutput": {
    "hookEventName": "ElicitationResult",
    "action": "decline"
  }
}

Код выхода 2 и "decision": "block" верхнего уровня также блокируют ответ. В разделе Другие способы отклонить запрос elicitation описано, какой из них действует, когда хук их сочетает, что видит пользователь и какие версии игнорировали decision.

Claude Code отбрасывает reason, systemMessage и continue из вывода JSON хука ElicitationResult, какое бы action вы ни вернули.

Prompt-based hooks

В дополнение к command, HTTP и MCP tool hooks, Claude Code поддерживает prompt-based hooks (type: "prompt"), которые используют LLM для оценки разрешения или блокировки действия, и agent hooks (type: "agent"), которые порождают агентного верификатора с доступом к инструментам. Не все события поддерживают каждый тип hook.

События, которые поддерживают все пять типов hook (command, http, mcp_tool, prompt и agent):

  • PermissionDenied
  • PostToolBatch
  • PostToolUse
  • PostToolUseFailure
  • PreToolUse
  • Stop
  • SubagentStop
  • TaskCompleted
  • TaskCreated
  • TeammateIdle
  • UserPromptExpansion
  • UserPromptSubmit

PermissionRequest поддерживает command, http, mcp_tool и prompt hooks, но не agent hooks. Если вы настроите agent hook на этом событии, Claude Code пропустит его и поток разрешений продолжится без изменений. Чтобы разрешить или отклонить из hook, верните объект решения из command или HTTP hook.

События, которые поддерживают command, http и mcp_tool hooks, но не prompt или agent:

  • ConfigChange
  • CwdChanged
  • DirectoryAdded
  • Elicitation
  • ElicitationResult
  • FileChanged
  • InstructionsLoaded
  • MessageDisplay
  • Notification
  • PostCompact
  • PostModelSwitch
  • PreCompact
  • PreModelSwitch
  • SessionEnd
  • StopFailure
  • SubagentStart
  • WorktreeCreate
  • WorktreeRemove

SessionStart и Setup поддерживают command и mcp_tool hooks, и MCP tool hook fields описывает, когда их mcp_tool hooks запускаются. Они не поддерживают http, prompt или agent hooks.

How prompt-based hooks work

Вместо выполнения команды Bash, prompt-based hooks:

  1. Отправляют входные данные hook и вашу подсказку модели Claude, по умолчанию той, которую Claude Code использует для фоновой функциональности
  2. LLM отвечает структурированным JSON, содержащим решение
  3. Claude Code автоматически обрабатывает решение

Prompt hook configuration

Установите type на "prompt" и предоставьте строку prompt вместо command. Используйте заполнитель $ARGUMENTS для внедрения данных JSON входа hook в текст вашей подсказки.

В prompt или agent хуке вы можете написать prompt как правило о том, что блокировать или разрешать, например «Блокировать любую команду Bash, которая читает файлы .env», или как условие, которое должно выполняться, например «Все модульные тесты проходят».

Этот hook Stop просит LLM оценить, должен ли Claude остановиться перед разрешением Claude закончить:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
          }
        ]
      }
    ]
  }
}
Поле Обязательно Описание
type да Должно быть "prompt"
prompt да Текст подсказки для отправки LLM. Используйте $ARGUMENTS как заполнитель для JSON входа hook. Если $ARGUMENTS отсутствует, JSON входа добавляется к подсказке
model нет Модель для использования при оценке. По умолчанию модель, которую Claude Code использует для фоновой функциональности
timeout нет Таймаут в секундах. По умолчанию: 30
continueOnBlock нет На событиях, к которым это применяется, true передаёт причину ok: false обратно Claude и продолжает вместо завершения хода. По умолчанию: false. См. Response schema для поведения для каждого события

Response schema

LLM должен ответить JSON, содержащим:

{
  "ok": true | false,
  "reason": "Explanation for the decision",
  "impossible": true | false
}
Поле Описание
ok true разрешает действие. Для false, см. поведение для каждого события ниже
reason Требуется при ok равном false
impossible Опционально. Модель возвращает его с ok: false, когда она судит, что условие никогда не может быть удовлетворено. На Stop и SubagentStop, Claude Code затем позволяет ходу закончиться вместо передачи причины обратно. Agent hooks и другие события игнорируют его

Что происходит при ok: false, зависит от события:

  • Stop и SubagentStop: причина передаётся обратно Claude как его следующая инструкция и ход продолжается, если только ответ также не устанавливает impossible: true, в этом случае Claude Code позволяет остановке и ход заканчивается
  • PreToolUse: вызов инструмента отклоняется; по умолчанию ход заканчивается и причина отказа появляется в чате как строка предупреждения. Установите continueOnBlock: true для возврата причины Claude как ошибки инструмента, чтобы он мог скорректировать и продолжить, эквивалентно permissionDecision: "deny" из command hook. До v2.1.210 причина отказа возвращалась Claude как ошибка инструмента и ход продолжался
  • PostToolUse: по умолчанию ход заканчивается и причина появляется в чате как строка предупреждения. Установите continueOnBlock: true для передачи причины обратно Claude и продолжения хода вместо этого
  • PostToolBatch, UserPromptSubmit и UserPromptExpansion: ход заканчивается и причина появляется как строка предупреждения. Эти события заканчивают ход на decision: "block" независимо от continue
  • PostToolUseFailure и TaskCreated: причина возвращается Claude как ошибка инструмента и ход продолжается, независимо от continueOnBlock
  • TaskCompleted: когда он срабатывает, потому что задача отмечена как завершённая во время хода, причина возвращается Claude как ошибка инструмента и ход продолжается, независимо от continueOnBlock. Когда он срабатывает, потому что товарищ по команде останавливается, он ведёт себя как TeammateIdle и останавливает товарища по команде по умолчанию
  • TeammateIdle: по умолчанию товарищ по команде останавливается и причина появляется как строка предупреждения. Установите continueOnBlock: true для передачи причины обратно товарищу по команде и продолжения его работы вместо этого
  • PermissionRequest: ok: false не имеет эффекта. Чтобы отклонить одобрение из hook, используйте command hook, возвращающий hookSpecificOutput.decision.behavior: "deny"
  • PermissionDenied: ok: false не имеет эффекта, потому что отказ уже произошёл. Единственный результат, который это событие читает, это hookSpecificOutput.retry, который prompt и agent hooks не могут установить. Они запускаются на этом событии, но их результат отбрасывается. Используйте command hook для возврата retry

Если вам нужен более точный контроль над любым событием, используйте command hook с полями для каждого события, описанными в Decision control.

Check multiple conditions before stopping

Этот hook Stop использует подробную подсказку для проверки трёх условий перед разрешением Claude остановиться. Hooks SubagentStop используют тот же формат для оценки, должен ли subagent остановиться. Если модель возвращает "ok": false, потому что условие ещё не выполнено, Claude продолжает работать с предоставленной причиной как своей следующей инструкцией:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Agent-based hooks

Agent-based hooks (type: "agent") похожи на prompt-based hooks, но с многооборотным доступом к инструментам. Вместо одного вызова LLM, agent hook порождает subagent, который может читать файлы, искать код и проверять кодовую базу для проверки условий. Agent hooks поддерживают те же события, что и prompt-based hooks, за исключением PermissionRequest.

How agent hooks work

Когда срабатывает agent hook:

  1. Claude Code порождает subagent с вашей подсказкой и JSON входом hook
  2. Subagent может использовать инструменты, такие как Read, Grep и Glob, для исследования
  3. После до 50 оборотов subagent возвращает структурированное решение { "ok": true/false }
  4. Claude Code разрешает действие, если ok имеет значение true. Если ok имеет значение false, Claude Code обрабатывает блокировку так же, как prompt hook с continueOnBlock: true на этом событии, как указано в разделе Response schema

Agent hooks полезны, когда проверка требует проверки фактических файлов или выхода тестов, а не только оценки данных входа hook.

Agent hook configuration

Установите type на "agent" и предоставьте строку prompt, используя $ARGUMENTS как заполнитель для JSON входа hook. Поля конфигурации те же, что и prompt hooks, за исключением того, что agent hooks имеют более длинный таймаут по умолчанию в 60 секунд и не имеют поля continueOnBlock.

Схема ответа — это { "ok": true } для разрешения или { "ok": false, "reason": "..." } для блокировки. При ok: false, Claude Code обрабатывает agent hook так же, как он обрабатывает prompt hook с continueOnBlock: true на том же событии; agent hooks не имеют поля continueOnBlock и не поддерживают поле impossible из prompt hook.

Этот hook Stop проверяет, что все модульные тесты проходят перед разрешением Claude закончить:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

Запуск hooks в фоне

По умолчанию hooks блокируют выполнение Claude до их завершения. Для долгоживущих задач, таких как развёртывания, наборы тестов или вызовы внешних API, установите "async": true для запуска hook в фоне, пока Claude продолжает работать. Асинхронные hooks не могут блокировать или управлять поведением Claude: поля ответа, такие как decision, permissionDecision и continue, не имеют эффекта, потому что действие, которое они контролировали, уже завершено.

Настройка асинхронного hook

Добавьте "async": true к конфигурации command hook для запуска его в фоне без блокировки Claude. Это поле доступно только на hooks type: "command".

Этот hook запускает скрипт тестирования после каждого вызова инструмента Write. Claude продолжает работать немедленно, пока run-tests.sh выполняется. Когда скрипт завершается, его выход доставляется на следующий ход разговора:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/run-tests.sh",
            "async": true
          }
        ]
      }
    ]
  }
}

Как только асинхронный hook запущен в фоне, Claude Code не применяет timeout к нему. Claude Code по-прежнему применяет timeout к hook, который вы запускаете с asyncRewake.

Claude Code доставляет результаты асинхронного hook только во время работы сеанса:

  • В неинтерактивном режиме с флагом -p Claude Code завершает любой асинхронный hook, который всё ещё работает при завершении, и завершает его с результатом cancelled
  • Если работа вашего hook должна пережить сеанс claude -p, запустите полностью отделённый процесс из него

Как выполняются асинхронные hooks

Когда срабатывает асинхронный hook, Claude Code запускает процесс hook и немедленно продолжает без ожидания его завершения. Hook получает те же JSON входные данные через stdin, что и синхронный hook.

После выхода фонового процесса Claude Code доставляет поля additionalContext и systemMessage из JSON ответа hook к Claude на следующем ходу разговора. В отличие от systemMessage синхронного hook, ни одно из этих полей не показывается вам.

Claude Code проверяет, что JSON ответ соответствует той же схеме выходных данных, что и синхронные hooks, и отбрасывает любое поле, значение которого имеет неправильный тип, например systemMessage, который не является строкой, вместо его доставки. Запустите с --debug для просмотра предупреждения, называющего каждое отброшенное поле. До версии v2.1.202 неправильно сформированный JSON выход из асинхронного hook мог привести к сбою сеанса, и сбой повторялся каждый раз при возобновлении сеанса.

Уведомления о завершении асинхронного hook подавляются по умолчанию. Чтобы их увидеть, включите подробный режим с помощью Ctrl+O или запустите Claude Code с --verbose.

Запуск тестов после изменения файлов

Этот hook запускает набор тестов в фоне всякий раз, когда Claude пишет файл, затем сообщает результаты обратно Claude при завершении тестов. Сохраните этот скрипт в .claude/hooks/run-tests-async.sh в вашем проекте и сделайте его исполняемым с помощью chmod +x:

#!/bin/bash
# run-tests-async.sh

# Read hook input from stdin
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Only run tests for source files
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
  exit 0
fi

# Run tests and report results to Claude via additionalContext
RESULT=$(npm test 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
  MSG="Tests passed after editing $FILE_PATH"
else
  MSG="Tests failed after editing $FILE_PATH: $RESULT"
fi
jq -nc --arg msg "$MSG" '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $msg}}'

Затем добавьте эту конфигурацию в .claude/settings.json в корне вашего проекта. Флаг async: true позволяет Claude продолжать работу, пока тесты запускаются:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",
            "args": [],
            "async": true
          }
        ]
      }
    ]
  }
}

Ограничения

Асинхронные hooks имеют дополнительные ограничения по сравнению с синхронными hooks:

  • Выход hook доставляется на следующий ход разговора. Если сеанс неактивен, ответ ждёт до следующего взаимодействия пользователя. Исключение: hook asyncRewake, который выходит с кодом 2, пробуждает Claude немедленно даже когда сеанс неактивен.
  • Каждое выполнение создаёт отдельный фоновый процесс. Нет дедупликации между несколькими срабатываниями одного и того же асинхронного hook.

Соображения безопасности

Отказ от ответственности

Доверие рабочей области

Claude Code проверяет доверие рабочей области перед запуском любого hook из файла параметров. Что считается доверенным, зависит от типа сеанса:

  • Интерактивный сеанс: Claude Code удерживает hooks из каждого файла параметров, включая ваш собственный ~/.claude/settings.json, пока вы не примете диалог доверия рабочей области для папки или для родительского каталога, чьё доверие распространяется на неё
  • Сеанс -p или SDK: Claude Code никогда не показывает диалог и рассматривает папку как доверенную, поэтому hooks, зафиксированные в .claude/settings.json репозитория, запускаются в папке, которой вы никогда не доверяли

Перед тем как запустить claude -p над репозиторием, который вы не писали, проверьте его файлы параметров .claude/, начните с --bare или отключите hooks для этого запуска с помощью --settings '{"disableAllHooks": true}'. Frontmatter hooks в проектном подагенте следуют более строгому правилу, чем hooks файлов параметров. Что запускается перед доверием папке перечисляет каждый вид содержимого репозитория по типу сеанса.

Лучшие практики безопасности

Помните об этих практиках при написании hooks:

  • Проверяйте и санитизируйте входные данные: никогда не доверяйте входным данным вслепую
  • Всегда заключайте переменные оболочки в кавычки: используйте "$VAR" не $VAR
  • Блокируйте обход пути: проверяйте наличие .. в путях файлов
  • Используйте абсолютные пути: указывайте полные пути для скриптов. В форме exec используйте ${CLAUDE_PROJECT_DIR} и путь не требует кавычек. В форме shell оберните его в двойные кавычки
  • Пропускайте чувствительные файлы: избегайте .env, .git/, ключей и т. д.

Windows PowerShell tool

На Windows вы можете запустить отдельные hooks в PowerShell, установив "shell": "powershell" на command hook. Claude Code автоматически обнаруживает pwsh.exe, исполняемый файл PowerShell 7 и более поздних версий, и переходит на powershell.exe для Windows PowerShell 5.1.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "shell": "powershell",
            "command": "Write-Host 'File written'"
          }
        ]
      }
    ]
  }
}

Чтобы ссылаться на корневой каталог проекта из команды PowerShell в форме shell, напишите ${CLAUDE_PROJECT_DIR} или $env:CLAUDE_PROJECT_DIR. Claude Code переписывает заполнители ${CLAUDE_PROJECT_DIR}, ${CLAUDE_PLUGIN_ROOT} и ${CLAUDE_PLUGIN_DATA} в команде PowerShell в форме shell в форму ${env:NAME} PowerShell, независимо от того, определён ли хук в settings.json, плагине или скилле. PowerShell затем разрешает значение из экспортированного окружения после анализа, поэтому заполнитель работает внутри строк в двойных кавычках, но не внутри строк в одинарных кавычках, где PowerShell никогда не расширяет переменные.

Не пишите простое написание $CLAUDE_PROJECT_DIR в hook PowerShell. PowerShell анализирует его как неопределённую локальную переменную и разрешает её в $null, что оставляет путь скрипта без префикса корневого каталога проекта. Claude Code не переписывает эту форму; вместо этого она регистрирует предупреждение в debug log.

Пример ниже показывает хук в settings.json, который запускает скрипт проекта с формой $env::

{
  "type": "command",
  "shell": "powershell",
  "command": "& \"$env:CLAUDE_PROJECT_DIR\\.claude\\hooks\\check.ps1\""
}

Debug hooks

Детали выполнения hooks записываются в файл отладочного журнала. Запустите Claude Code с claude --debug-file <path> для записи журнала в известное расположение, или запустите claude --debug и прочитайте журнал в ~/.claude/debug/<session-id>.txt. Флаг --debug не выводит на терминал.

Например, hook PostToolUse на Write, чья команда выводит hook-ran, создаёт записи вроде:

2026-07-19T02:03:24.382Z [DEBUG] Hook output does not start with {, treating as plain text
2026-07-19T02:03:24.382Z [DEBUG] "Hook PostToolUse:Write (PostToolUse) success:\nhook-ran"

Для более детальной информации о совпадении hooks установите CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose для просмотра дополнительных строк логирования, таких как количество совпадений фильтра hook и совпадение запроса.

Для устранения неполадок распространённых проблем, таких как hooks, которые не срабатывают, Stop hooks, которые продолжают блокировать, или ошибки конфигурации, см. Limitations and troubleshooting в руководстве. Для более широкого диагностического пошагового руководства, охватывающего /context, /doctor и приоритет параметров, см. Debug your config.