SpyBara
Go Premium

hooks.md 2026-10-29 23:57 UTC to 2026-10-30 23:01 UTC

This page contains 411 additions and 411 deletions.

2026
Thu 1 23:59 Fri 2 22:59 Sat 3 23:57 Sun 4 23:58 Mon 5 23:58 Tue 6 23:59 Wed 7 23:59 Thu 8 22:58 Fri 9 23:02 Sat 10 23:01 Sun 11 23:57 Wed 14 22:58 Fri 16 22:59 Sat 17 23:57 Mon 19 23:58 Tue 20 23:00 Wed 21 23:02 Thu 22 22:58 Fri 23 22:59 Sat 24 22:59 Sun 25 23:58 Mon 26 22:57 Tue 27 22:59 Thu 29 23:57 Fri 30 23:01

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

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

[!TIP]

Для краткого руководства с примерами см. Автоматизация рабочих процессов с помощью hooks.

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

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

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

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

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

  • один раз за сеанс: SessionStart и SessionEnd
  • один раз за ход: UserPromptSubmit, Stop и StopFailure
  • при каждом вызове инструмента внутри агентного цикла: PreToolUse и PostToolUse, за исключением вызовов EndConversation, которые пропускают оба

Диаграмма жизненного цикла hook, показывающая опциональный Setup, переходящий в SessionStart, затем цикл за ход, содержащий UserPromptSubmit, UserPromptExpansion для slash commands, вложенный агентный цикл (PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart/Stop, TaskCreated, TaskCompleted) и Stop или StopFailure, за которым следуют TeammateIdle, PreCompact, PostCompact и SessionEnd, с Elicitation и ElicitationResult вложенными внутри выполнения MCP tool, PermissionDenied как боковая ветвь от PermissionRequest для автоматических отказов, WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, FileChanged и DirectoryAdded как отдельные асинхронные события, PreModelSwitch как отдельное последовательное событие, которое запускается перед запрошенным переключением модели, PostModelSwitch как отдельное асинхронное событие, которое запускается после изменения модели сеанса, и MessageDisplay как событие только для отображения, которое запускается во время потоковой передачи текста сообщения помощника

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

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

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

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

[tab] macOS/Linux

Фильтр 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 перед попыткой их использования.

[tab] Windows (PowerShell)

Фильтр 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. Вот что происходит:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[!NOTE]

На этой странице используются специальные термины для каждого уровня: 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 совпадает с объединённым allowlist
  • httpHookAllowedEnvVars: когда определено, Claude Code интерполирует только переменные окружения из этого списка в заголовки hook

Matcher patterns

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

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

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

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

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

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

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

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

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

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

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

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

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

Match MCP tools

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

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

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

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

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

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

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

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

Hook handler fields

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

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

Когда срабатывает событие, его совпадающие хуки запускаются параллельно. Если более 12 из них — хуки-промпты или хуки-агенты, одновременно запускаются 12 из них, а остальные стартуют по мере завершения выполняющихся. timeout каждого ожидающего хука начинает отсчитываться, когда этот хук запускается.

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

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

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

Common fields

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

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

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

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

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

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

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

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

Command hook fields

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

Поле Обязательно Описание
command да Команда оболочки для выполнения. С args, исполняемый файл для прямого запуска. См. Exec form and shell form
args нет Список аргументов. Когда присутствует, command разрешается как исполняемый файл и запускается напрямую с args как вектор аргументов, без участия оболочки. См. Exec form and shell form
async нет Если true, запускается в фоне без блокировки. См. Run hooks in the background
asyncRewake нет Если true, запускается в фоне и пробуждает Claude при коде выхода 2. Hook stderr или stdout, если stderr пусто, показывается Claude как системное напоминание чтобы он мог реагировать на долгоживущий фоновый сбой
shell нет Оболочка для использования для этого hook. Принимает "bash" или "powershell". По умолчанию "bash", или "powershell" на Windows когда Git Bash не установлен. Установка "powershell" запускает команду через PowerShell на Windows. Не требует CLAUDE_CODE_USE_POWERSHELL_TOOL, так как hooks порождают PowerShell напрямую. Игнорируется когда установлен args
onFailure нет Выполняется ли действие, вызвавшее хук, при сбое хука: "continue" или "block". О значении "block" и о случае, когда поле не задано, см. Блокировка действия при сбое хука. Требует Claude Code v2.1.295 или новее
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.

[!NOTE]

На 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.*}.

[!NOTE]

В 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 нет Список имён переменных окружения, которые могут быть интерполированы в значения заголовков. Ссылки на неуказанные переменные заменяются пустыми строками. Требуется для любой интерполяции переменных окружения
onFailure нет Что происходит с действием при сбое хука: "continue" (по умолчанию) или "block". См. Блокировка действия при сбое хука. Требует Claude Code v2.1.295 или новее

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}: каталог постоянных данных плагина, для зависимостей и состояния, которые должны пережить обновления плагина.

[!NOTE]

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 хука — это корень worktree после того, как Claude входит в worktree, и новый каталог после того, как Claude выполняет cd. Читайте его, когда хуку нужно знать, в каком каталоге работает Claude.

О том, куда указывает ${CLAUDE_PROJECT_DIR} в сессии worktree, запущенной из десктопного приложения, см. Что worktree разделяют с основным checkout.

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

[tab] Project scripts

Этот пример использует ${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": []
          }
        ]
      }
    ]
  }
}

[tab] Plugin scripts

Определите 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 субагента проекта запускаются только после того, как вы примете диалоговое окно доверия к рабочему пространству для папки, из которой взят файл агента. Сессия с -p не считается принятием. В разделе Что запускается до того, как вы доверитесь папке это сравнивается с правилом для файлов настроек, а на странице о субагентах перечислено, какие области действия являются исключениями.

Ограничения хуков скиллов

Если хуки из frontmatter скилла не срабатывают после того, как вы или Claude вызвали скилл, выполните /hooks в этой сессии. Если они есть в списке, см. Хук не срабатывает. Если ни одного из них в списке нет, возможно, скилл попал под блокировку «только плагины» или превысил лимит хуков и работает без всех своих хуков из frontmatter. Проверьте каждый случай:

  • Блокировка «только плагины»: администратор вашей организации может ограничить хуки плагинами и управляемыми настройками с помощью управляемой настройки strictPluginOnlyCustomization. При такой блокировке скилл сохраняет свои хуки из frontmatter, только если это скилл плагина, встроенный скилл или скилл из каталога управляемых настроек. Хуки из ваших собственных файлов настроек тоже не запускаются.
    • Проверка: введите /status в сессии Claude Code. Если строка Setting sources содержит Enterprise managed settings, спросите администратора, блокирует ли strictPluginOnlyCustomization хуки.
    • Решение: перенесите скилл в плагин, потому что скиллы плагинов сохраняют свои хуки из frontmatter. Добавьте плагин в маркетплейс и установите его оттуда. Если Claude Code отклоняет маркетплейс с сообщением is blocked by enterprise policy, попросите администратора разрешить этот маркетплейс.
  • Лимит хуков: в Claude Code v2.1.304 и новее у каждого источника скиллов есть собственный лимит на сессию. Ваш проект, ваша папка личных скиллов и каждый плагин считаются отдельными источниками. Скиллы из одного источника могут зарегистрировать не более 1 024 хуков. Matcher этих хуков в сумме могут содержать не более 16 384 символов. Matcher, под которым несколько хуков, учитывается по одному разу для каждого хука.
    • Проверка: повторите сессию, в которой хук не сработал, с включённым отладочным логированием:
      1. Запустите новую сессию из оболочки командой claude --debug. Если вы не запускаете сессии из оболочки, вместо этого первым делом введите /debug в новой сессии.
      2. Вызовите те же скиллы в том же порядке, включая те, которые вызывал Claude, потому что обе суммы накапливаются скилл за скиллом. Попросите Claude вызвать любой скилл, который вы не можете ввести как команду.
      3. Найдите в отладочном логе текст registers none of its hooks. Строка с этим текстом называет скилл, превысивший лимит.
    • Решение: объявите меньше хуков или более короткие matcher в этом скилле или в других скиллах из того же источника, затем начните новую сессию. Если эти скиллы не ваши и вы не можете их редактировать, попросите их автора внести такое изменение. До тех пор начинайте новую сессию и вызывайте нужный скилл раньше любого другого скилла из того же источника.

Если ни один из случаев к вам не относится, проверьте, нет ли frontmatter, который не удалось разобрать.

Меню /hooks

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

agent_id и agent_type сообщают вашему скрипту, в каком агенте сработал хук, например в субагенте, участнике команды, работающем в том же процессе, или агенте, выбранном с помощью --agent:

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

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

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

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

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

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

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

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

Когда выполнение вашего хука завершается, Claude Code по его коду выхода решает, продолжать ли действие, которое вызвало хук, например вызов инструмента или промпт:

  • Успех: ваш хук завершается с кодом 0. Claude Code применяет все поля вывода JSON, которые вывел ваш хук, и действие выполняется, если эти поля не блокируют и не отклоняют его. Исключение описано в разделе Хуки, запускающие фоновый процесс.
  • Блокирующая ошибка: ваш хук завершается с кодом 2. Для событий, которые могут блокировать, Claude Code останавливает действие.
  • Неблокирующая ошибка: ваш хук завершается с любым другим кодом или завершается сбоем иным образом, например не запускается или выводит некорректный JSON. Действие выполняется, за исключением хуков, сбой которых блокирует действие, например хука с onFailure, установленным в "block". Для таких событий, как PreToolUse, в транскрипте отображается уведомление <hook name> hook error.

То, что ваш хук выводит в stdout, может изменить результат. Например, если хук PreToolUse завершается с кодом 1, но выводит JSON, прошедший проверку, этот JSON всё равно может определить, что произойдёт. Чтобы узнать результат вашего хука для такого события, как PreToolUse, найдите в первом столбце то, что он вывел в stdout, а в верхней строке — его код выхода:

Stdout Выход с кодом 0 Выход с кодом 2 Любой другой код выхода
Объект JSON, прошедший проверку схемы Успех. Поля применяются Блокирующая ошибка. Claude Code всё равно читает поля, но они не могут переопределить блокировку Успех. Claude Code игнорирует код выхода, и результат определяют только поля. Для хуков, сбой которых блокирует действие, запуск вместо этого считается сбоем
JSON, который не удаётся разобрать или который не прошёл проверку схемы Неблокирующая ошибка. Уведомление содержит сообщение о разборе или проверке Блокирующая ошибка. Причиной служит ваш stderr Неблокирующая ошибка. Уведомление содержит сообщение о разборе или проверке
Обычный текст или ничего Успех Блокирующая ошибка. Причиной служит ваш stderr Неблокирующая ошибка. Уведомление содержит первую строку вашего stderr

У некоторых событий собственные правила:

  • WorktreeCreate: любой ненулевой код выхода приводит к ошибке создания worktree, что бы ни содержал ваш JSON.
  • WorktreeRemove: любой ненулевой код выхода приводит к ошибке удаления worktree, если каталог после этого всё ещё существует.
  • Stop, SubagentStop, TaskCompleted и хук UserPromptSubmit плагина: если ваш хук завершается с кодом 2, ничего не выводит в stdout, а его stderr сообщает об отсутствии файла, например No such file or directory, Claude Code обрабатывает запуск как неблокирующую ошибку.
  • Elicitation и ElicitationResult: Claude Code применяет ваш hookSpecificOutput, когда хук завершается с кодом 0, и игнорирует его при любом другом коде выхода.
  • События, отбрасывающие вывод хука, такие как StopFailure: Claude Code игнорирует ваш JSON при любом коде выхода, за исключением полей с побочными эффектами, таких как terminalSequence, которые всё равно срабатывают.

Чтобы узнать, что делает код выхода 2 для вашего события, смотрите Поведение кода выхода 2 для каждого события. Чтобы узнать, какие поля решений оно учитывает, смотрите Управление решениями.

Код выхода 0

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

Чтобы исправить вывод, воспользуйтесь сообщением о разборе или проверке, которое содержит уведомление <hook name> hook error. Для хука, который не является асинхронным, ваш stdout считается выводом JSON, только если вы выводите один объект JSON на одной или нескольких строках, вокруг которого нет ничего, кроме пробельных символов. Любой другой stdout Claude Code воспринимает как обычный текст или как ошибку разбора:

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

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

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

Код выхода 2

Завершитесь с кодом 2, чтобы заблокировать действие. Для событий, которые могут блокировать, Claude Code останавливает действие: например, хук PreToolUse блокирует вызов инструмента, а хук UserPromptSubmit отклоняет промпт.

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

Выход с кодом 2 блокирует, даже если ваш хук выводит JSON:

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

Этот скрипт блокирует команды 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

Если этот скрипт зарегистрирован как хук PreToolUse для Bash, команда, начинающаяся с rm, блокируется, а Claude получает stderr хука как ошибку инструмента с префиксом из имени события, имени инструмента и команды хука:

PreToolUse:Bash hook error: [${CLAUDE_PROJECT_DIR}/.claude/hooks/no-rm.sh]: Blocked: rm commands are not allowed

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

Уведомление <hook name> hook error с Failed with non-blocking status code: означает, что код выхода вашего хука не был равен 0 или 2, а его stdout содержал обычный текст или был пустым. Если хук не относится к тем, сбой которых блокирует действие, это неблокирующая ошибка, поэтому ваш хук не остановил действие. Чтобы получить полный stderr вашего хука, а не только первую строку, которую показывает уведомление, включите отладочное логирование.

Например, если хук PreToolUse для Bash выводит something broke в stderr и завершается с кодом 1, уведомление PreToolUse:Bash hook error содержит такую строку:

Failed with non-blocking status code: something broke

Если уведомление содержит сообщение оболочки No such file or directory, как в следующей строке, хук так и не был запущен, например потому что путь к его скрипту не существует:

Failed with non-blocking status code: /bin/sh: /path/to/hook.sh: No such file or directory

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

Настраивая хук для политики, следите за этим уведомлением при его первом запуске, потому что опечатка в пути в settings.json означает, что хук никогда не выполнится. Чтобы блокировать действие, когда хук не удаётся запустить, установите onFailure: "block".

[!WARNING]

Код выхода 1 без корректного вывода JSON является неблокирующей ошибкой, если хук не относится к тем, сбой которых блокирует действие, хотя 1 — общепринятый код ошибки в Unix. Если ваш хук предназначен для соблюдения политики, используйте exit 2.

Таймауты

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

Для PreToolUse хук command, http или mcp_tool, превысивший таймаут, не блокирует вызов инструмента, за исключением хуков, сбой которых блокирует действие. Вызов продолжается через обычный процесс разрешений, поэтому не рассчитывайте, что зависший хук сработает как защитный барьер. Чтобы блокировать вызов при превышении таймаута хуком command или http, установите onFailure: "block". Callback-хук Agent SDK, превысивший свой таймаут, блокирует вызов инструмента.

Хуки, запускающие фоновый процесс

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

Выводите решение до запуска фонового процесса и перенаправляйте stdout и stderr этого процесса. Если ваш скрипт выводит разрешение, а затем запускает фоновый процесс, не перенаправив его stdout и stderr, Claude Code блокирует вызов инструмента. Ошибка хука содержит refusing to honor the suspect capture.

Этот скрипт хука PreToolUse выводит решение с помощью policy-check.sh, а затем загружает метрики в фоновом режиме:

#!/bin/bash
"$CLAUDE_PROJECT_DIR"/.claude/hooks/policy-check.sh || exit $?
"$CLAUDE_PROJECT_DIR"/.claude/hooks/upload-metrics.sh >/dev/null 2>&1 &

Остальная часть скрипта делает следующее:

  • || exit $?: делает код выхода проверки собственным кодом выхода хука, поэтому проверка, завершающаяся с кодом 2 для отказа, по-прежнему блокирует. Иначе хук завершается с кодом последней строки, то есть 0.
  • >/dev/null 2>&1 &: запускает upload-metrics.sh в фоновом режиме с перенаправленным выводом, чтобы он не удерживал вывод хука открытым.

Чтобы проверить скрипт, сделайте так, чтобы policy-check.sh завершался с кодом 2, а затем вызовите инструмент, которому соответствует хук. Вызов инструмента будет заблокирован.

Блокировка действия при сбое хука

Для большинства событий, когда хук завершается сбоем или превышает таймаут, Claude Code всё равно выполняет действие. В результате хук политики с неверным путём или падающим скриптом пропускает всё. Чтобы вместо этого блокировать действие, установите "onFailure": "block" для хука command или http. Требуется Claude Code v2.1.295 или новее.

[!NOTE]

В Windows без Git Bash командный хук PreToolUse или PermissionRequest в форме оболочки, завершившийся сбоем при незаданном onFailure, может вместо этого заблокировать вызов инструмента.

Этот хук PreToolUse в .claude/settings.json запускает скрипт проекта перед каждой командой Bash и блокирует команду, если скрипт завершается сбоем:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node",
            "args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js"],
            "onFailure": "block"
          }
        ]
      }
    ]
  }
}

Чтобы проверить его, не создавайте check-command.js и попросите Claude выполнить команду Bash, например ls. Claude Code блокирует вызов, и ошибка содержит failed; blocking because onFailure is "block", за которым следует собственный вывод ошибки node, сокращённый здесь до одной строки:

PreToolUse:Bash hook error: [node ${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js]: failed; blocking because onFailure is "block"
Error: Cannot find module '/path/to/project/.claude/hooks/check-command.js'

После таймаута сообщение содержит timed out вместо failed. Без заданного onFailure тот же отсутствующий скрипт является неблокирующей ошибкой, и ls выполняется.

Каждый из следующих случаев считается сбоем:

  • Не удаётся запустить: командный хук не запускается, например потому что скрипт или исполняемый файл не существует
  • Код выхода, отличный от 0 или 2: учитывается для командного хука, даже если он вывел JSON, разрешающий действие, например permissionDecision: "allow". Чтобы вернуть решение в JSON, завершитесь с кодом 0
  • Ошибка HTTP: соединение HTTP-хука не удаётся установить, или статус ответа не 2xx
  • Таймаут: хук достигает своего timeout
  • Некорректный вывод: вывод JSON не удаётся разобрать или он не проходит проверку схемы. Для HTTP-хука сбоем также считается тело ответа 2xx, которое не является ни пустым, ни объектом JSON. Обычный текст в stdout командного хука сбоем не считается

При установленном "block" сбой действует так же, как код выхода 2 для этого события, за исключением PermissionRequest, где он отклоняет запрос. Например, сбой PreToolUse блокирует вызов инструмента, а сбой UserPromptSubmit блокирует промпт.

Поле не действует для следующих хуков:

  • Хуки Stop, SubagentStop, TaskCompleted и TeammateIdle: код выхода 2 для этих событий отправляет Claude продолжать работу, а Claude не может исправить хук, который не запускается
  • Фоновые командные хуки: командные хуки, в которых задано async или asyncRewake

Сбой хуков в Windows без Git Bash

Если ошибка заблокированного вызова инструмента содержит этот текст, командный хук PreToolUse или PermissionRequest завершился сбоем или превысил таймаут и заблокировал вызов:

blocking because Git Bash wasn't found, so only exit code 0 or 2 counts as this hook's verdict.

Если ошибка также содержит PowerShell started no program, начните с исправлений для command, состоящего только из строк в кавычках, которые PowerShell выводит вместо того, чтобы выполнять.

Без Git Bash любой код выхода этого хука, отличный от 0 или 2, считается сбоем, даже если хук выводит JSON, который не отклоняет вызов. Чтобы узнать причину сбоя хука, проверьте отладочный лог. Затем выберите исправление, подходящее для вашего хука:

  • Действие должно выполняться при сбое хука: добавьте в хук "onFailure": "continue"
  • Хук завершается с кодом 1 для предупреждения, но должен блокировать при падении: с "onFailure": "continue" вызов выполняется при коде выхода 1 — как при предупреждении, так и при падении. Вместо этого добавьте "onFailure": "block" и завершайте скрипт с кодом 0 там, где он выдаёт предупреждение, чтобы такие вызовы выполнялись. Чтобы по-прежнему показывать предупреждение для PreToolUse, выведите его в stdout в поле systemMessage одного объекта JSON.
  • Хук написан для Bash: без Git Bash хук в форме оболочки без заданного shell выполняется в PowerShell, где он может завершиться сбоем. Установите Git for Windows или задайте CLAUDE_CODE_GIT_BASH_PATH, если Git Bash установлен там, где Claude Code его не находит. Затем начните новую сессию.
  • Хук запускает один файл .exe, а не файл .cmd или .bat: укажите программу в command, а её аргументы в args, чтобы использовать форму exec, и добавьте "onFailure": "block". Тогда Claude Code запускает программу без оболочки. Без "onFailure": "block" вызов может выполниться, если программа завершается сбоем, отсутствует или превышает таймаут
  • Защитный хук PowerShell, который должен блокировать при сбое: при незаданном onFailure он перестаёт это делать, как только установлен Git for Windows и Claude Code находит Git Bash. Добавьте "onFailure": "block", чтобы сбой или таймаут блокировал вызов как с Git Bash, так и без него. Также задайте "shell": "powershell", чтобы хук и дальше выполнялся в PowerShell, а не в Git Bash

До версии v2.1.309 сообщение выглядело так: blocking because Git Bash wasn't found and this hook doesn't set "shell" to "powershell", so only exit code 0 or 2 counts as its verdict.

PowerShell started no program

Хук с этой ошибкой выполнялся в PowerShell, который выводит command, состоящий только из строк в кавычках, например "command": "\"C:\\Tools\\guard.exe\"", вместо того чтобы его выполнить.

  • Чтобы вывести текст, например JSON: задайте "shell": "powershell", и хук завершится с кодом 0
  • Чтобы запустить файл .ps1, .cmd или .bat либо .exe, путь к которому содержит переменную: задайте "shell": "powershell" и поставьте & перед путём в кавычках. Оставьте $HOME как есть, а переменные окружения записывайте так, как пример check.ps1 записывает $env:CLAUDE_PROJECT_DIR
  • Чтобы запустить один .exe по пути без $: удалите внутренние кавычки из command, затем примените исправление для хука, запускающего один .exe, указав "args": [], если программа не принимает аргументов
  • Чтобы запустить скрипт, написанный для Bash: примените исправление для хука, написанного для Bash

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

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

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

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

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

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

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

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

Вывод JSON

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

[!NOTE]

Выберите один подход для каждого хука: либо используйте только коды выхода для сигнализации, либо завершайтесь с кодом 0 и выводите JSON для структурированного управления. Если вы их смешиваете, выход с кодом 2 сохраняет свой блокирующий эффект, а Claude Code по-прежнему читает поля JSON, с единственным исключением для elicitation, отмеченным в разделе Код выхода 2.

Выводите в stdout только объект JSON. Для хука, который не является асинхронным, любой другой текст там, например строка, которую профиль вашей оболочки выводит при запуске, мешает Claude Code прочитать объект как JSON; в разделе JSON хука не действует показано, как найти и убрать этот текст.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Если ваша строка содержит тег <system-reminder> или </system-reminder>, Claude получает строку, в которой < этого тега заменён на &lt;.

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

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

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

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

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

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

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

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

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

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

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

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

[tab] decision верхнего уровня

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

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

[tab] PreToolUse

Использует hookSpecificOutput для более развитого управления: разрешить, отклонить или передать решение пользователю. Вы также можете изменить входные данные инструмента перед его запуском или внедрить дополнительный контекст для Claude. Смотрите Управление решениями PreToolUse, чтобы узнать обо всех доступных параметрах.

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database writes are not allowed"
  }
}

[tab] PermissionRequest

Использует hookSpecificOutput, чтобы разрешить или отклонить запрос разрешения от имени пользователя. При разрешении вы также можете изменить входные данные инструмента или применить правила разрешений, чтобы пользователю не пришлось подтверждать снова. Смотрите Управление решениями PermissionRequest, чтобы узнать обо всех доступных параметрах.

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

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

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

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

SessionStart

Выполняется, когда Claude Code начинает новую сессию или возобновляет существующую. Полезно для загрузки контекста разработки, например существующих issues или недавних изменений в вашей кодовой базе, или для настройки переменных окружения. Для статического контекста, который не требует скрипта, используйте вместо этого 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 или диалог, который вы переводите в фон

Когда вы начинаете интерактивную сессию, возобновляете диалог при запуске с --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

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

Поле Описание
additionalContext Строка, добавляемая в контекст Claude в начале диалога, перед первым промптом. О том, как доставляется текст и что в него включать, см. Добавление контекста для Claude
initialUserMessage Строка, используемая как первое сообщение пользователя в сессии в неинтерактивном режиме с флагом -p. Она становится первым ходом, даже если вы не передаёте промпт. Переданный вами промпт следует следующим ходом
sessionTitle Задаёт название сессии с тем же эффектом, что и /rename. Применяется, когда source равен "startup", "resume" или "fork"
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"
  }
}

Хук, который только добавляет контекст, может просто напечатать его, не формируя JSON, поскольку Claude Code добавляет обычный текстовый stdout хука SessionStart в контекст Claude.

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

Перезагрузка скиллов, которые устанавливает хук

Чтобы скиллы, которые устанавливает хук SessionStart, стали доступны в той же сессии, верните reloadSkills. Обнаружение скиллов обычно выполняется до завершения хуков 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 репозитория — это заглушка. Замените его на свой репозиторий скиллов.

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

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

Чтобы задать отдельные переменные окружения, запишите операторы 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

Каждая команда Bash выполняет содержимое файла как shell-код перед самой командой, поэтому строка в нём может использовать всё, что вычисляет Bash, например ссылку на $PATH в export PATH="$PATH:./node_modules/.bin".

Сохранённые переменные в командах PowerShell

Команды PowerShell тоже получают переменные из CLAUDE_ENV_FILE в Claude Code v2.1.296 или новее, но PowerShell никогда не выполняет этот файл. Вместо этого Claude Code считывает из него присваивания и копирует их в окружение команды PowerShell. Это происходит, только если каждая строка относится к одному из следующих видов — во всём, что записали все хуки в этой сессии, и в любом скрипте, на который вы установили CLAUDE_ENV_FILE до запуска:

  • Пустая строка или комментарий #
  • Одно присваивание в начале строки, записанное как export NAME=value, declare -x NAME=value или NAME=value, со значением, которое Bash использовал бы в точности как написано, составленным из любого сочетания следующих частей: текст без кавычек, использующий только буквы, цифры и символы _ @ % + = : , . / -, текст в одинарных кавычках и текст в двойных кавычках, где любые $, обратная кавычка или " внутри экранированы обратной косой чертой

Если какая-либо строка имеет другой вид, например export PATH="$PATH:./node_modules/.bin" с неэкранированным $PATH, команда source или строки $'...', которые печатает direnv export bash, команды PowerShell не получают ни одной переменной, а claude --debug записывает в лог Session environment is not all plain assignments. Команды Bash по-прежнему получают их все. В Windows команды PowerShell также не получают переменную, значение которой содержит / или \, поскольку Git Bash и Windows записывают пути по-разному. Команда PowerShell, выполняемая в песочнице, не получает ни одной переменной.

[!NOTE]

CLAUDE_ENV_FILE доступна для хуков SessionStart, Setup, CwdChanged и FileChanged. Другие события хуков не имеют доступа к этой переменной, как и хук, выполняемый в PowerShell, будь то через "shell": "powershell" или по умолчанию в Windows без Git Bash.

Setup

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

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

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

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

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

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

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

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

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

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

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

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

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

InstructionsLoaded

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

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

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

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

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

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

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

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

UserPromptSubmit

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

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

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

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

Callback-хук Agent SDK на UserPromptSubmit, достигший таймаута, блокирует промпт с сообщением, в котором указаны хук и таймаут, потому что callback в этом месте может выполнять роль политики, которая не должна пропускать промпт при сбое. Сессия продолжается.

Esc во время проверки промпта

Если вы нажмёте Esc или прервёте сессию, пока хуки ещё проверяют промпт, проверка останавливается, и промпт не доходит до Claude. Текст, который вы ввели в терминале, возвращается в поле ввода промпта, и хуки проверяют его снова, когда вы отправляете его повторно.

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

Входные данные 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"
}

Когда промпт — это введённый вами скилл или пользовательская команда, например /deploy staging, хук получает эту строку в том виде, в каком вы её ввели, а не развёрнутое содержимое скилла. Если хук блокирует промпт, команда не выполняется, поэтому shell-команды, которые скилл вставляет с помощью !`<command>`, тоже не выполняются. Чтобы вместо этого сопоставлять имя или аргументы команды, используйте UserPromptExpansion.

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

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

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

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

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

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

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

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

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

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

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

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

UserPromptExpansion

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

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

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

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

Вывод MessageDisplay

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

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

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

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

[tab] macOS/Linux

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

{
  "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("`"; ""))}}'

[tab] Windows (PowerShell)

Зарегистрируйте командный хук, который запускает скрипт через 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 после изменения, и у них нет управления решениями, поэтому они не могут заблокировать запись.

[!WARNING]

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

Вы можете блокировать определённые пути в ссылках @ или отключить вставку:

  • Блокировка определённых путей: добавьте для них правило запрета Read
  • Отключение вставки: задайте CLAUDE_CODE_DISABLE_FILE_MENTIONS=1 в своей оболочке, пользовательских настройках или управляемых настройках. Тогда Claude получает упомянутые пути с пометкой прочитать их с помощью своих файловых инструментов. Если он это делает, выполняются соответствующие хуки PreToolUse. Для этой переменной требуется Claude Code v2.1.299 или новее, и Claude Code игнорирует её в настройках проекта и локальных настройках.

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

Хук обратного вызова Agent SDK в PreToolUse, превысивший свой таймаут, блокирует вызов инструмента.

Входные данные 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 или новее.

[!NOTE]

Список формируется по принципу «насколько возможно» и доступен в публичной бета-версии. 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" Промпт, применяемый к загруженному содержимому
offset number 100000 Необязательное количество символов, пропускаемых от начала страницы. Claude задаёт его, чтобы продолжить чтение длинной страницы. Требуется Claude Code v2.1.290 или новее

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

Поле Тип Пример Описание
query string "react hooks best practices" Поисковый запрос
allowed_domains array ["docs.example.com"] Необязательно: включать результаты только с этих доменов
blocked_domains array ["spam.example.com"] Необязательно: исключать результаты с этих доменов
mode string "extended" Необязательно: тип поиска. "standard" запрашивает облегчённый поиск, а "extended" — полный поиск. Некоторые сессии не отправляют mode, и вызов без него выполняет полный поиск. Требует Claude Code v2.1.282 или новее
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" для фоновых субагентов. О том, какие субагенты как выполняются, см. Запуск субагентов на переднем плане или в фоне
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"] Использованные модели по порядку, последовательные повторы схлопнуты; задаётся, только если модель менялась во время запуска
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 указывает модель, использовавшуюся в момент перехода агента в фон, поэтому смена модели, произошедшая до перехода в фон, отражается там.

AskUserQuestion

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

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

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

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

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

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

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

Поле Описание
permissionDecision "allow" пропускает запрос разрешения, за исключением действий, которые не одобряет автоматически ни один режим, и чтения из сетевых путей. Оно также не пропускает подтверждение вызовов инструментов, которые приложение Agent SDK помечает как always_ask, если только режим разрешений не одобряет вызов. Для AskUserQuestion и ExitPlanMode нужен updatedInput в паре с "allow". О режиме планирования см. Одобрение хуками в режиме планирования. "deny" предотвращает вызов инструмента. "ask" просит пользователя подтвердить. "defer" завершает ход, чтобы инструмент можно было возобновить позже, и требует неинтерактивного режима с флагом -p. Правила запрета и запроса подтверждения всё равно проверяются независимо от того, что возвращает хук
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 скилла. Это помогает пользователям понять, какой источник конфигурации запрашивает подтверждение.

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

[!NOTE]

Ранее PreToolUse использовал поля decision и reason верхнего уровня, но для этого события они объявлены устаревшими. Используйте вместо них hookSpecificOutput.permissionDecision и hookSpecificOutput.permissionDecisionReason. Устаревшие значения "approve" и "block" соответствуют "allow" и "deny". Другие события, такие как PostToolUse и Stop, по-прежнему используют decision и reason верхнего уровня в качестве текущего формата.

Решения ask в PreToolUse в авторежиме

В авторежиме "ask" хука PreToolUse обычно отправляет вызов инструмента в запрос разрешения, и классификатор по нему не решает. Некоторые вызовы идут другим путём:

  • Вызовы, которые также помечают собственные проверки Claude Code: к ним относятся запись в защищённые пути и команды, запрашивающие выполнение вне песочницы. Классификатор может отклонить такой вызов до появления какого-либо запроса. Если он одобряет, вызов обычно всё равно попадает в запрос.
  • Вызовы SubagentHandback: этот инструмент передаёт отчёт субагента. Для него запрос не появляется, и вызов выполняется, даже если классификатор возражает.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Таймаута или ограничения на повторные попытки нет. Сессия остаётся на диске, пока вы её не возобновите, с учётом очистки по сроку хранения cleanupPeriodDays, которая по умолчанию удаляет файлы сессий через 30 дней согласно правилам очистки по сроку хранения.

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

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

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

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

[!NOTE]

Чтобы возобновить отложенную сессию в режиме планирования, передайте --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
addDirectories directories, destination Добавляет рабочие каталоги. directories — массив строк с путями
removeDirectories directories, destination Удаляет рабочие каталоги

[!NOTE]

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 с matcher Edit|Write, когда тот же файл перезаписывает команда Bash или процесс вне Claude Code.

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

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

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

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

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

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

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

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

[!WARNING]

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

[!WARNING]

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

PostToolUseFailure

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

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

[!NOTE]

Это событие не срабатывает для вызовов инструментов, отклонённых до выполнения: неизвестное имя инструмента, входные данные, не прошедшие проверку схемы или специфичную для инструмента валидацию, или отказ в разрешении. Отклонения при валидации возвращаются как результаты 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 это означает текст с префиксами номеров строк, а не необработанное содержимое файла. Ответы могут быть большими, поэтому разбирайте только нужные поля.

[!NOTE]

Форма 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
plugin_notification Мод выводит уведомление с помощью $.ui.notify. title хука — это имя мода, если мод не задал его сам

Для типов 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 или новее.

[!NOTE]

Типы 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 с matcher на SubagentHandback получает как tool_input.message.

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

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

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

TaskCreated

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

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

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

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

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

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

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

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

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

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

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

exit 0

TaskCompleted

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

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

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

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

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

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

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

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

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

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

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

exit 0

Stop

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

[!TIP]

Команда /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 вызывает инструмент. Чтобы повысить ограничение, установите CLAUDE_CODE_STOP_HOOK_BLOCK_CAP.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

StopFailure

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

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

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

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

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

TeammateIdle

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

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

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

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

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

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

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

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

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

#!/bin/bash

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

exit 0

ConfigChange

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

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

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

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

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

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

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

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

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

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

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

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

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

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

CwdChanged

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

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

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

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

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

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

Вывод CwdChanged

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

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

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

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

DirectoryAdded

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

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

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

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

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

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

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

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

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

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

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

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

FileChanged

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

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

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

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

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

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

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

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

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

Запускается, когда Claude Code создаёт worktree, например в таких случаях:

Хук WorktreeCreate не запускается для worktree, который Claude добавляет через Bash, а хук плагина не запускается для worktree, создаваемого при запуске с --worktree и --tmux:

  • git worktree add через Bash: чтобы проверить или заблокировать эту команду, добавьте хук PreToolUse с matcher Bash.
  • --worktree с --tmux: Claude Code создаёт worktree до загрузки плагинов, поэтому хук WorktreeCreate из плагина не запускается. Чтобы ваш хук запускался с обоими флагами, определите его в файле настроек. До v2.1.300 хук WorktreeCreate плагина не запускался и с одним только --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. Это slug-идентификатор нового worktree, заданный пользователем или сгенерированный автоматически, например bold-oak-a3f2.

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

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

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

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

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

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

WorktreeRemove

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

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

Когда субагент или агент рабочего процесса завершает работу, Claude Code сохраняет worktree, созданный для него вашим хуком WorktreeCreate, и не запускает хук WorktreeRemove. Удаляйте такие worktree самостоятельно, когда работа завершена.

Claude Code не запрашивает подтверждение перед запуском хука для вызова ExitWorktree или очистки claude remote-control. ExitWorktree отказывается удалять созданный хуком worktree, если вызов не передаёт discard_changes: true, что делает ваш хук последней проверкой на этом пути.

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

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

  • Нет хука WorktreeRemove: Claude Code использует git как резервный вариант при удалении на выходе или через ExitWorktree и сохраняет worktree при очистке claude remote-control:
    • Вы выходите из сессии worktree или Claude вызывает ExitWorktree: Claude Code удаляет путь, возвращённый хуком WorktreeCreate, так же, как это сделал бы git worktree remove --force, поэтому worktree, распознаваемый git, удаляется. Worktree, который git не распознаёт, например созданный хуком с системой контроля версий, отличной от git, остаётся на диске.
    • claude remote-control очищает worktree сессии: worktree остаётся на диске, а терминал, в котором работает claude remote-control, показывает worktree removal failed, kept: <path>.
    • Вы удаляете фоновую сессию: см. Что удаляется при удалении сессии.
  • Хук завершается с кодом 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, в котором ещё есть файлы, только если вы подтверждаете удаление в представлении агентов. Для такого worktree claude rm вместо этого сохраняет сессию и worktree.

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 попадают в отладочный лог.
  • Если вы удаляли фоновую сессию, сессия тоже сохраняется. Сообщение об отказе в представлении агентов сообщает, как завершился хук, например exited 1, цитирует начало его stderr и указывает, удалит ли повторное удаление сессии каталог в любом случае.
  • Если claude remote-control очищал worktree сессии, терминал, в котором он работает, показывает worktree removal failed, kept: <path>.

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, и пропускает любую другую целевую модель:

[tab] macOS/Linux

Команда проверяет 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"
          }
        ]
      }
    ]
  }
}

[tab] Windows (PowerShell)

Зарегистрируйте хук команды, который запускает скрипт через 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 Время жизни кэша промптов основного диалога на момент этой смены: "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 и другие коды выхода

Хук PreModelSwitch, не ответивший до истечения таймаута, блокирует переключение. Таймаут по умолчанию для этого события — 30 секунд.

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

PostModelSwitch

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

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

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

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

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

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

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

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

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

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

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

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

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

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

SessionEnd

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

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

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

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

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

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

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

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

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

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

CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude

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

Elicitation

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

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

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

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

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

Для 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[tab] macOS/Linux

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

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

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

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

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

[tab] Windows (PowerShell)

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

{
  "hooks": {
    "Elicitation": [
      {
        "matcher": "issue-tracker",
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe",
            "args": [
              "-NoProfile",
              "-ExecutionPolicy",
              "Bypass",
              "-File",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/answer-project-key.ps1"
            ]
          }
        ]
      }
    ]
  }
}

Сохраните этот скрипт в .claude/hooks/answer-project-key.ps1 в вашем проекте:

$request = [Console]::In.ReadToEnd() | ConvertFrom-Json
$fields = @($request.requested_schema.properties.PSObject.Properties.Name)

if ($fields.Count -eq 1 -and $fields[0] -eq 'project_key') {
  @{
    hookSpecificOutput = @{
      hookEventName = "Elicitation"
      action = "accept"
      content = @{ project_key = "DOCS" }
    }
  } | ConvertTo-Json -Depth 3
}

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

ElicitationResult

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Prompt-based hooks

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

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

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

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

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

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

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

How prompt-based hooks work

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

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

Prompt hook configuration

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

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

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

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

Response schema

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

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

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

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

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

Check multiple conditions before stopping

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

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

Agent-based hooks

[!WARNING]

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:

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

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

Agent hook configuration

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Выводите JSON-ответ в stdout либо единственным содержимым, либо на отдельной строке:

  • Единственным содержимым stdout: когда ответ — единственный текст в stdout, он может занимать несколько строк, например отформатированный вывод jq. Для ответа на нескольких строках требуется Claude Code v2.1.295 или новее.
  • На отдельной строке: асинхронный хук может выводить в stdout другой текст, если ответ целиком умещается на отдельной строке, например при использовании jq -c.

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

Уведомления о завершении асинхронного 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 немедленно даже когда сеанс неактивен.
  • Каждое выполнение создаёт отдельный фоновый процесс.
  • Асинхронный хук SessionStart запускается каждый раз, когда срабатывает событие. Claude Code пропускает вывод хука, если текст additionalContext и systemMessage совпадает с тем, что Claude последним получил от этого хука, если только синхронный хук SessionStart с тех пор не добавил другой текст. Claude Code доставляет текст, когда он меняется, а также в следующий раз, когда хук запускается после сжатия контекста или /clear. До v2.1.295 Claude Code добавлял один и тот же текст в диалог заново при каждом возобновлении.

Соображения безопасности

Отказ от ответственности

[!WARNING]

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

Чтобы ссылаться на корневой каталог проекта из команды 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 v2.1.296 или новее каждый командный хук на событии инструмента, UserPromptSubmit, SessionStart, Stop и ряде других событий оставляет такую строку по завершении, независимо от того, что он вывел. Строка содержит имя события, соединённое двоеточием с именем инструмента или другим значением, по которому сработал хук, затем команду хука в квадратных скобках, плагин, из которого он получен (если есть), то, чем завершился запуск, и его длительность, например Hooks: PostToolUse:Write [.claude/hooks/log-write.sh] finished with status 0 (31ms). Запуск также может завершиться как timed out after <N>ms, cancelled, moved to the background или failed to start. На некоторых событиях, таких как Notification, SessionEnd и PreCompact, командный хук вместо этого оставляет строку completed with status без длительности.

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

Для устранения неполадок распространённых проблем, таких как hooks, которые не срабатывают, Stop hooks, которые продолжают блокировать, или ошибки конфигурации, см. Limitations and troubleshooting в руководстве. Для более широкого диагностического пошагового руководства, охватывающего /context, /doctor и приоритет параметров, см. Debug your config.