SpyBara
Go Premium

agent-sdk/hooks.md 2026-05-13 23:01 UTC to 2026-05-14 17:02 UTC

11 added, 5 removed.

2026
Sun 31 06:39 Sat 30 06:23 Fri 29 06:38 Thu 28 06:37 Wed 27 06:42 Tue 26 06:33 Sun 24 06:25 Sat 23 06:18 Fri 22 06:33 Thu 21 06:36 Wed 20 06:35 Tue 19 06:34 Mon 18 23:59 Sun 17 01:01 Fri 15 22:58 Thu 14 17:02 Wed 13 23:01 Tue 12 22:57 Mon 11 23:00 Sun 10 23:03 Sat 9 04:57 Fri 8 22:00 Thu 7 22:59 Tue 5 23:00 Mon 4 22:58 Sat 2 18:14 Fri 1 18:19

Перехватывайте и контролируйте поведение агента с помощью hooks

Перехватывайте и настраивайте поведение агента в ключевых точках выполнения с помощью hooks

Hooks — это функции обратного вызова, которые выполняют ваш код в ответ на события агента, такие как вызов инструмента, начало сеанса или остановка выполнения. С помощью hooks вы можете:

  • Блокировать опасные операции перед их выполнением, такие как деструктивные команды shell или несанкционированный доступ к файлам
  • Логировать и аудировать каждый вызов инструмента для соответствия требованиям, отладки или аналитики
  • Преобразовывать входные и выходные данные для санитизации данных, внедрения учетных данных или перенаправления путей файлов
  • Требовать одобрение человека для чувствительных действий, таких как запись в базу данных или вызовы API
  • Отслеживать жизненный цикл сеанса для управления состоянием, очистки ресурсов или отправки уведомлений

Это руководство охватывает, как работают hooks, как их настроить, и предоставляет примеры для распространенных паттернов, таких как блокировка инструментов, изменение входных данных и перенаправление уведомлений.

Как работают hooks

1

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

Что-то происходит во время выполнения агента, и SDK срабатывает событие: инструмент вот-вот будет вызван (PreToolUse), инструмент вернул результат (PostToolUse), подагент запустился или остановился, агент неактивен или выполнение завершилось. См. полный список событий.

2

SDK собирает зарегистрированные hooks

SDK проверяет наличие hooks, зарегистрированных для этого типа события. Это включает callback hooks, которые вы передаете в options.hooks, и hooks команд shell из файлов настроек, когда соответствующая запись settingSources или setting_sources включена, что она есть для параметров query() по умолчанию.

3

Matchers фильтруют, какие hooks запускаются

Если hook имеет паттерн matcher (например, "Write|Edit"), SDK проверяет его против цели события (например, имя инструмента). Hooks без matcher запускаются для каждого события этого типа.

4

Выполняются функции обратного вызова

Каждая функция обратного вызова matching hook получает информацию о том, что происходит: имя инструмента, его аргументы, ID сеанса и другие детали, специфичные для события.

5

Ваш callback возвращает решение

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

Следующий пример объединяет эти шаги. Он регистрирует hook PreToolUse (шаг 1) с matcher "Write|Edit" (шаг 3), поэтому callback срабатывает только для инструментов записи файлов. При срабатывании callback получает входные данные инструмента (шаг 4), проверяет, нацелена ли путь файла на файл .env, и возвращает permissionDecision: "deny" для блокировки операции (шаг 5):

import asyncio
from claude_agent_sdk import (
AssistantMessage,
ClaudeSDKClient,
ClaudeAgentOptions,
HookMatcher,
ResultMessage,
)


# Define a hook callback that receives tool call details
async def protect_env_files(input_data, tool_use_id, context):
# Extract the file path from the tool's input arguments
file_path = input_data["tool_input"].get("file_path", "")
file_name = file_path.split("/")[-1]

# Block the operation if targeting a .env file
if file_name == ".env":
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "deny",
"permissionDecisionReason": "Cannot modify .env files",
}
}

# Return empty object to allow the operation
return {}


async def main():
options = ClaudeAgentOptions(
hooks={
# Register the hook for PreToolUse events
# The matcher filters to only Write and Edit tool calls
"PreToolUse": [HookMatcher(matcher="Write|Edit", hooks=[protect_env_files])]
}
)

async with ClaudeSDKClient(options=options) as client:
await client.query("Update the database configuration")
async for message in client.receive_response():
# Filter for assistant and result messages
if isinstance(message, (AssistantMessage, ResultMessage)):
print(message)


asyncio.run(main())

Доступные hooks

SDK предоставляет hooks для различных этапов выполнения агента. Некоторые hooks доступны в обоих SDK, в то время как другие доступны только в TypeScript.

Hook Event Python SDK TypeScript SDK Что его срабатывает Пример использования
PreToolUse Да Да Запрос вызова инструмента (может блокировать или изменять) Блокировать опасные команды shell
PostToolUse Да Да Результат выполнения инструмента Логировать все изменения файлов в журнал аудита
PostToolUseFailure Да Да Ошибка выполнения инструмента Обработать или логировать ошибки инструмента
PostToolBatch Нет Да Полный пакет вызовов инструментов разрешается, один раз за пакет перед следующим вызовом модели Внедрить соглашения один раз для всего пакета
UserPromptSubmit Да Да Отправка пользовательского приглашения Внедрить дополнительный контекст в приглашения
Stop Да Да Остановка выполнения агента Сохранить состояние сеанса перед выходом
SubagentStart Да Да Инициализация подагента Отслеживать порождение параллельных задач
SubagentStop Да Да Завершение подагента Агрегировать результаты из параллельных задач
PreCompact Да Да Запрос сжатия разговора Архивировать полную стенограмму перед суммированием
PermissionRequest Да Да Диалог разрешения будет отображен Пользовательская обработка разрешений
SessionStart Нет Да Инициализация сеанса Инициализировать логирование и телеметрию
SessionEnd Нет Да Завершение сеанса Очистить временные ресурсы
Notification Да Да Сообщения о статусе агента Отправить обновления статуса агента в Slack или PagerDuty
Setup Нет Да Настройка/обслуживание сеанса Запустить задачи инициализации
TeammateIdle Нет Да Товарищ по команде становится неактивным Переназначить работу или уведомить
TaskCompleted Нет Да Фоновая задача завершена Агрегировать результаты из параллельных задач
ConfigChange Нет Да Файл конфигурации изменился Динамически перезагрузить настройки
WorktreeCreate Нет Да Git worktree создан Отслеживать изолированные рабочие пространства
WorktreeRemove Нет Да Git worktree удален Очистить ресурсы рабочего пространства

Настройка hooks

Чтобы настроить hook, передайте его в поле hooks ваших параметров агента (ClaudeAgentOptions в Python, объект options в TypeScript):

options = ClaudeAgentOptions(
hooks={"PreToolUse": [HookMatcher(matcher="Bash", hooks=[my_callback])]}
)

async with ClaudeSDKClient(options=options) as client:
await client.query("Your prompt")
async for message in client.receive_response():
print(message)

Опция hooks — это словарь (Python) или объект (TypeScript), где:

Matchers

Используйте matchers для фильтрации, когда срабатывают ваши callbacks. Поле matcher — это строка regex, которая соответствует другому значению в зависимости от типа события hook. Например, hooks на основе инструментов соответствуют имени инструмента, в то время как hooks Notification соответствуют типу уведомления. См. справочник hooks Claude Code для полного списка значений matcher для каждого типа события.

Опция Тип По умолчанию Описание
matcher string undefined Паттерн Regex, сопоставляемый с полем фильтра события. Для hooks инструментов это имя инструмента. Встроенные инструменты включают Bash, Read, Write, Edit, Glob, Grep, WebFetch, Agent и другие (см. Tool Input Types для полного списка). MCP инструменты используют паттерн mcp__<server>__<action>.
hooks HookCallback[] - Обязательно. Массив функций обратного вызова для выполнения, когда паттерн совпадает
timeout number 60 Timeout в секундах

Используйте паттерн matcher для нацеливания на конкретные инструменты, когда это возможно. Matcher с 'Bash' запускается только для команд Bash, в то время как опущение паттерна запускает ваши callbacks для каждого возникновения события. Обратите внимание, что для hooks на основе инструментов, matchers фильтруют только по имени инструмента, а не по путям файлов или другим аргументам. Для фильтрации по пути файла проверьте tool_input.file_path внутри вашего callback.

Функции обратного вызова

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

Каждый callback hook получает три аргумента:

  • Входные данные: типизированный объект, содержащий детали события. Каждый тип hook имеет свою форму входных данных (например, PreToolUseHookInput включает tool_name и tool_input, в то время как NotificationHookInput включает message). См. полные определения типов в справочниках TypeScript и Python SDK.
    • Все входные данные hook содержат session_id, cwd и hook_event_name.
    • agent_id и agent_type заполняются, когда hook срабатывает внутри подагента. В TypeScript они находятся на базовом входе hook и доступны для всех типов hook. В Python они находятся только на PreToolUse, PostToolUse и PostToolUseFailure.
  • ID использования инструмента (str | None / string | undefined): коррелирует события PreToolUse и PostToolUse для одного и того же вызова инструмента.
  • Контекст: в TypeScript содержит свойство signal (AbortSignal) для отмены. В Python этот аргумент зарезервирован для будущего использования.

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

Ваш callback возвращает объект с двумя категориями полей:

  • Поля верхнего уровня работают одинаково для каждого события: systemMessage показывает сообщение пользователю, и continue (continue_ в Python) определяет, продолжает ли агент работать после этого hook.
  • hookSpecificOutput контролирует текущую операцию. Поля внутри зависят от типа события hook. Для hooks PreToolUse здесь вы устанавливаете permissionDecision ("allow", "deny", "ask" или "defer"), permissionDecisionReason и updatedInput. Возврат "defer" завершает запрос, чтобы вы могли возобновить его позже. Для hooks PostToolUse вы можете установить additionalContext для добавления информации к результату инструмента, или updatedToolOutput для полной замены выходных данных инструмента перед тем, как Claude их увидит.

Возвращайте {} для разрешения операции без изменений. SDK callback hooks используют тот же формат вывода JSON, что и hooks команд shell Claude Code, который документирует каждое поле и опцию, специфичную для события. Для определений типов SDK см. справочники TypeScript и Python SDK.

Асинхронный вывод

По умолчанию агент ждет, пока ваш hook вернется, прежде чем продолжить. Если ваш hook выполняет побочный эффект (логирование, отправка webhook) и не нужно влиять на поведение агента, вы можете вернуть асинхронный вывод вместо этого. Это говорит агенту продолжить немедленно без ожидания завершения hook:

async def async_hook(input_data, tool_use_id, context):
# Start a background task, then return immediately
asyncio.create_task(send_to_logging_service(input_data))
return {"async_": True, "asyncTimeout": 30000}
Поле Тип Описание
async true Сигнализирует асинхронный режим. Агент продолжает без ожидания. В Python используйте async_ для избежания зарезервированного ключевого слова.
asyncTimeout number Необязательный timeout в миллисекундах для фоновой операции

Примеры

Изменение входных данных инструмента

Этот пример перехватывает вызовы инструмента Write и переписывает аргумент file_path для добавления префикса /sandbox, перенаправляя все записи файлов в изолированный каталог. Callback возвращает updatedInput с измененным путем и permissionDecision: 'allow' для автоматического одобрения переписанной операции:

async def redirect_to_sandbox(input_data, tool_use_id, context):
if input_data["hook_event_name"] != "PreToolUse":
return {}

if input_data["tool_name"] == "Write":
original_path = input_data["tool_input"].get("file_path", "")
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "allow",
"updatedInput": {
**input_data["tool_input"],
"file_path": f"/sandbox{original_path}",
},
}
}
return {}

Добавление контекста и блокировка инструмента

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

  • permissionDecision: 'deny' останавливает вызов инструмента.
  • permissionDecisionReason сообщает модели причину, чтобы она избежала повторной попытки.
  • systemMessage показывает пользователю, что произошло.
async def block_etc_writes(input_data, tool_use_id, context):
file_path = input_data["tool_input"].get("file_path", "")

if file_path.startswith("/etc"):
return {
# Top-level field: message shown to the user
"systemMessage": "Remember: system directories like /etc are protected.",
# hookSpecificOutput: block the operation
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "deny",
"permissionDecisionReason": "Writing to /etc is not allowed",
},
}
return {}

Автоматическое одобрение конкретных инструментов

По умолчанию агент может запросить разрешение перед использованием определенных инструментов. Этот пример автоматически одобряет инструменты файловой системы только для чтения (Read, Glob, Grep), возвращая permissionDecision: 'allow', позволяя им запускаться без подтверждения пользователя, в то время как оставляя все остальные инструменты подлежащими обычным проверкам разрешений:

async def auto_approve_read_only(input_data, tool_use_id, context):
if input_data["hook_event_name"] != "PreToolUse":
return {}

read_only_tools = ["Read", "Glob", "Grep"]
if input_data["tool_name"] in read_only_tools:
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "allow",
"permissionDecisionReason": "Read-only tool auto-approved",
}
}
return {}

Регистрация нескольких hooks

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

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

options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
HookMatcher(hooks=[authorization_check]),
HookMatcher(hooks=[input_validator]),
HookMatcher(hooks=[audit_logger]),
]
}
)

Фильтрация с помощью regex matchers

Используйте regex паттерны для соответствия нескольким инструментам. Этот пример регистрирует три matcher с разными областями: первый срабатывает file_security_hook только для инструментов модификации файлов, второй срабатывает mcp_audit_hook для любого MCP инструмента (инструменты, имена которых начинаются с mcp__), и третий срабатывает global_logger для каждого вызова инструмента независимо от имени:

options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
# Match file modification tools
HookMatcher(matcher="Write|Edit|Delete", hooks=[file_security_hook]),
# Match all MCP tools
HookMatcher(matcher="^mcp__", hooks=[mcp_audit_hook]),
# Match everything (no matcher)
HookMatcher(hooks=[global_logger]),
]
}
)

Отслеживание активности подагента

Используйте hooks SubagentStop для мониторинга, когда подагенты завершают свою работу. См. полный тип входных данных в справочниках TypeScript и Python SDK. Этот пример логирует сводку каждый раз, когда подагент завершается:

async def subagent_tracker(input_data, tool_use_id, context):
# Log subagent details when it finishes
print(f"[SUBAGENT] Completed: {input_data['agent_id']}")
print(f"  Transcript: {input_data['agent_transcript_path']}")
print(f"  Tool use ID: {tool_use_id}")
print(f"  Stop hook active: {input_data.get('stop_hook_active')}")
return {}


options = ClaudeAgentOptions(
hooks={"SubagentStop": [HookMatcher(hooks=[subagent_tracker])]}
)

Выполнение HTTP запросов из hooks

Hooks могут выполнять асинхронные операции, такие как HTTP запросы. Ловите ошибки внутри вашего hook вместо того, чтобы позволить им распространяться, так как необработанное исключение может прервать агента.

Этот пример отправляет webhook после завершения каждого инструмента, логируя, какой инструмент запустился и когда. Hook ловит ошибки, чтобы неудачный webhook не прерывал агента:

import asyncio
import json
import urllib.request
from datetime import datetime


def _send_webhook(tool_name):
"""Synchronous helper that POSTs tool usage data to an external webhook."""
data = json.dumps(
{
"tool": tool_name,
"timestamp": datetime.now().isoformat(),
}
).encode()
req = urllib.request.Request(
"https://api.example.com/webhook",
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
urllib.request.urlopen(req)


async def webhook_notifier(input_data, tool_use_id, context):
# Only fire after a tool completes (PostToolUse), not before
if input_data["hook_event_name"] != "PostToolUse":
return {}

try:
# Run the blocking HTTP call in a thread to avoid blocking the event loop
await asyncio.to_thread(_send_webhook, input_data["tool_name"])
except Exception as e:
# Log the error but don't raise. A failed webhook shouldn't stop the agent
print(f"Webhook request failed: {e}")

return {}

Перенаправление уведомлений в Slack

Используйте hooks Notification для получения системных уведомлений от агента и перенаправления их во внешние сервисы. Уведомления срабатывают для конкретных типов событий: permission_prompt (Claude нужно разрешение), idle_prompt (Claude ждет ввода), auth_success (аутентификация завершена), elicitation_dialog (Claude запрашивает пользователя), elicitation_response (пользователь ответил на запрос), и elicitation_complete (запрос закрыт). Каждое уведомление включает поле message с описанием, понятным человеку, и опционально title.

Этот пример перенаправляет каждое уведомление в канал Slack. Требуется URL входящего webhook Slack, который вы создаете, добавляя приложение в ваше рабочее пространство Slack и включая входящие webhooks:

import asyncio
import json
import urllib.request

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, HookMatcher


def _send_slack_notification(message):
"""Synchronous helper that sends a message to Slack via incoming webhook."""
data = json.dumps({"text": f"Agent status: {message}"}).encode()
req = urllib.request.Request(
"https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
urllib.request.urlopen(req)


async def notification_handler(input_data, tool_use_id, context):
try:
# Run the blocking HTTP call in a thread to avoid blocking the event loop
await asyncio.to_thread(_send_slack_notification, input_data.get("message", ""))
except Exception as e:
print(f"Failed to send notification: {e}")

# Return empty object. Notification hooks don't modify agent behavior
return {}


async def main():
options = ClaudeAgentOptions(
hooks={
# Register the hook for Notification events (no matcher needed)
"Notification": [HookMatcher(hooks=[notification_handler])],
},
)

async with ClaudeSDKClient(options=options) as client:
await client.query("Analyze this codebase")
async for message in client.receive_response():
print(message)


asyncio.run(main())

Исправление распространенных проблем

Hook не срабатывает

  • Проверьте, что имя события hook правильное и чувствительно к регистру (PreToolUse, а не preToolUse)
  • Проверьте, что ваш паттерн matcher точно совпадает с именем инструмента
  • Убедитесь, что hook находится под правильным типом события в options.hooks
  • Для non-tool hooks, таких как Stop и SubagentStop, matchers соответствуют разным полям (см. matcher patterns)
  • Hooks могут не срабатывать, когда агент достигает лимита max_turns, потому что сеанс заканчивается перед тем, как hooks смогут выполниться

Matcher не фильтрует как ожидается

Matchers соответствуют только имени инструмента, а не путям файлов или другим аргументам. Для фильтрации по пути файла проверьте tool_input.file_path внутри вашего hook:

const myHook: HookCallback = async (input, toolUseID, { signal }) => {
  const preInput = input as PreToolUseHookInput;
  const toolInput = preInput.tool_input as Record<string, unknown>;
  const filePath = toolInput?.file_path as string;
  if (!filePath?.endsWith(".md")) return {}; // Skip non-markdown files
  // Process markdown files...
  return {};
};

Hook timeout

  • Увеличьте значение timeout в конфигурации HookMatcher
  • Используйте AbortSignal из третьего аргумента callback для корректной обработки отмены в TypeScript

Инструмент заблокирован неожиданно

  • Проверьте все hooks PreToolUse на возвращение permissionDecision: 'deny'
  • Добавьте логирование в ваши hooks, чтобы увидеть, какие permissionDecisionReason они возвращают
  • Проверьте, что паттерны matcher не слишком широкие (пустой matcher соответствует всем инструментам)

Измененный входной сигнал не применяется

  • Убедитесь, что updatedInput находится внутри hookSpecificOutput, а не на верхнем уровне:

    return {
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "allow",
        updatedInput: { command: "new command" }
      }
    };
    
  • Вы также должны вернуть permissionDecision: 'allow' или 'ask' для того, чтобы изменение входных данных вступило в силу

  • Включите hookEventName в hookSpecificOutput для идентификации типа hook, для которого предназначен вывод

Hooks сеанса недоступны в Python

SessionStart и SessionEnd могут быть зарегистрированы как SDK callback hooks в TypeScript, но недоступны в Python SDK (HookEvent их опускает). В Python они доступны только как shell command hooks, определенные в файлах настроек (например, .claude/settings.json). Для загрузки shell command hooks из вашего приложения SDK включите соответствующий источник настроек с setting_sources или settingSources:

options = ClaudeAgentOptions(
setting_sources=["project"],  # Loads .claude/settings.json including hooks
)

Для запуска логики инициализации как Python SDK callback вместо этого используйте первое сообщение из client.receive_response() как ваш триггер.

Запросы разрешений подагента умножаются

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

Рекурсивные циклы hook с подагентами

Hook UserPromptSubmit, который порождает подагентов, может создать бесконечные циклы, если эти подагенты срабатывают тот же hook. Чтобы предотвратить это:

  • Проверьте индикатор подагента во входных данных hook перед порождением
  • Используйте общую переменную или состояние сеанса для отслеживания, находитесь ли вы уже внутри подагента
  • Ограничьте область действия hooks, чтобы они запускались только для сеанса агента верхнего уровня

systemMessage не появляется в выводе

Поле systemMessage показывает сообщение пользователю, а не модели. По умолчанию SDK не выводит выходные данные hook в поток сообщений, поэтому сообщение может не появиться, если вы не установите includeHookEvents (include_hook_events в Python). Для передачи контекста модели вместо этого верните additionalContext.

Если вам нужно надежно вывести решения hook в ваше приложение, логируйте их отдельно или используйте выделенный канал вывода.

Связанные ресурсы