{/* TRANSLATED — 已翻译为中文 */}

> ## 文档索引
> 在此获取完整文档索引: https://code.claude.com/docs/llms.txt
> 使用此文件发现所有可用页面，然后再进一步探索。

# 使用钩子拦截和控制智能体行为

> 使用钩子在关键执行点拦截和自定义智能体行为

钩子是回调函数，用于响应智能体事件运行您的代码，例如工具被调用、会话开始或执行停止。使用钩子，您可以：

* **阻止危险操作**在执行之前，例如破坏性 shell 命令或未授权的文件访问
* **记录和审计**每次工具调用，用于合规、调试或分析
* **转换输入和输出**以清理数据、注入凭据或重定向文件路径
* **要求人工批准**敏感操作，如数据库写入或 API 调用
* **跟踪会话生命周期**以管理状态、清理资源或发送通知

本指南介绍钩子的工作原理、如何配置它们，并提供常见模式的示例，如阻止工具、修改输入和转发通知。

## 钩子的工作原理

<Steps>
  <Step title="事件触发">
    在智能体执行期间发生某些事情，SDK 触发一个事件：工具即将被调用（`PreToolUse`）、工具返回了结果（`PostToolUse`）、子智能体启动或停止、智能体空闲或执行完成。请参阅[完整事件列表](#available-hooks)。
  </Step>

  <Step title="SDK 收集已注册的钩子">
    SDK 检查为该事件类型注册的钩子。这包括您在 `options.hooks` 中传递的回调钩子，以及当相应的 [`settingSources`](/en/agent-sdk/typescript#settingsource) 或 [`setting_sources`](/en/agent-sdk/python#settingsource) 条目启用时（默认 `query()` 选项会启用）来自设置文件的 shell 命令钩子。
  </Step>

  <Step title="匹配器过滤哪些钩子运行">
    如果钩子有 [`matcher`](#matchers) 模式（如 `"Write|Edit"`），SDK 会针对事件的目标（例如工具名称）进行测试。没有匹配器的钩子对该类型的每个事件都运行。
  </Step>

  <Step title="回调函数执行">
    每个匹配的钩子的[回调函数](#callback-functions)接收关于正在发生什么的输入：工具名称、其参数、会话 ID 和其他事件特定的详细信息。
  </Step>

  <Step title="您的回调返回决策">
    执行任何操作（记录、API 调用、验证）后，您的回调返回一个[输出对象](#outputs)，告诉智能体该做什么：允许操作、阻止操作、修改输入或向对话注入上下文。
  </Step>
</Steps>

以下示例将这些步骤组合在一起。它注册了一个带有 `"Write|Edit"` 匹配器（步骤 3）的 `PreToolUse` 钩子（步骤 1），因此回调仅对文件写入工具触发。触发时，回调接收工具的输入（步骤 4），检查文件路径是否针对 `.env` 文件，并返回 `permissionDecision: "deny"` 以阻止操作（步骤 5）：

<CodeGroup>
  ```python Python theme={null}
  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())
  ```

  ```typescript TypeScript theme={null}
  import { query, HookCallback, PreToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";

  // Define a hook callback with the HookCallback type
  const protectEnvFiles: HookCallback = async (input, toolUseID, { signal }) => {
    // Cast input to the specific hook type for type safety
    const preInput = input as PreToolUseHookInput;

    // Cast tool_input to access its properties (typed as unknown in the SDK)
    const toolInput = preInput.tool_input as Record<string, unknown>;
    const filePath = toolInput?.file_path as string;
    const fileName = filePath?.split("/").pop();

    // Block the operation if targeting a .env file
    if (fileName === ".env") {
      return {
        hookSpecificOutput: {
          hookEventName: preInput.hook_event_name,
          permissionDecision: "deny",
          permissionDecisionReason: "Cannot modify .env files"
        }
      };
    }

    // Return empty object to allow the operation
    return {};
  };

  for await (const message of query({
    prompt: "Update the database configuration",
    options: {
      hooks: {
        // Register the hook for PreToolUse events
        // The matcher filters to only Write and Edit tool calls
        PreToolUse: [{ matcher: "Write|Edit", hooks: [protectEnvFiles] }]
      }
    }
  })) {
    // Filter for assistant and result messages
    if (message.type === "assistant" || message.type === "result") {
      console.log(message);
    }
  }
  ```
</CodeGroup>

## 可用钩子

SDK 为智能体执行的不同阶段提供钩子。某些钩子在两个 SDK 中都可用，而其他钩子仅限 TypeScript。

| 钩子事件             | 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 工作树创建                                                                 | 跟踪隔离的工作空间                              |
| `WorktreeRemove`     | 否         | 是             | Git 工作树移除                                                                 | 清理工作空间资源                                |

## 配置钩子

要配置钩子，在智能体选项的 `hooks` 字段中传递它（Python 中的 `ClaudeAgentOptions`，TypeScript 中的 `options` 对象）：

<CodeGroup>
  ```python Python theme={null}
  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)
  ```

  ```typescript TypeScript theme={null}
  for await (const message of query({
    prompt: "Your prompt",
    options: {
      hooks: {
        PreToolUse: [{ matcher: "Bash", hooks: [myCallback] }]
      }
    }
  })) {
    console.log(message);
  }
  ```
</CodeGroup>

`hooks` 选项是一个字典（Python）或对象（TypeScript），其中：

* **键**是[钩子事件名称](#available-hooks)（例如 `'PreToolUse'`、`'PostToolUse'`、`'Stop'`）
* **值**是[匹配器](#matchers)数组，每个包含可选的过滤模式和您的[回调函数](#callback-functions)

### 匹配器

使用匹配器来过滤回调何时触发。`matcher` 字段是一个正则表达式字符串，根据钩子事件类型匹配不同的值。例如，基于工具的钩子匹配工具名称，而 `Notification` 钩子匹配通知类型。有关每种事件类型的匹配器值完整列表，请参阅 [Claude Code 钩子参考](/en/hooks#matcher-patterns)。

| 选项      | 类型             | 默认值      | 描述                                                                                                                                                                                                                                                                                                                                               |
| --------- | ---------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `matcher` | `string`         | `undefined` | 与事件过滤字段匹配的正则表达式模式。对于工具钩子，这是工具名称。内置工具包括 `Bash`、`Read`、`Write`、`Edit`、`Glob`、`Grep`、`WebFetch`、`Agent` 等（有关完整列表，请参阅[工具输入类型](/en/agent-sdk/typescript#tool-input-types)）。MCP 工具使用模式 `mcp__<server>__<action>`。 |
| `hooks`   | `HookCallback[]` | -           | 必需。模式匹配时要执行的回调函数数组                                                                                                                                                                                                                                                                                                               |
| `timeout` | `number`         | `60`        | 超时时间（秒）                                                                                                                                                                                                                                                                                                                                     |

尽可能使用 `matcher` 模式来针对特定工具。带有 `'Bash'` 的匹配器仅对 Bash 命令运行，而省略模式会使您的回调对该事件的每次出现都运行。请注意，对于基于工具的钩子，匹配器仅按**工具名称**过滤，不按文件路径或其他参数过滤。要按文件路径过滤，请在回调内部检查 `tool_input.file_path`。

<Tip>
  **发现工具名称：** 有关内置工具名称的完整列表，请参阅[工具输入类型](/en/agent-sdk/typescript#tool-input-types)，或添加不带匹配器的钩子来记录会话进行的所有工具调用。

  **MCP 工具命名：** MCP 工具始终以 `mcp__` 开头，后跟服务器名称和操作：`mcp__<server>__<action>`。例如，如果您配置了名为 `playwright` 的服务器，其工具将命名为 `mcp__playwright__browser_screenshot`、`mcp__playwright__browser_click` 等。服务器名称来自您在 `mcpServers` 配置中使用的键。
</Tip>

### 回调函数

#### 输入

每个钩子回调接收三个参数：

* **输入数据：** 包含事件详细信息的类型化对象。每种钩子类型都有自己的输入形状（例如 `PreToolUseHookInput` 包含 `tool_name` 和 `tool_input`，而 `NotificationHookInput` 包含 `message`）。有关完整的类型定义，请参阅 [TypeScript](/en/agent-sdk/typescript#hookinput) 和 [Python](/en/agent-sdk/python#hookinput) SDK 参考。
  * 所有钩子输入共享 `session_id`、`cwd` 和 `hook_event_name`。
  * 当钩子在子智能体内部触发时，`agent_id` 和 `agent_type` 会被填充。在 TypeScript 中，这些在基础钩子输入上，对所有钩子类型可用。在 Python 中，它们仅在 `PreToolUse`、`PostToolUse` 和 `PostToolUseFailure` 上。
* **工具使用 ID**（`str | None` / `string | undefined`）：将同一工具调用的 `PreToolUse` 和 `PostToolUse` 事件关联起来。
* **上下文：** 在 TypeScript 中，包含用于取消的 `signal` 属性（`AbortSignal`）。在 Python 中，此参数保留供将来使用。

#### 输出

您的回调返回一个包含两类字段的对象：

* **顶级字段**在每个事件上的工作方式相同：`systemMessage` 向用户显示消息，`continue`（Python 中的 `continue_`）决定智能体在此钩子后是否继续运行。
* **`hookSpecificOutput`** 控制当前操作。内部的字段取决于钩子事件类型。对于 `PreToolUse` 钩子，这是您设置 `permissionDecision`（`"allow"`、`"deny"`、`"ask"` 或 `"defer"`）、`permissionDecisionReason` 和 `updatedInput` 的地方。返回 `"defer"` 会结束查询，以便您可以[稍后恢复](/en/hooks#defer-a-tool-call-for-later)。对于 `PostToolUse` 钩子，您可以设置 `additionalContext` 以向工具结果追加信息，或设置 `updatedToolOutput` 以在 Claude 看到之前完全替换工具的输出。

返回 `{}` 以允许操作不做更改。SDK 回调钩子使用与 [Claude Code shell 命令钩子](/en/hooks#json-output)相同的 JSON 输出格式，后者记录了每个字段和事件特定选项。有关 SDK 类型定义，请参阅 [TypeScript](/en/agent-sdk/typescript#synchookjsonoutput) 和 [Python](/en/agent-sdk/python#synchookjsonoutput) SDK 参考。

<Note>
  当多个钩子或权限规则适用时，**deny** 优先于 **defer**，**defer** 优先于 **ask**，**ask** 优先于 **allow**。如果任何钩子返回 `deny`，无论其他钩子如何，操作都会被阻止。
</Note>

#### 异步输出

默认情况下，智能体等待您的钩子返回后再继续。如果您的钩子执行副作用（记录、发送 webhook）且不需要影响智能体的行为，您可以返回异步输出。这告诉智能体立即继续而不等待钩子完成：

<CodeGroup>
  ```python Python theme={null}
  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}
  ```

  ```typescript TypeScript theme={null}
  const asyncHook: HookCallback = async (input, toolUseID, { signal }) => {
    // Start a background task, then return immediately
    sendToLoggingService(input).catch(console.error);
    return { async: true, asyncTimeout: 30000 };
  };
  ```
</CodeGroup>

| 字段           | 类型     | 描述                                                                                                     |
| -------------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `async`        | `true`   | 发出异步模式信号。智能体继续而不等待。在 Python 中，使用 `async_` 以避免保留关键字。                     |
| `asyncTimeout` | `number` | 可选的后台操作超时时间（毫秒）                                                                           |

<Note>
  异步输出无法阻止、修改或向操作注入上下文，因为智能体已经继续。仅将它们用于副作用，如记录、指标或通知。
</Note>

## 示例

### 修改工具输入

此示例拦截 Write 工具调用并重写 `file_path` 参数以在前面添加 `/sandbox`，将所有文件写入重定向到沙箱目录。回调返回带有修改路径的 `updatedInput` 和 `permissionDecision: 'allow'` 以自动批准重写的操作：

<CodeGroup>
  ```python Python theme={null}
  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 {}
  ```

  ```typescript TypeScript theme={null}
  const redirectToSandbox: HookCallback = async (input, toolUseID, { signal }) => {
    if (input.hook_event_name !== "PreToolUse") return {};

    const preInput = input as PreToolUseHookInput;
    const toolInput = preInput.tool_input as Record<string, unknown>;
    if (preInput.tool_name === "Write") {
      const originalPath = toolInput.file_path as string;
      return {
        hookSpecificOutput: {
          hookEventName: preInput.hook_event_name,
          permissionDecision: "allow",
          updatedInput: {
            ...toolInput,
            file_path: `/sandbox${originalPath}`
          }
        }
      };
    }
    return {};
  };
  ```
</CodeGroup>

<Note>
  使用 `updatedInput` 时，您还必须包含 `permissionDecision: 'allow'` 以自动批准修改后的输入，或 `permissionDecision: 'ask'` 以将其显示给用户。使用 `'defer'` 时，`updatedInput` 会被忽略。始终返回新对象而不是修改原始 `tool_input`。
</Note>

### 添加上下文并阻止工具

此示例阻止对 `/etc` 目录的写入，并向模型和用户解释原因：

* `permissionDecision: 'deny'` 停止工具调用。
* `permissionDecisionReason` 告诉模型原因，使其避免重试。
* `systemMessage` 向用户显示发生了什么。

<CodeGroup>
  ```python Python theme={null}
  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 {}
  ```

  ```typescript TypeScript theme={null}
  const blockEtcWrites: 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?.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: preInput.hook_event_name,
          permissionDecision: "deny",
          permissionDecisionReason: "Writing to /etc is not allowed"
        }
      };
    }
    return {};
  };
  ```
</CodeGroup>

### 自动批准特定工具

默认情况下，智能体可能在使用某些工具之前提示权限。此示例通过返回 `permissionDecision: 'allow'` 自动批准只读文件系统工具（Read、Glob、Grep），让它们无需用户确认即可运行，同时让所有其他工具受正常权限检查：

<CodeGroup>
  ```python Python theme={null}
  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 {}
  ```

  ```typescript TypeScript theme={null}
  const autoApproveReadOnly: HookCallback = async (input, toolUseID, { signal }) => {
    if (input.hook_event_name !== "PreToolUse") return {};

    const preInput = input as PreToolUseHookInput;
    const readOnlyTools = ["Read", "Glob", "Grep"];
    if (readOnlyTools.includes(preInput.tool_name)) {
      return {
        hookSpecificOutput: {
          hookEventName: preInput.hook_event_name,
          permissionDecision: "allow",
          permissionDecisionReason: "Read-only tool auto-approved"
        }
      };
    }
    return {};
  };
  ```
</CodeGroup>

### 注册多个钩子

当事件触发时，所有匹配的钩子并行运行。对于权限决策，最具限制性的结果获胜：单个 `deny` 会阻止工具调用，无论其他钩子返回什么。因为完成顺序是不确定的，所以将每个钩子编写为独立操作，而不是依赖另一个钩子先运行。

下面的示例为每个工具调用注册三个独立检查：

<CodeGroup>
  ```python Python theme={null}
  options = ClaudeAgentOptions(
      hooks={
          "PreToolUse": [
              HookMatcher(hooks=[authorization_check]),
              HookMatcher(hooks=[input_validator]),
              HookMatcher(hooks=[audit_logger]),
          ]
      }
  )
  ```

  ```typescript TypeScript theme={null}
  const options = {
    hooks: {
      PreToolUse: [
        { hooks: [authorizationCheck] },
        { hooks: [inputValidator] },
        { hooks: [auditLogger] }
      ]
    }
  };
  ```
</CodeGroup>

### 使用正则表达式匹配器过滤

使用正则表达式模式匹配多个工具。此示例注册了三个具有不同作用域的匹配器：第一个仅对文件修改工具触发 `file_security_hook`，第二个对任何 MCP 工具（名称以 `mcp__` 开头的工具）触发 `mcp_audit_hook`，第三个对每个工具调用触发 `global_logger`，无论名称如何：

<CodeGroup>
  ```python Python theme={null}
  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]),
          ]
      }
  )
  ```

  ```typescript TypeScript theme={null}
  const options = {
    hooks: {
      PreToolUse: [
        // Match file modification tools
        { matcher: "Write|Edit|Delete", hooks: [fileSecurityHook] },

        // Match all MCP tools
        { matcher: "^mcp__", hooks: [mcpAuditHook] },

        // Match everything (no matcher)
        { hooks: [globalLogger] }
      ]
    }
  };
  ```
</CodeGroup>

### 跟踪子智能体活动

使用 `SubagentStop` 钩子监控子智能体何时完成工作。有关完整的输入类型，请参阅 [TypeScript](/en/agent-sdk/typescript#hookinput) 和 [Python](/en/agent-sdk/python#hookinput) SDK 参考。此示例在每次子智能体完成时记录摘要：

<CodeGroup>
  ```python Python theme={null}
  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])]}
  )
  ```

  ```typescript TypeScript theme={null}
  import { HookCallback, SubagentStopHookInput } from "@anthropic-ai/claude-agent-sdk";

  const subagentTracker: HookCallback = async (input, toolUseID, { signal }) => {
    // Cast to SubagentStopHookInput to access subagent-specific fields
    const subInput = input as SubagentStopHookInput;

    // Log subagent details when it finishes
    console.log(`[SUBAGENT] Completed: ${subInput.agent_id}`);
    console.log(`  Transcript: ${subInput.agent_transcript_path}`);
    console.log(`  Tool use ID: ${toolUseID}`);
    console.log(`  Stop hook active: ${subInput.stop_hook_active}`);
    return {};
  };

  const options = {
    hooks: {
      SubagentStop: [{ hooks: [subagentTracker] }]
    }
  };
  ```
</CodeGroup>

### 从钩子发出 HTTP 请求

钩子可以执行异步操作，如 HTTP 请求。在钩子内部捕获错误而不是让它们传播，因为未处理的异常可能会中断智能体。

此示例在每个工具完成后发送 webhook，记录哪个工具运行以及何时运行。钩子捕获错误，因此失败的 webhook 不会中断智能体：

<CodeGroup>
  ```python Python theme={null}
  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 {}
  ```

  ```typescript TypeScript theme={null}
  import { query, HookCallback, PostToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";

  const webhookNotifier: HookCallback = async (input, toolUseID, { signal }) => {
    // Only fire after a tool completes (PostToolUse), not before
    if (input.hook_event_name !== "PostToolUse") return {};

    try {
      await fetch("https://api.example.com/webhook", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          tool: (input as PostToolUseHookInput).tool_name,
          timestamp: new Date().toISOString()
        }),
        // Pass signal so the request cancels if the hook times out
        signal
      });
    } catch (error) {
      // Handle cancellation separately from other errors
      if (error instanceof Error && error.name === "AbortError") {
        console.log("Webhook request cancelled");
      }
      // Don't re-throw. A failed webhook shouldn't stop the agent
    }

    return {};
  };

  // Register as a PostToolUse hook
  for await (const message of query({
    prompt: "Refactor the auth module",
    options: {
      hooks: {
        PostToolUse: [{ hooks: [webhookNotifier] }]
      }
    }
  })) {
    console.log(message);
  }
  ```
</CodeGroup>

### 将通知转发到 Slack

使用 `Notification` 钩子接收来自智能体的系统通知并将其转发到外部服务。通知针对特定事件类型触发：`permission_prompt`（Claude 需要权限）、`idle_prompt`（Claude 等待输入）、`auth_success`（认证完成）、`elicitation_dialog`（Claude 提示用户）、`elicitation_response`（用户回答了引出）和 `elicitation_complete`（引出关闭）。每个通知包含一个带有人类可读描述的 `message` 字段，以及可选的 `title`。

此示例将每个通知转发到 Slack 频道。它需要一个 [Slack 传入 webhook URL](https://api.slack.com/messaging/webhooks)，您可以通过向 Slack 工作区添加应用并启用传入 webhook 来创建：

<CodeGroup>
  ```python Python theme={null}
  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())
  ```

  ```typescript TypeScript theme={null}
  import { query, HookCallback, NotificationHookInput } from "@anthropic-ai/claude-agent-sdk";

  // Define a hook callback that sends notifications to Slack
  const notificationHandler: HookCallback = async (input, toolUseID, { signal }) => {
    // Cast to NotificationHookInput to access the message field
    const notification = input as NotificationHookInput;

    try {
      // POST the notification message to a Slack incoming webhook
      await fetch("https://hooks.slack.com/services/YOUR/WEBHOOK/URL", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          text: `Agent status: ${notification.message}`
        }),
        // Pass signal so the request cancels if the hook times out
        signal
      });
    } catch (error) {
      if (error instanceof Error && error.name === "AbortError") {
        console.log("Notification cancelled");
      } else {
        console.error("Failed to send notification:", error);
      }
    }

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

  // Register the hook for Notification events (no matcher needed)
  for await (const message of query({
    prompt: "Analyze this codebase",
    options: {
      hooks: {
        Notification: [{ hooks: [notificationHandler] }]
      }
    }
  })) {
    console.log(message);
  }
  ```
</CodeGroup>

## 修复常见问题

### 钩子未触发

* 验证钩子事件名称是否正确且区分大小写（`PreToolUse`，不是 `preToolUse`）
* 检查您的匹配器模式是否与工具名称完全匹配
* 确保钩子在 `options.hooks` 中的正确事件类型下
* 对于非工具钩子（如 `Stop` 和 `SubagentStop`），匹配器匹配不同的字段（请参阅[匹配器模式](/en/hooks#matcher-patterns)）
* 当智能体达到 [`max_turns`](/en/agent-sdk/python#claudeagentoptions) 限制时，钩子可能不会触发，因为会话在钩子执行之前就结束了

### 匹配器未按预期过滤

匹配器仅匹配**工具名称**，不匹配文件路径或其他参数。要按文件路径过滤，请在钩子内部检查 `tool_input.file_path`：

```typescript theme={null}
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 {};
};
```

### 钩子超时

* 增加 `HookMatcher` 配置中的 `timeout` 值
* 在 TypeScript 中使用第三个回调参数中的 `AbortSignal` 优雅地处理取消

### 工具被意外阻止

* 检查所有 `PreToolUse` 钩子是否有 `permissionDecision: 'deny'` 返回
* 在钩子中添加日志以查看它们返回的 `permissionDecisionReason`
* 验证匹配器模式是否过于宽泛（空匹配器匹配所有工具）

### 修改后的输入未被应用

* 确保 `updatedInput` 在 `hookSpecificOutput` 内部，而不是在顶级：

  ```typescript theme={null}
  return {
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "allow",
      updatedInput: { command: "new command" }
    }
  };
  ```

* 您还必须返回 `permissionDecision: 'allow'` 或 `'ask'` 才能使输入修改生效

* 在 `hookSpecificOutput` 中包含 `hookEventName` 以标识输出适用于哪种钩子类型

### Python 中不可用的会话钩子

`SessionStart` 和 `SessionEnd` 可以在 TypeScript 中注册为 SDK 回调钩子，但在 Python SDK 中不可用（`HookEvent` 省略了它们）。在 Python 中，它们仅作为在设置文件（例如 `.claude/settings.json`）中定义的 [shell 命令钩子](/en/hooks#hook-events)可用。要从您的 SDK 应用程序加载 shell 命令钩子，请使用 [`setting_sources`](/en/agent-sdk/python#settingsource) 或 [`settingSources`](/en/agent-sdk/typescript#settingsource) 包含适当的设置源：

<CodeGroup>
  ```python Python theme={null}
  options = ClaudeAgentOptions(
      setting_sources=["project"],  # Loads .claude/settings.json including hooks
  )
  ```

  ```typescript TypeScript theme={null}
  const options = {
    settingSources: ["project"] // Loads .claude/settings.json including hooks
  };
  ```
</CodeGroup>

要在 Python SDK 中作为回调运行初始化逻辑，请使用 `client.receive_response()` 的第一条消息作为触发器。

### 子智能体权限提示倍增

当生成多个子智能体时，每个子智能体可能单独请求权限。子智能体不会自动继承父智能体权限。要避免重复提示，请使用 `PreToolUse` 钩子自动批准特定工具，或配置适用于子智能体会话的权限规则。

### 子智能体的递归钩子循环

生成子智能体的 `UserPromptSubmit` 钩子如果这些子智能体触发相同的钩子，可能会创建无限循环。要防止这种情况：

* 在生成之前检查钩子输入中是否有子智能体指示器
* 使用共享变量或会话状态跟踪您是否已经在子智能体内部
* 将钩子限定为仅对顶级智能体会话运行

### systemMessage 未出现在输出中

`systemMessage` 字段向用户显示消息，而不是向模型显示。默认情况下，SDK 不会在消息流中显示钩子输出，因此除非您设置 `includeHookEvents`（Python 中的 `include_hook_events`），否则消息可能不会出现。要改为向模型传递上下文，请返回 [`additionalContext`](/en/hooks#add-context-for-claude)。

如果您需要可靠地将钩子决策显示给应用程序，请单独记录它们或使用专用输出通道。

## 相关资源

* [Claude Code 钩子参考](/en/hooks)：完整的 JSON 输入/输出模式、事件文档和匹配器模式
* [Claude Code 钩子指南](/en/hooks-guide)：shell 命令钩子示例和演练
* [TypeScript SDK 参考](/en/agent-sdk/typescript)：钩子类型、输入/输出定义和配置选项
* [Python SDK 参考](/en/agent-sdk/python)：钩子类型、输入/输出定义和配置选项
* [权限](/en/agent-sdk/permissions)：控制您的智能体可以做什么
* [自定义工具](/en/agent-sdk/custom-tools)：构建工具以扩展智能体功能
