Справочник по hooks
Справочник по событиям hook Claude Code, схеме конфигурации, форматам JSON входа/выхода, кодам выхода, асинхронным hooks, HTTP hooks, prompt hooks и MCP tool hooks.
Для краткого руководства с примерами см. Автоматизация рабочих процессов с помощью 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, которые пропускают оба
<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 удаляется при выходе из сеанса, когда подагент завершает работу, или когда вы удаляете фоновый сеанс |
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 перед попыткой их использования.
Фильтр Bash|PowerShell охватывает инструмент PowerShell а также Bash. Одно правило if совпадает только с вызовами одного инструмента, поэтому каждый инструмент получает свой обработчик: первый сужает область до команд Bash, совпадающих с rm *, второй — до команд PowerShell, совпадающих с Remove-Item *. Оба запускают один и тот же скрипт через powershell.exe:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|PowerShell",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"
]
},
{
"type": "command",
"if": "PowerShell(Remove-Item *)",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"
]
}
]
}
]
}
}
Флаг -NoProfile пропускает загрузку вашего профиля PowerShell, чтобы hook запустился быстро, а -ExecutionPolicy Bypass позволяет PowerShell запустить локальный файл скрипта.
Скрипт читает JSON входные данные из stdin, извлекает команду и возвращает permissionDecision со значением "deny", если она содержит rm -rf или Remove-Item с последующим -Recurse. Сохраните его в .claude/hooks/block-rm.ps1 в вашем проекте:
# .claude/hooks/block-rm.ps1
$callInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
$command = $callInput.tool_input.command
if ($command -match 'rm -rf|Remove-Item.*-Recurse') {
@{
hookSpecificOutput = @{
hookEventName = "PreToolUse"
permissionDecision = "deny"
permissionDecisionReason = "Destructive command blocked by hook"
}
} | ConvertTo-Json
} else {
exit 0 # no decision; normal permission flow applies
}
Теперь предположим, что Claude Code решает запустить Bash "rm -rf /tmp/build" с конфигурацией macOS/Linux. Вот что происходит:
Событие срабатывает
Событие PreToolUse срабатывает. Claude Code отправляет входные данные инструмента как JSON на stdin hook:
{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
Фильтр проверяет
Фильтр "Bash" совпадает с именем инструмента, поэтому эта группа hook активируется. Если вы опустите фильтр или используете "*", группа активируется при каждом возникновении события.
Условие if проверяет
Условие if "Bash(rm *)" совпадает, потому что rm -rf /tmp/build — это подкоманда, совпадающая с rm *, поэтому этот обработчик запускается. Если бы команда была npm test, проверка if не удалась бы и block-rm.sh никогда не запустился бы, избегая затрат на порождение процесса. Поле if опционально; без него каждый обработчик в совпадающей группе запускается.
Обработчик hook запускается
Скрипт проверяет полную команду и находит rm -rf, поэтому выводит решение на stdout:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}
Если бы команда была более безопасным вариантом rm, таким как rm file.txt, скрипт выполнил бы exit 0 вместо этого. Код выхода 0 без вывода означает, что hook не имеет решения для отчёта, поэтому вызов инструмента продолжается через нормальный поток разрешений. Hook может отклонить вызов, но молчание не одобряет его.
Claude Code действует на основе результата
Claude Code читает JSON решение, блокирует вызов инструмента и показывает Claude причину.
Раздел Configuration ниже документирует полную схему, и каждый раздел hook event документирует, какой входной JSON получает ваша команда и какой выход она может вернуть.
Конфигурация
Hooks определяются в JSON файлах настроек. Конфигурация имеет три уровня вложенности:
- Выберите hook event для ответа, например
PreToolUseилиStop - Добавьте matcher group для фильтрации срабатывания, например "только для инструмента Bash"
- Определите один или несколько hook handlers для запуска при совпадении
См. Как разрешается hook выше для полного пошагового руководства с аннотированным примером.
На этой странице используются специальные термины для каждого уровня: hook event для точки жизненного цикла, matcher group для фильтра и hook handler для команды оболочки, конечной точки HTTP, инструмента MCP, подсказки или агента, который запускается. "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 совпадает с объединённым allowlisthttpHookAllowedEnvVars: когда определено, 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 сервера Memorymcp__filesystem__read_file: инструмент read file сервера Filesystemmcp__github__search_repositories: инструмент поиска сервера GitHub
Чтобы совпадать с каждым инструментом с сервера, добавьте .* к префиксу сервера. .* требуется: фильтр, такой как mcp__memory или mcp__brave-search, содержит только символы точного совпадения, поэтому он сравнивается как точная строка и не совпадает ни с одним инструментом.
mcp__memory__.*совпадает со всеми инструментами сервераmemorymcp__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 |
нет | Синтаксис правила разрешения для фильтрации срабатывания этого hook, такой как "Bash(git *)" или "Edit(*.ts)". Hook запускается только если вызов инструмента совпадает с шаблоном. См. таблицу Bash matching table ниже для того, как Bash шаблоны оцениваются против подкоманд, $() и обратных кавычек. Оценивается только на событиях инструмента: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest и PermissionDenied. На других событиях hook с установленным 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 на любой глубине под рабочим каталогом.
Для Bash шаблонов, запускается ли ваша команда hook зависит от формы шаблона и команды 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.
На Windows, exec form требует, чтобы command разрешался в реальный исполняемый файл, такой как .exe. Shims .cmd и .bat, которые npm, npx, eslint и другие инструменты устанавливают в node_modules/.bin, не являются исполняемыми файлами и не могут быть запущены без оболочки. Чтобы запустить их в exec form, вызовите базовый скрипт с node напрямую, например "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/node_modules/eslint/bin/eslint.js"]. Паттерн node плюс script-path работает на каждой платформе, потому что node.exe — это реальный бинарный файл. Чтобы запустить shim .cmd или .bat по имени, используйте shell form.
Этот пример запускает 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.*}.
В exec form, command — это только имя исполняемого файла или путь. Если command — это голое имя без разделителя пути и содержит пробелы рядом с args, Claude Code логирует предупреждение, потому что spawn не удастся: нет исполняемого файла с именем node script.js. Переместите дополнительные токены в args. Абсолютные пути с пробелами, такие как C:\Program Files\nodejs\node.exe, — это один действительный исполняемый файл и не вызывают предупреждение.
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}: каталог постоянных данных плагина, для зависимостей и состояния, которые должны пережить обновления плагина.
Worktrees отличаются. Если Claude входит в worktree во время сеанса, Claude Code держит ${CLAUDE_PROJECT_DIR} там, где он был, и передаёт путь worktree вашим hooks другим способом:
${CLAUDE_PROJECT_DIR}остаётся на месте: он всё ещё указывает на корень проекта, где сеанс начался, поэтому команда, такая как${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh, всё ещё запускает скрипт в основной checkout.cwdследует за Claude: полеcwdв входных JSON hook — это корень worktree после того, как Claude входит в worktree, и новый каталог после того, как Claude запускаетcd. Прочитайте его, когда hook нужно знать, в каком каталоге Claude работает.
Предпочитайте 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": []
}
]
}
]
}
}
Определите plugin hooks в hooks/hooks.json с опциональным полем description верхнего уровня. Когда плагин включен, его hooks объединяются с вашими пользовательскими и проектными hooks.
Этот пример запускает скрипт форматирования, поставляемый с плагином:
{
"description": "Automatic code formatting",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
"args": [],
"timeout": 30
}
]
}
]
}
}
См. plugin components reference для получения подробной информации о создании plugin hooks.
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 в файлах настроек обычно захватываются автоматически наблюдателем файлов.
Входные и выходные данные Hook
Hooks команд получают JSON-данные через stdin и передают результаты через коды выхода, stdout и stderr. HTTP hooks получают тот же JSON, что и тело POST-запроса, и передают результаты через тело HTTP-ответа. В этом разделе рассматриваются поля и поведение, общие для всех событий. Каждый раздел события в разделе Hook events включает его конкретную схему входных данных и параметры управления решением.
На macOS и Linux hooks команд запускаются в собственном сеансе без управляющего терминала. Процесс hook и любые дочерние процессы не могут открыть /dev/tty или отправлять последовательности escape непосредственно в интерфейс Claude Code. Windows не имеет /dev/tty.
Чтобы вывести сообщение пользователю на любой платформе, верните systemMessage в JSON-выводе. Некоторые события игнорируют его или доставляют его в другое место, и в каждом разделе события указано, как это происходит. Чтобы вызвать уведомление рабочего стола, установить заголовок окна или издать звуковой сигнал, верните terminalSequence вместо этого.
Общие входные поля
Hook события получают эти поля в виде JSON в дополнение к полям, специфичным для события, задокументированным в каждом разделе hook event. Для hooks команд этот JSON поступает через stdin. Для HTTP hooks он поступает как тело POST-запроса.
| Поле | Описание |
|---|---|
session_id |
Текущий идентификатор сеанса |
prompt_id |
UUID, идентифицирующий пользовательский запрос, который в настоящее время обрабатывается. Совпадает с атрибутом prompt.id на событиях OpenTelemetry, поэтому вы можете коррелировать выход hook с телеметрией для одного запроса. Отсутствует до первого ввода пользователя. Требуется Claude Code v2.1.196 или позже |
transcript_path |
Путь к файлу JSON разговора. Файл транскрипта записывается асинхронно и может отставать от разговора в памяти, поэтому он может еще не включать самые последние сообщения текущего хода, когда срабатывает hook. Hooks, которым нужен финальный текст ассистента текущего хода, должны использовать last_assistant_message на Stop и SubagentStop вместо чтения транскрипта |
cwd |
Текущий рабочий каталог при вызове hook |
scratchpad_dir |
Путь к каталогу scratchpad сеанса, где Claude хранит временные рабочие файлы. Отсутствует, когда сеанс не имеет scratchpad или временный каталог недоступен. Требуется Claude Code v2.1.257 или позже |
permission_mode |
Текущий режим разрешений: "default", "plan", "acceptEdits", "auto", "dontAsk" или "bypassPermissions". Режим, обозначенный как Manual, поступает как "default", никогда не как "manual", поэтому скрипты, которые совпадают с "default", продолжают работать. Не все события получают это поле. Проверьте пример JSON в каждом разделе hook event |
effort |
Объект с полем level, содержащим уровень усилий, действующий при запуске hook: "low", "medium", "high", "xhigh" или "max". Если вы установите уровень, который активная модель не поддерживает, level сообщает уровень, который вместо этого запустил Claude Code; Adjust effort level говорит, как он выбирает этот уровень. Объект совпадает с полем effort строки состояния. Присутствует для событий, которые срабатывают в контексте использования инструмента, таких как PreToolUse, PostToolUse, Stop и SubagentStop, когда текущая модель поддерживает параметр усилий. Уровень также доступен для команд hook и инструмента Bash как переменная окружения $CLAUDE_EFFORT. |
hook_event_name |
Имя события, которое сработало |
При запуске с --agent или внутри subagent включаются два дополнительных поля:
| Поле | Описание |
|---|---|
agent_id |
Уникальный идентификатор для subagent. Присутствует только когда hook срабатывает внутри вызова subagent. Используйте это для различения вызовов hook subagent от вызовов основного потока. |
agent_type |
Имя агента (например, "Explore" или "security-reviewer"). Присутствует, когда сеанс использует --agent или hook срабатывает внутри subagent. Для subagents тип subagent имеет приоритет над значением --agent сеанса. См. SubagentStart для значений, которые сообщают пользовательские и plugin subagents, и как написать matcher для имени с областью plugin. |
Только hooks SessionStart могут получить поле model, и Claude Code не всегда его включает. Hooks PreModelSwitch и PostModelSwitch получают from_model и to_model вместо этого, поэтому используйте hook PostModelSwitch для отслеживания модели по мере ее изменения во время сеанса.
Нет переменной окружения $CLAUDE_MODEL. Hook может читать $ANTHROPIC_MODEL, если вы установили ее в своей оболочке, но это значение не изменяется при переключении моделей с помощью /model во время сеанса.
Процесс hook наследует родительское окружение, за исключением переменных экспортера OTEL_*, которые Claude Code удаляет из каждого подпроцесса, который он порождает, и, когда установлена CLAUDE_CODE_SUBPROCESS_ENV_SCRUB в 1, переменные, которые он удаляет.
Например, hook 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 специфичны для события. Каждый раздел hook event документирует дополнительные поля для этого события.
Выходные коды выхода
Код выхода из вашей команды hook сообщает Claude Code, должно ли действие продолжаться, быть заблокировано или игнорироваться. Код выхода не действует в одиночку. Claude Code читает поля JSON output из stdout при каждом коде выхода, не только 0, и для событий, которые используют стандартную модель решения, проанализированный объект, который проходит валидацию схемы, вступает в силу наряду с кодом. Блокировка Exit 2 — это единственный результат, который JSON не может переопределить.
Две таблицы владеют исключениями для каждого события: Exit code 2 behavior per event говорит, что коды выхода делают для каждого события, и Decision control говорит, какие поля решения каждое событие учитывает. Универсальные поля, такие как systemMessage, работают на большинстве событий и перечислены в таблице JSON output.
Exit code 0
Exit 0 означает успех и является предполагаемым кодом выхода, когда вы печатаете JSON для структурированного управления.
Для большинства событий Claude Code записывает stdout в журнал отладки и не показывает его в транскрипте. Исключения — это UserPromptSubmit, UserPromptExpansion, SessionStart и PostModelSwitch, где Claude Code добавляет простой текст stdout как контекст, который Claude может видеть и действовать.
Читает ли Claude Code ваш stdout как JSON output или как простой текст, зависит от того, как он начинается и заканчивается, игнорируя окружающие пробелы:
- Начинается с
{и заканчивается на}: Claude Code анализирует его как JSON. Когда выход состоит из двух или более строк, которые каждая анализируются как JSON самостоятельно, и ни одна строка не является объектом JSON output, который устанавливает поле, Claude Code рассматривает весь выход как простой текст. Когда одна из этих строк устанавливает поле, весь выход является ошибкой анализа, описанной ниже. - Начинается с
{но не заканчивается на}: Claude Code рассматривает это как простой текст. - Начинается с чего-либо еще: Claude Code рассматривает это как простой текст, JSON массив или включенную строку JSON в кавычках.
Для событий, которые используют стандартную модель решения, exit 0 с проанализированным объектом, который не проходит валидацию схемы, является неблокирующей ошибкой: действие продолжается, и транскрипт показывает уведомление об ошибке <hook name> hook error с сообщением валидации. То же самое происходит при любом коде выхода, отличном от 2, в то время как exit 2 все еще блокирует.
Для событий, которые используют стандартную модель решения, когда Claude Code пытается анализировать ваш stdout как JSON и не может, он сообщает о неблокирующей ошибке при каждом коде выхода, отличном от 2. Транскрипт показывает уведомление об ошибке <hook name> hook error с сообщением анализа. На событиях, которые добавляют простой текст stdout как контекст, Claude Code не добавляет текст. До v2.1.248 Claude Code рассматривал этот stdout как простой текст.
Stderr из hook, который выходит с 0, идет только в журнал отладки, никогда в транскрипт, и Claude его не видит. Чтобы прочитать его самостоятельно, включите debug logging. Чтобы вывести предупреждение Claude из hook PostToolUse или PostToolUseFailure, выйдите с 2 вместо этого, чтобы Claude видел stderr, даже если инструмент уже запустился.
Exit code 2
Exit 2 означает блокирующую ошибку. На событиях, которые могут блокировать, exit 2 блокирует независимо от того, печатаете ли вы JSON: даже JSON permissionDecision из "allow" не может его переопределить. Claude Code все еще читает любой действительный JSON output на stdout. На Elicitation и ElicitationResult, hookSpecificOutput hook с exit-2 игнорируется.
Сообщение блокировки — это причина из решения блокировки вашего JSON, когда оно его делает, и ваш текст stderr в противном случае. Что делает блокировка, варьируется в зависимости от события: PreToolUse блокирует вызов инструмента, UserPromptSubmit отклоняет запрос и так далее. Exit code 2 behavior per event перечисляет эффект для каждого события, и каждый раздел события говорит, куда идет сообщение.
Hook, который выходит с 2 при печати JSON, который не проходит валидацию схемы JSON output, все еще блокирует: 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
Другие коды выхода
Любой другой код выхода не блокирует сам по себе для большинства hook событий. Что происходит, зависит от вашего stdout:
- С проанализированным объектом, который проходит валидацию схемы, для событий, которые используют стандартную модель решения, Claude Code игнорирует код выхода и только JSON решает результат:
- Каждое поле, которое событие поддерживает, учитывается, включая
permissionDecision,additionalContext,updatedInputиsystemMessage, и hook не сообщается как ошибка. - Decision control перечисляет поля решения для каждого события; универсальные поля, такие как
systemMessage, следуют таблице JSON output.
- Каждое поле, которое событие поддерживает, учитывается, включая
- С проанализированным объектом, который не проходит валидацию схемы, для событий, которые используют стандартную модель решения, это то же самое неблокирующее ошибка, что и на exit 0: действие продолжается, и уведомление
<hook name> hook errorсодержит сообщение валидации. - С stdout, который Claude Code пытается анализировать как JSON и не может, Claude Code сообщает о той же неблокирующей ошибке, что и на exit 0 для событий, которые используют стандартную модель решения. Действие продолжается, и уведомление содержит сообщение анализа.
- С stdout, который Claude Code рассматривает как простой текст, или с пустым stdout, это неблокирующая ошибка для большинства hook событий: действие продолжается, и транскрипт показывает уведомление об ошибке
<hook name> hook error, за которым следует первая строка stderr, с префиксомFailed with non-blocking status code:. Чтобы захватить полный stderr, включите debug logging.
События вне стандартной модели решения сохраняют свои собственные строки в таблице для каждого события: WorktreeCreate не создает при любом ненулевом выходе, независимо от того, что говорит ваш JSON, и события, которые полностью игнорируют выход hook, такие как StopFailure, игнорируют ваш JSON при каждом коде выхода, кроме полей побочных эффектов, таких как terminalSequence, которые все еще срабатывают.
Hook, который не может запуститься, попадает в ту же неблокирующую корзину. Когда путь скрипта не существует или не исполняемый, оболочка выходит с кодом, например 127, и вы видите то же уведомление с сообщением интерпретатора, например Failed with non-blocking status code: /bin/sh: /path/to/hook.sh: No such file or directory. Для большинства hook событий действие продолжается. Когда вы устанавливаете hook политики, следите за этим уведомлением при его первом запуске: неправильно введенный путь в settings.json оставляет ворота молча отключенными.
Для большинства hook событий exit code 2 — это единственный код выхода, который блокирует только через код. Без действительного JSON на stdout Claude Code рассматривает exit code 1 как неблокирующую ошибку и продолжает действие, даже хотя 1 — это обычный код отказа Unix. Если ваш hook предназначен для обеспечения политики, используйте exit 2. События worktree отличаются: любой ненулевой код выхода из WorktreeCreate прерывает создание worktree, и любой ненулевой код выхода из WorktreeRemove делает удаление worktree неудачным, если каталог все еще существует после этого.
Timeouts
Кроме hook команды, который вы запускаете с async: true, Claude Code отменяет hook command, http или mcp_tool, который достигает своего timeout, отбрасывая выход hook, поэтому на большинстве событий истекший по времени hook не отображает решение.
На PreModelSwitch, hook, отмененный при его timeout, блокирует переключение модели. На PreToolUse две семьи hook отличаются:
- Истекший по времени hook
command,httpилиmcp_toolне блокирует вызов инструмента. Вызов продолжается через нормальный поток разрешений, поэтому не рассчитывайте на зависший hook, чтобы действовать как ворота. - Agent SDK callback hook, который превышает свой timeout, блокирует вызов инструмента.
Exit code 2 behavior per event
Exit code 2 — это способ, которым hook сигнализирует "стоп, не делай этого". Эффект зависит от события, потому что некоторые события представляют действия, которые могут быть заблокированы (например, вызов инструмента, который еще не произошел), а другие представляют вещи, которые уже произошли или не могут быть предотвращены.
| Hook event | Может блокировать? | Что происходит на exit 2 |
|---|---|---|
PreToolUse |
Да | Блокирует вызов инструмента |
PermissionRequest |
Нет | Exit code 2 не учитывается для этого события и поток разрешений продолжается без изменений. Отклоните через объект decision вместо этого |
UserPromptSubmit |
Да | Блокирует обработку запроса. См. What a blocked prompt leaves behind |
UserPromptExpansion |
Да | Блокирует расширение |
Stop |
Да | Предотвращает остановку Claude, продолжает разговор |
SubagentStop |
Да | Предотвращает остановку subagent |
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 exit code 2 в транскрипте как уведомление об ошибке <hook name> hook error, так же как оно отображает неблокирующую ошибку. Claude его не видит, и сеанс или subagent продолжается. Для SubagentStart уведомление появляется в собственном транскрипте subagent, а не в родительском разговоре.
HTTP response handling
HTTP hooks используют коды состояния HTTP и тела ответов вместо кодов выхода и stdout. Результаты ниже применяются к большинству событий; событие с его собственным контрактом отказа в таблице для каждого события, такое как WorktreeCreate, применяет этот контракт к неудачному HTTP hook также:
- 2xx с пустым телом: успех, эквивалентно exit code 0 без выхода
- 2xx с телом объекта JSON: анализируется с использованием той же схемы JSON output, что и hooks команд. Тело, которое не проходит валидацию схемы, является неблокирующей ошибкой
- 2xx с любым другим телом, таким как простой текст: неблокирующая ошибка, обрабатывается так же, как статус non-2xx. Claude Code не добавляет текст в контекст Claude
- Статус non-2xx: неблокирующая ошибка, выполнение продолжается
- Ошибка соединения: неблокирующая ошибка, выполнение продолжается
- Timeout: hook отменяется, как описано в разделе Timeouts
В отличие от hooks команд, HTTP hooks не могут сигнализировать блокирующую ошибку только через коды состояния. Чтобы заблокировать вызов инструмента или отклонить разрешение, верните ответ 2xx с телом JSON, содержащим соответствующие поля решения.
JSON output
Коды выхода позволяют вам только блокировать или молчать, но JSON output дает вам более точное управление. Вместо выхода с кодом 2 для блокировки, выйдите с 0 и напечатайте объект JSON на stdout. Claude Code читает конкретные поля из этого JSON для управления поведением, включая decision control для блокировки, разрешения или эскалации пользователю.
Выберите один подход для каждого hook: либо используйте коды выхода только для сигнализации, либо выйдите с 0 и напечатайте JSON для структурированного управления. Если вы их смешиваете, exit 2 сохраняет свой блокирующий эффект, и Claude Code все еще читает поля JSON, с единственным исключением elicitation, отмеченным в разделе Exit code 2.
Stdout вашего hook должен содержать только объект JSON. Если ваш профиль оболочки печатает текст при запуске, это может помешать анализу JSON. См. Hook JSON has no effect в руководстве по устранению неполадок.
Строки additionalContext, systemMessage и initialUserMessage hook, а также его простой stdout, ограничены 10 000 символов:
- Область: Claude Code измеряет каждую строку отдельно, даже когда несколько hooks запускаются для одного события. Для JSON output каждое поле измеряется отдельно; простой stdout измеряется целиком.
- Превышение лимита: Claude Code сохраняет выход в файл в каталоге сеанса и заменяет его путем к файлу и предпросмотром до первых 2000 символов. Большой действительный результат Bash обрабатывается так же, как описано в разделе Output limits. В отличие от этого потолка Bash, эта крышка не имеет параметра или переменной окружения для ее повышения.
- Чтение файла: Claude Code не просит Claude прочитать файл, поэтому держите все, что Claude должен всегда видеть, в пределах крышки.
Объект JSON поддерживает три вида полей:
- Универсальные поля, такие как
continue, перечислены в таблице ниже. Каждое событие их принимает, но некоторые события игнорируют их или доставляютsystemMessageв другое место, чем транскрипт. Каждый раздел события говорит об этом.terminalSequenceработает на этих событиях также, с исключениями, перечисленными в разделе Emit terminal notifications. - Top-level
decisionиreasonиспользуются некоторыми событиями для блокировки или предоставления обратной связи. hookSpecificOutput— это вложенный объект для событий, которым нужно более богатое управление. Он требует полеhookEventName, установленное на имя события.
| Поле | По умолчанию | Описание |
|---|---|---|
continue |
true |
Если false, Claude полностью прекращает обработку после запуска hook. Имеет приоритет над любыми полями решения, специфичными для события |
stopReason |
нет | Сообщение, показанное пользователю, когда continue равно false. Оно остается в разговоре, поэтому Claude видит его, если разговор продолжается |
suppressOutput |
false |
Не имеет эффекта: Claude Code принимает поле, но не действует на него. Stdout успешного hook никогда не показывается в транскрипте и записывается в журнал отладки |
systemMessage |
нет | Предупреждающее сообщение, показанное пользователю. В выводе Agent SDK и --output-format stream-json, оно может поступить как SDKInformationalMessage |
terminalSequence |
нет | Последовательность escape терминала для Claude Code для выпуска от вашего имени, такая как уведомление рабочего стола, заголовок окна или звонок. Ограничено OSC 0/1/2/9/99/777 и BEL. Если значение содержит что-либо вне списка разрешений, поле игнорируется. Используйте это вместо записи в /dev/tty, которая недоступна для hooks |
Чтобы полностью остановить Claude:
{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
Для hooks PreToolUse и PostToolUse остановка применяется даже когда вызов инструмента не удается или завершается, пока Claude все еще потоком ответ.
Emit terminal notifications
Hooks запускаются без управляющего терминала, поэтому запись последовательностей 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 - Bare BEL
Последовательности могут быть завершены BEL или ST. Все, что находится вне списка разрешений, включая последовательности курсора CSI и цвета, последовательности палитры OSC, гиперссылки OSC 8, записи буфера обмена OSC 52 и OSC 1337, отклоняется и поле игнорируется.
Claude Code выпускает саму последовательность, когда обрабатывает выход вашего hook, поэтому поле работает на событиях, которые игнорируют systemMessage и continue, такие как Notification и StopFailure. Оно имеет два ограничения:
- Claude Code выпускает последовательность только в интерактивном сеансе и только пока его интерфейс находится на экране. В неинтерактивном режиме с флагом
-pи в Agent SDK он игнорирует поле. - Hook команды
WorktreeCreateне может вернуть JSON, потому что Claude Code читает его stdout как путь worktree. HTTP hookWorktreeCreateвозвращает JSON и может включать поле.
Пример ниже срабатывает уведомление рабочего стола из hook Notification. Последовательность escape строится с помощью printf восьмеричных escape, поэтому управляющие байты никогда не появляются в командной строке оболочки, и 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": "..." } одинакова из любой оболочки или языка.
Add context for Claude
Поле additionalContext передает строку из вашего hook в контекстное окно Claude. Claude Code оборачивает строку в напоминание системы и вставляет ее в разговор в точке, где сработал hook. Claude читает напоминание при следующем запросе модели, но оно не появляется как сообщение чата в интерфейсе.
Верните additionalContext внутри hookSpecificOutput рядом с именем события:
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
}
}
Где появляется напоминание, зависит от события:
- SessionStart и SubagentStart: в начале разговора, перед первым запросом
- UserPromptSubmit и UserPromptExpansion: рядом с отправленным запросом
- PreToolUse, PostToolUse, PostToolUseFailure и PostToolBatch: рядом с результатом инструмента
- Stop и SubagentStop: в конце хода. Разговор продолжается, поэтому Claude может действовать на обратную связь. См. Stop decision control
- PostModelSwitch: со следующим запросом после переключения. См. PostModelSwitch decision control для синхронизации
Когда несколько hooks возвращают additionalContext для одного события, Claude получает все значения.
Если значение превышает 10 000 символов, Claude Code записывает текст в файл в каталоге сеанса и передает Claude путь к файлу с предпросмотром до первых 2000 символов вместо этого. Claude может прочитать файл, но Claude Code не просит его.
Используйте additionalContext для информации, которую Claude должен знать о текущем состоянии вашей среды или операции, которая только что запустилась:
- Состояние среды: текущая ветвь, цель развертывания или активные флаги функций
- Условные правила проекта: какая команда теста применяется к только что отредактированному файлу, какие каталоги доступны только для чтения в этом worktree
- Внешние данные: открытые проблемы, назначенные вам, недавние результаты CI, контент, полученный из внутреннего сервиса
Для инструкций, которые никогда не изменяются, предпочитайте CLAUDE.md. Он загружается без запуска скрипта и является стандартным местом для статических соглашений проекта.
Напишите текст как фактические утверждения, а не как императивные системные инструкции. Фразировка, такая как "Цель развертывания — production" или "Этот репо использует bun test", читается как информация о проекте. Текст, сформулированный как внеполосные системные команды, может вызвать защиту Claude от инъекций подсказок, что заставляет Claude вывести текст вам вместо того, чтобы рассматривать его как контекст.
Claude Code сохраняет введенный текст в транскрипте сеанса. Для событий середины сеанса, таких как PostToolUse или UserPromptSubmit, когда вы возобновляете с --continue или --resume, Claude Code воспроизводит сохраненный текст, а не повторно запускает hook для прошлых ходов, поэтому значения, такие как временные метки или SHA коммитов, становятся устаревшими. Hooks SessionStart запускаются снова при возобновлении с source, установленным на "resume", или "fork", если вы добавили --fork-session, поэтому они могут обновить свой контекст.
Decision control
Не каждое событие поддерживает блокировку или управление поведением через JSON. События, которые это делают, каждое использует другой набор полей для выражения этого решения. Используйте эту таблицу как быструю ссылку перед написанием hook:
| События | Паттерн решения | Ключевые поля |
|---|---|---|
| UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact | Top-level decision |
decision: "block", reason. Stop и SubagentStop также принимают hookSpecificOutput.additionalContext для неошибочной обратной связи, которая продолжает разговор |
| TeammateIdle, TaskCompleted | Exit code или continue: false |
Exit code 2 блокирует действие с обратной связью stderr. JSON {"continue": false, "stopReason": "..."} также полностью останавливает товарища, совпадая с поведением hook Stop; TaskCompleted игнорирует это, когда инструмент TaskUpdate вызвал событие |
| TaskCreated | Exit code или top-level decision |
Exit code 2 или decision: "block" отменяет задачу и возвращает сообщение Claude. continue: false игнорируется |
| PreToolUse | hookSpecificOutput |
permissionDecision (allow/deny/ask/defer), permissionDecisionReason |
| PreModelSwitch | hookSpecificOutput или top-level decision |
permissionDecision (allow/deny/ask), permissionDecisionReason. decision: "block" также отменяет переключение |
| PermissionRequest | hookSpecificOutput |
decision.behavior (allow/deny) |
| PermissionDenied | hookSpecificOutput |
retry: true сообщает модели, что она может повторить попытку отклоненного вызова инструмента; Claude Code игнорирует это для отказов без вердикта |
| WorktreeCreate | path return | Hook команды печатает путь на stdout; HTTP hook возвращает hookSpecificOutput.worktreePath. Ошибка hook или отсутствующий путь не создает |
| WorktreeRemove | Exit code | Любой ненулевой код выхода делает удаление неудачным, если каталог все еще существует после этого. Выход JSON отбрасывается |
| Elicitation | hookSpecificOutput |
action (accept/decline/cancel), content (значения полей формы для accept) |
| ElicitationResult | hookSpecificOutput |
action (accept/decline/cancel), content (значения полей формы переопределяют) |
| 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:updatedInputнепосредственно подhookSpecificOutputзаменяет аргументы инструмента перед его запуском. См. PreToolUse decision controlPermissionRequest:updatedInputвнутри объектаdecision. См. PermissionRequest decision controlPostToolUse:updatedToolOutputзаменяет результат инструмента. См. PostToolUse decision controlUserPromptSubmit: не может переписать запрос; он только вводитadditionalContextрядом с ним
Для случаев использования редакции или трансформации перехватите на PreToolUse для исходящих входов инструмента и PostToolUse для входящих результатов инструмента.
Вот примеры каждого паттерна в действии:
Единственное значение для decision — это "block". Чтобы разрешить действию продолжаться, опустите decision из вашего JSON или выйдите с 0 без какого-либо JSON вообще:
{
"decision": "block",
"reason": "Test suite must pass before proceeding"
}
Использует hookSpecificOutput для более богатого управления: разрешить, отклонить или эскалировать пользователю. Вы также можете изменить входные данные инструмента перед его запуском или вводить дополнительный контекст для Claude. См. PreToolUse decision control для полного набора опций.
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Database writes are not allowed"
}
}
Использует hookSpecificOutput для разрешения или отклонения запроса разрешения от имени пользователя. При разрешении вы также можете изменить входные данные инструмента или применить правила разрешений, чтобы пользователь не был запрошен снова. См. PermissionRequest decision control для полного набора опций.
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": {
"command": "npm run lint"
}
}
}
}
Для расширенных примеров, включая валидацию команд Bash, фильтрацию запросов и скрипты автоматического одобрения, см. What you can automate в руководстве и Bash command validator reference implementation.
События хуков
Каждое событие соответствует точке жизненного цикла 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
CLAUDE_ENV_FILE доступна для хуков SessionStart, Setup, CwdChanged и FileChanged. Хукам других типов эта переменная недоступна.
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 также запускает их, когда:
- срабатывает запланированная задача, включая итерацию
/loop - фоновый субагент отчитывается сессии, которая его запустила
- другая сессия отправляет сообщение в ваш основной диалог
У хуков UserPromptSubmit таймаут по умолчанию составляет 30 секунд для типов command, http и mcp_tool — меньше, чем 600 секунд по умолчанию для этих типов в большинстве других событий. Поскольку этот хук выполняется перед каждым промптом и блокирует обработку моделью до своего завершения, зависший хук останавливает сессию. Если вашему хуку нужно больше времени, задайте поле timeout в записи хука.
За исключением command-хука, запущенного с async: true, command-, HTTP- или MCP-хук UserPromptSubmit, достигший таймаута, отменяется, а его вывод, включая любой additionalContext, отбрасывается. Промпт всё равно доходит до Claude, но без этого контекста. В транскрипте отображается уведомление с именем хука, сработавшим таймаутом и указанием на то, что вывод был отброшен.
Callback-хук Agent SDK на UserPromptSubmit, достигший таймаута, блокирует промпт с сообщением, в котором указаны хук и таймаут, поскольку callback в этом месте может выступать шлюзом политики, который не должен при сбое пропускать запросы. Сессия продолжается. До v2.1.208 таймаут callback для этого события завершал ход с ошибкой выполнения.
Входные данные 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. У хука с кодом выхода 2, который не выводит JSON, текст промпта всегда попадает в сообщение о блокировке.
suppressOriginalPrompt изменяет только сообщение о блокировке. Отправленный текст всё равно может появиться в локальных файлах, таких как транскрипт сессии и история промптов, поэтому блокирующий хук не является способом не допустить попадания секрета на диск. Чтобы ограничить или удалить эти файлы, см. Хранение в открытом виде и Очистка локальных данных.
UserPromptExpansion
Выполняется, когда введённая пользователем команда разворачивается в промпт до того, как дойти до Claude. Используйте его, чтобы запретить прямой вызов определённых команд, внедрить контекст для конкретного скилла или логировать, какие команды вызывают пользователи. Например, хук с matcher deploy может блокировать /deploy, если нет файла подтверждения, а хук, соответствующий скиллу ревью, может добавлять чек-лист ревью команды как 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.
В этом примере из ответов Claude удаляется форматирование markdown для отображения в виде обычного текста. Скрипт читает каждый пакет из stdin, удаляет маркеры жирного шрифта и обратные кавычки встроенного кода из delta и возвращает результат как displayContent.
Зарегистрируйте command-хук для события в файле настроек:
{
"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("`"; ""))}}'
Зарегистрируйте command-хук, который запускает скрипт через PowerShell:
{
"hooks": {
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.ps1"
]
}
]
}
]
}
}
Флаг -NoProfile пропускает загрузку вашего профиля PowerShell, чтобы хук запускался быстро, а -ExecutionPolicy Bypass позволяет PowerShell выполнить локальный файл скрипта.
Сохраните этот скрипт в .claude/hooks/plain-display.ps1 в вашем проекте:
$batch = [Console]::In.ReadToEnd() | ConvertFrom-Json
$text = $batch.delta -replace '\*\*', '' -replace '`', ''
@{
hookSpecificOutput = @{
hookEventName = "MessageDisplay"
displayContent = $text
}
} | ConvertTo-Json
Пакеты без 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 выполняется только когда Claude вызывает инструмент. Файлы, на которые вы ссылаетесь через @ в промпте, добавляются без какого-либо вызова инструмента: Claude Code вставляет их содержимое при построении промпта, поэтому для них не срабатывает ни один хук PreToolUse, включая хуки, соответствующие Read. Чтобы заблокировать определённые пути для ссылок через @, используйте вместо этого правило запрета Read.
PreToolUse также не срабатывает для EndConversation.
Используйте управление решениями PreToolUse, чтобы разрешить, запретить, запросить подтверждение или отложить вызов инструмента.
Callback-хук 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 или новее.
Список формируется по принципу «насколько возможно» и доступен в публичной бета-версии. Claude Code может пропустить изменение, включить файл, который одновременно изменил другой процесс, или остановиться на своих ограничениях размера. Структура поля может измениться. Используйте список, чтобы найти, что проверить, а не для применения политики.
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 с matcher 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"}], "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."
}
}
В неинтерактивном режиме с флагом -p Claude Code предлагает AskUserQuestion и ExitPlanMode только если у запуска есть хост разрешений для получения запроса, например callback canUseTool в Agent SDK. Этим инструментам требуется взаимодействие с пользователем. Возврат permissionDecision: "allow" вместе с updatedInput удовлетворяет это требование: хук читает входные данные инструмента из stdin, получает ответ через ваш собственный интерфейс и возвращает его в updatedInput, чтобы инструмент выполнился без запроса. Одного "allow" для этих инструментов недостаточно. Для AskUserQuestion верните исходный массив questions и добавьте объект answers, сопоставляющий текст каждого вопроса с выбранным ответом.
MCP-инструмент, который его сервер помечает с помощью _meta["anthropic/requiresUserInteraction"], строже: хук не может пропустить его запрос подтверждения с помощью "allow", с updatedInput или без него, потому что Claude Code не может убедиться, что хук получил необходимое инструменту взаимодействие.
Ранее PreToolUse использовал поля decision и reason верхнего уровня, но для этого события они объявлены устаревшими. Используйте вместо них hookSpecificOutput.permissionDecision и hookSpecificOutput.permissionDecisionReason. Устаревшие значения "approve" и "block" соответствуют "allow" и "deny" соответственно. Другие события, такие как PostToolUse и Stop, по-прежнему используют поля decision и reason верхнего уровня в качестве текущего формата.
Отложить вызов инструмента
"defer" предназначено для интеграций, которые запускают claude -p как подпроцесс и читают его вывод JSON, например приложения на Agent SDK или пользовательского интерфейса, построенного поверх Claude Code. Оно позволяет вызывающему процессу приостановить Claude на вызове инструмента, получить ввод через собственный интерфейс и продолжить с того же места. Claude Code учитывает это значение только в неинтерактивном режиме с флагом -p. В интерактивных сессиях он записывает в лог предупреждение и игнорирует результат хука.
Типичный случай — инструмент AskUserQuestion: Claude хочет что-то спросить у пользователя, но терминала для ответа нет. Запуск с -p предлагает AskUserQuestion, только если у него есть хост разрешений, например MCP-инструмент, переданный через --permission-prompt-tool, поэтому запускайте с ним. Цикл работает так:
- Claude вызывает
AskUserQuestion. Срабатывает хукPreToolUse. - Хук возвращает
permissionDecision: "defer". Инструмент не выполняется. Процесс завершается сstop_reason: "tool_deferred", а ожидающий вызов инструмента сохраняется в транскрипте. - Вызывающий процесс читает
deferred_tool_useиз результата SDK, показывает вопрос в своём интерфейсе и ждёт ответа. - Вызывающий процесс выполняет
claude -p --resume <session-id>с тем же хостом разрешений. Тот же вызов инструмента снова запускаетPreToolUse. - Хук возвращает
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"}, {"label": "Vue"}], "multiSelect": false }] }
}
}
Ограничений по таймауту или количеству повторных попыток нет. Сессия остаётся на диске, пока вы её не возобновите, с учётом очистки по сроку хранения cleanupPeriodDays, которая по умолчанию удаляет файлы сессий через 30 дней согласно правилам очистки по сроку хранения. Если ответ не готов к моменту возобновления, хук может снова вернуть "defer", и процесс завершится так же. Вызывающий процесс сам решает, когда выйти из цикла, в итоге возвращая из хука "allow" или "deny".
"defer" работает, только когда Claude делает в ходе один вызов инструмента. Если Claude делает несколько вызовов инструментов одновременно, "defer" игнорируется с предупреждением, и инструмент проходит обычный процесс проверки разрешений. Это ограничение существует потому, что при возобновлении можно повторно выполнить только один инструмент: нельзя отложить один вызов из пакета, не оставив остальные неразрешёнными.
Если отложенный инструмент при возобновлении больше недоступен, процесс завершается с stop_reason: "tool_deferred_unavailable" и is_error: true до срабатывания хука. Это происходит, когда MCP-сервер, предоставлявший инструмент, не подключён в возобновлённой сессии. Данные deferred_tool_use всё равно включаются, чтобы вы могли определить, какой инструмент пропал.
Чтобы возобновить отложенную сессию в режиме планирования, передайте --permission-prompt-tool вместе с --resume, чтобы Claude Code мог представить план на утверждение. Если вы передаёте некоторые другие флаги запуска, возобновлённый запуск не возвращается в режим планирования; см. Возобновление в режиме планирования с -p. Требуется Claude Code v2.1.246 или новее.
При возобновлении с -p Claude Code не восстанавливает никакой другой сохранённый режим разрешений. Он запускает работу в том режиме разрешений, в котором запустился бы новый запуск claude -p, поэтому снова передайте --permission-mode или --dangerously-skip-permissions, если отложенная сессия их использовала. При возобновлении с claude --resume <session-id> без -p Claude Code восстанавливает сохранённый режим разрешений, за исключениями, перечисленными в разделе режим разрешений при возобновлении.
PermissionRequest
Выполняется, когда Claude Code собирается запросить у вас разрешение на использование инструмента. В сессиях, которые не могут показать запрос, например у фоновых субагентов в неинтерактивном режиме, Claude Code всё равно запускает эти хуки, и если ни один хук не вернёт решение, он запрещает вызов инструмента. Для вызова, который доходит до --permission-prompt-tool или callback 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. Для псевдонима manual требуется Claude Code v2.1.200 или новее |
addDirectories |
directories, destination |
Добавляет рабочие каталоги. directories — массив строк путей |
removeDirectories |
directories, destination |
Удаляет рабочие каталоги |
setMode с bypassPermissions действует, только если вы запустили сессию с уже доступным режимом обхода: --dangerously-skip-permissions, --permission-mode bypassPermissions, --allow-dangerously-skip-permissions или permissions.defaultMode: "bypassPermissions" в пользовательских настройках, --settings или управляемых настройках. В противном случае обновление ничего не делает. Обновление также ничего не делает, когда permissions.disableBypassPermissionsMode отключает этот режим или когда сессия запускается в ограниченном режиме.
bypassPermissions никогда не сохраняется как defaultMode независимо от 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, сопоставленный с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
}
}
}
updatedToolOutput изменяет только то, что видит Claude. К моменту срабатывания хука инструмент уже выполнился, поэтому все записанные файлы, выполненные команды или отправленные сетевые запросы уже вступили в силу. Телеметрия, например спаны инструментов OpenTelemetry и аналитические события, также фиксирует исходный вывод до запуска хука. Чтобы предотвратить или изменить вызов инструмента до его выполнения, используйте вместо этого хук PreToolUse.
Значение замены должно соответствовать форме вывода инструмента. Встроенные инструменты возвращают структурированные объекты, а не простые строки. Например, Bash возвращает объект с полями stdout, stderr, interrupted и isImage. Для встроенных инструментов значение, не соответствующее схеме вывода инструмента, игнорируется, и используется исходный вывод. Вывод MCP-инструментов передаётся без проверки схемы. Удаление сведений об ошибках, которые нужны Claude, может привести к тому, что он продолжит работу на основе ложного предположения.
Аннотирование результата для классификатора авторежима
Верните 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 ограничивает заметки для одного вызова инструмента 2 000 символами и обрезает остальное. Ограничение общее для всех хуков, отвечающих на этот вызов
- Только синхронные ответы: Claude Code игнорирует это поле в ответе хука, который выполняется в фоне, поскольку такой ответ приходит после того, как Claude Code записывает результат инструмента
- Вызовы, которые классификатор не записывает: транскрипт классификатора не включает операции только для чтения, такие как чтение файлов и поиск. Claude Code отбрасывает заметку, прикреплённую к одному из таких вызовов
- Взаимодействие с перезаписью: когда заметка описывает вывод, который вы заменяете с помощью
updatedToolOutput, верните оба поля в одном ответе хука. Claude Code отбрасывает заметку, если эта перезапись отклонена или её заменяет перезапись другого хука. Claude Code доставляет заметку, возвращённую без перезаписи, даже когда другой хук перезаписывает вывод
Классификатор воспринимает содержимое, которое вы помещаете в classifierContext, как информацию от приложения, в котором размещена сессия, поэтому не копируйте в него недоверенный вывод инструментов или сторонний текст. Ограничьте заметку коротким утверждением об этом конкретном вызове, например фактом о его происхождении или утверждением пользователя о нём; не используйте это поле для доставки несвязанных сообщений или потока событий.
PostToolUseFailure
Запускается, когда инструмент, начавший выполнение, завершается сбоем: инструмент выбросил ошибку или MCP-инструмент вернул результат с ошибкой. Используйте его для записи сбоев в лог, отправки оповещений или передачи Claude корректирующей обратной связи.
Сопоставляется по имени инструмента, значения те же, что и для PreToolUse.
Это событие не срабатывает для вызовов инструментов, отклонённых до выполнения: неизвестное имя инструмента, входные данные, не прошедшие проверку схемы или специфичную для инструмента проверку, или отказ в разрешении. Отклонения при проверке возвращаются как результаты tool_use_error и происходят до запуска хуков, поэтому они не вызывают ни PreToolUse, ни PostToolUseFailure. Отказы в разрешении вызывают PreToolUse, но не это событие; см. PermissionDenied.
Входные данные 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 это означает текст с префиксами номеров строк, а не необработанное содержимое файла. Ответы могут быть большими, поэтому разбирайте только нужные поля.
Форма tool_response отличается от формы в PostToolUse. PostToolUse передаёт структурированный объект Output инструмента, например {filePath: "...", type: "create"} для Write; PostToolBatch передаёт сериализованное содержимое tool_result, которое видит модель.
Управление решениями 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 или новее.
Типы permission_prompt, idle_prompt, elicitation_dialog и elicitation_url_dialog используют те же тайминги, что и уведомления рабочего стола, поэтому в терминальных сессиях вы видите их, только когда, судя по всему, отошли от терминала:
- Ожидайте
permission_prompt, когда вы ничего не вводили около шести секунд. Таймер запускается при появлении запроса разрешения, и каждое нажатие клавиши откладывает его. Чтобы запускать хук сразу, когда Claude запрашивает разрешение на использование инструмента, используйте вместо этого PermissionRequest. - Ожидайте
idle_promptпримерно через 60 секунд после того, как Claude закончит отвечать, и только если с тех пор вы ничего не вводили и ни один фоновый агент, например фоновый субагент, ещё не работает. Claude Code не отправляетidle_prompt, пока ожидает сброса лимита использования claude.ai. Когда ожидание заканчивается само по себе, вместо этого срабатывает один из типовquota_auto_resume_*. - Ожидайте
elicitation_dialogдля формы запроса данных илиelicitation_url_dialogдля запроса URL в браузере, когда вы ничего не вводили около шести секунд. Оба используют тот же шестисекундный порог, что иpermission_prompt: таймер запускается при появлении диалогового окна, и каждое нажатие клавиши откладывает его.
Запрос разрешения или запрос данных, поступивший, пока на экране открыто другое диалоговое окно, сохраняет тот же шестисекундный порог, отсчитываемый с момента поступления запроса. Уведомление о нём может дойти до вас, пока запрос всё ещё ожидает за открытым диалоговым окном.
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, сопоставленный с 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 |
Устаревшее. Имя команды, производное от сессии; будет удалено в будущем выпуске |
Управление решениями 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 |
Устаревшее. Имя команды, производное от сессии; будет удалено в будущем выпуске |
Управление решениями 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.
Команда /goal — встроенный ярлык для хука Stop на основе промпта с областью действия сессии. Используйте её, когда хотите, чтобы Claude продолжал работать над достижением условия, без написания конфигурации хука.
Входные данные Stop
Помимо общих входных полей, хуки Stop получают stop_hook_active, last_assistant_message, background_tasks и session_crons. Поле stop_hook_active равно true, когда Claude Code уже продолжает работу в результате хука остановки. Проверяйте это значение или обрабатывайте транскрипт, чтобы избежать блокировки по условию, которое никогда не разрешится. Claude Code применяет ограничение в 8 последовательных продолжений: после того как хуки остановки продолжили ход восемь раз подряд, Claude Code переопределяет следующую блокировку и завершает ход. Чтобы повысить ограничение, задайте 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 |
Устаревшее. Имя команды, производное от сессии; будет удалено в будущем выпуске |
Управление решениями 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 "*" тоже совпадает с каждым файлом, но 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 не обрабатывается. Если вам нужно скопировать локальные файлы конфигурации, например .env, в новый worktree, сделайте это в своём скрипте хука.
Хук должен вернуть путь к созданному каталогу 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 не используют стандартную модель решений allow/block. Вместо этого результат определяется успехом или неудачей хука. Хук должен вернуть путь к созданному каталогу worktree:
- Командные хуки (
type: "command"): выведите путь последней непустой строкой stdout. Claude Code удаляет escape-последовательности 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
Выполняется при удалении worktree. Это парный хук очистки для WorktreeCreate. Событие срабатывает, когда:
- вы выходите из сессии
--worktreeи выбираете её удаление - завершается субагент с
isolation: "worktree" - вы удаляете фоновую сессию, worktree которой создал хук
Для worktree на основе git Claude Code выполняет очистку автоматически с помощью git worktree remove. Если вы настроили хук WorktreeCreate, добавьте к нему хук WorktreeRemove, чтобы управлять очисткой создаваемых им worktree:
- Нет хука WorktreeRemove: когда вы выходите из сессии
--worktreeи выбираете удаление, Claude Code использует как резервный вариантgit worktree remove --forceдля пути, который вернул ваш хук WorktreeCreate, поэтому worktree, который распознаёт git, удаляется. Worktree, который git не распознаёт, например созданный вашим хуком с помощью системы контроля версий, отличной от git, остаётся на диске. О том, что происходит с созданным хуком worktree при удалении фоновой сессии, см. правила удаления в agent view. - Хук завершается с кодом 0: worktree считается удалённым. Claude Code больше ничего не читает из хука, поэтому убедитесь, что ваш хук удалил каталог.
- Хук завершается с ненулевым кодом: удаление завершается ошибкой, если каталог по пути
worktree_pathпосле этого всё ещё существует, и worktree остаётся на диске без резервного варианта через git. Хук, удаливший каталог перед завершением с ненулевым кодом, считается выполнившим удаление. О том, как сообщается об ошибке, см. Входные данные 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"
}
]
}
]
}
}
Зарегистрируйте командный хук, который запускает скрипт через PowerShell:
{
"hooks": {
"PreModelSwitch": [
{
"matcher": "claude-opus-4-6",
"hooks": [
{
"type": "command",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-opus-46.ps1"
]
}
]
}
]
}
}
Сохраните этот скрипт в .claude/hooks/block-opus-46.ps1 в вашем проекте:
$hookInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
if ($hookInput.to_model -match 'opus-4-6') {
[Console]::Error.WriteLine('Opus 4.6 is retired for this project. Use a newer model.')
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" |
Только /model в интерактивной сессии может показать запрос "ask". Во всех остальных интерфейсах, включая неинтерактивный режим с флагом -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 после любого из следующих изменений:
- Переключение, запрошенное вами или клиентом
- Автоматическое переключение на резервную модель, которое меняет модель сессии
- Настройка, например
opusplan, при входе в режим планирования или выходе из него - Восстановление модели Claude Code при возобновлении сессии
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.
Для elicitation в режиме формы, наиболее распространённого случая:
{
"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" }
}
}
}
Для elicitation в режиме 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
Чтобы ответить программно без показа диалогового окна, верните JSON-объект с hookSpecificOutput:
{
"hookSpecificOutput": {
"hookEventName": "Elicitation",
"action": "accept",
"content": {
"username": "alice"
}
}
}
| Поле | Значения | Описание |
|---|---|---|
action |
accept, decline, cancel |
Принять, отклонить или отменить запрос |
content |
object | Значения полей формы для отправки. Используется только когда action равно accept |
Код выхода 2 отклоняет elicitation. Claude Code нигде не показывает ваше сообщение stderr.
Claude Code использует hookSpecificOutput из JSON-вывода хука Elicitation и отбрасывает systemMessage и continue.
ElicitationResult
Выполняется после того, как пользователь отвечает на elicitation MCP. Хуки могут наблюдать, изменять или блокировать ответ до его отправки обратно MCP-серверу.
Поле 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",
"elicitation_id": "elicit-123"
}
Вывод ElicitationResult
Чтобы переопределить ответ пользователя, верните JSON-объект с hookSpecificOutput:
{
"hookSpecificOutput": {
"hookEventName": "ElicitationResult",
"action": "decline",
"content": {}
}
}
| Поле | Значения | Описание |
|---|---|---|
action |
accept, decline, cancel |
Переопределяет действие пользователя |
content |
object | Переопределяет значения полей формы. Имеет смысл только когда action равно accept |
Код выхода 2 блокирует ответ, меняя фактическое действие на decline. Claude Code нигде не показывает ваше сообщение stderr.
Claude Code использует hookSpecificOutput из JSON-вывода хука ElicitationResult и отбрасывает systemMessage и continue.
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):
PermissionDeniedPostToolBatchPostToolUsePostToolUseFailurePreToolUseStopSubagentStopTaskCompletedTaskCreatedTeammateIdleUserPromptExpansionUserPromptSubmit
PermissionRequest поддерживает command, http, mcp_tool и prompt hooks, но не agent hooks. Если вы настроите agent hook на этом событии, Claude Code пропустит его и поток разрешений продолжится без изменений. Чтобы разрешить или отклонить из hook, верните объект решения из command или HTTP hook.
События, которые поддерживают command, http и mcp_tool hooks, но не prompt или agent:
ConfigChangeCwdChangedDirectoryAddedElicitationElicitationResultFileChangedInstructionsLoadedMessageDisplayNotificationPostCompactPostModelSwitchPreCompactPreModelSwitchSessionEndStopFailureSubagentStartWorktreeCreateWorktreeRemove
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:
- Отправляют входные данные hook и вашу подсказку модели Claude, по умолчанию той, которую Claude Code использует для фоновой функциональности
- LLM отвечает структурированным JSON, содержащим решение
- Claude Code автоматически обрабатывает решение
Prompt hook configuration
Установите type на "prompt" и предоставьте строку prompt вместо command. Используйте заполнитель $ARGUMENTS для внедрения данных JSON входа hook в текст вашей подсказки.
Этот 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"независимо отcontinuePostToolUseFailureиTaskCreated: причина возвращается Claude как ошибка инструмента и ход продолжается, независимо отcontinueOnBlockTaskCompleted: когда он срабатывает, потому что задача отмечена как завершённая во время хода, причина возвращается 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 hooks являются экспериментальными. Поведение и конфигурация могут измениться в будущих выпусках. Для рабочих процессов в производстве предпочитайте command 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:
- Claude Code порождает subagent с вашей подсказкой и JSON входом hook
- Subagent может использовать инструменты, такие как Read, Grep и Glob, для исследования
- После до 50 оборотов subagent возвращает структурированное решение
{ "ok": true/false } - 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 только во время работы сеанса:
- В неинтерактивном режиме с флагом
-pClaude 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.
Соображения безопасности
Отказ от ответственности
Command hooks выполняют команды оболочки с вашими полными разрешениями пользователя. Они могут изменять, удалять или получать доступ к любым файлам, к которым может получить доступ ваша учётная запись пользователя. Проверьте и протестируйте все команды 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.