English ← MyDocs

文档索引

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

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

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

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

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

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

钩子的工作原理

  1. 事件触发

    在智能体执行期间发生某些事情,SDK 触发一个事件:工具即将被调用(PreToolUse)、工具返回了结果(PostToolUse)、子智能体启动或停止、智能体空闲或执行完成。请参阅完整事件列表

  2. SDK 收集已注册的钩子

    SDK 检查为该事件类型注册的钩子。这包括您在 options.hooks 中传递的回调钩子,以及当相应的 settingSourcessetting_sources 条目启用时(默认 query() 选项会启用)来自设置文件的 shell 命令钩子。

  3. 匹配器过滤哪些钩子运行

    如果钩子有 matcher 模式(如 "Write|Edit"),SDK 会针对事件的目标(例如工具名称)进行测试。没有匹配器的钩子对该类型的每个事件都运行。

  4. 回调函数执行

    每个匹配的钩子的回调函数接收关于正在发生什么的输入:工具名称、其参数、会话 ID 和其他事件特定的详细信息。

  5. 您的回调返回决策

    执行任何操作(记录、API 调用、验证)后,您的回调返回一个输出对象,告诉智能体该做什么:允许操作、阻止操作、修改输入或向对话注入上下文。

以下示例将这些步骤组合在一起。它注册了一个带有 "Write|Edit" 匹配器(步骤 3)的 PreToolUse 钩子(步骤 1),因此回调仅对文件写入工具触发。触发时,回调接收工具的输入(步骤 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())
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);
  }
}

可用钩子

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

钩子事件Python SDKTypeScript SDK触发条件示例用例
PreToolUse工具调用请求(可以阻止或修改)阻止危险的 shell 命令
PostToolUse工具执行结果将所有文件更改记录到审计跟踪
PostToolUseFailure工具执行失败处理或记录工具错误
PostToolBatch整批工具调用解决,每批在下一次模型调用之前一次为整批注入一次规范
UserPromptSubmit用户提示提交向提示注入额外上下文
Stop智能体执行停止退出前保存会话状态
SubagentStart子智能体初始化跟踪并行任务生成
SubagentStop子智能体完成聚合并行任务的结果
PreCompact对话压缩请求在摘要之前存档完整记录
PermissionRequest权限对话框将被显示自定义权限处理
SessionStart会话初始化初始化日志和遥测
SessionEnd会话终止清理临时资源
Notification智能体状态消息将智能体状态更新发送到 Slack 或 PagerDuty
Setup会话设置/维护运行初始化任务
TeammateIdle队友变为空闲重新分配工作或通知
TaskCompleted后台任务完成聚合并行任务的结果
ConfigChange配置文件更改动态重新加载设置
WorktreeCreateGit 工作树创建跟踪隔离的工作空间
WorktreeRemoveGit 工作树移除清理工作空间资源

配置钩子

要配置钩子,在智能体选项的 hooks 字段中传递它(Python 中的 ClaudeAgentOptions,TypeScript 中的 options 对象):

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)
for await (const message of query({
  prompt: "Your prompt",
  options: {
    hooks: {
      PreToolUse: [{ matcher: "Bash", hooks: [myCallback] }]
    }
  }
})) {
  console.log(message);
}

hooks 选项是一个字典(Python)或对象(TypeScript),其中:

匹配器

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

选项类型默认值描述
matcherstringundefined与事件过滤字段匹配的正则表达式模式。对于工具钩子,这是工具名称。内置工具包括 BashReadWriteEditGlobGrepWebFetchAgent 等(有关完整列表,请参阅工具输入类型)。MCP 工具使用模式 mcp__<server>__<action>
hooksHookCallback[]-必需。模式匹配时要执行的回调函数数组
timeoutnumber60超时时间(秒)

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

Tip

发现工具名称: 有关内置工具名称的完整列表,请参阅工具输入类型,或添加不带匹配器的钩子来记录会话进行的所有工具调用。

MCP 工具命名: MCP 工具始终以 mcp__ 开头,后跟服务器名称和操作:mcp__<server>__<action>。例如,如果您配置了名为 playwright 的服务器,其工具将命名为 mcp__playwright__browser_screenshotmcp__playwright__browser_click 等。服务器名称来自您在 mcpServers 配置中使用的键。

回调函数

输入

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

  • 输入数据: 包含事件详细信息的类型化对象。每种钩子类型都有自己的输入形状(例如 PreToolUseHookInput 包含 tool_nametool_input,而 NotificationHookInput 包含 message)。有关完整的类型定义,请参阅 TypeScriptPython SDK 参考。
    • 所有钩子输入共享 session_idcwdhook_event_name
    • 当钩子在子智能体内部触发时,agent_idagent_type 会被填充。在 TypeScript 中,这些在基础钩子输入上,对所有钩子类型可用。在 Python 中,它们仅在 PreToolUsePostToolUsePostToolUseFailure 上。
  • 工具使用 IDstr | None / string | undefined):将同一工具调用的 PreToolUsePostToolUse 事件关联起来。
  • 上下文: 在 TypeScript 中,包含用于取消的 signal 属性(AbortSignal)。在 Python 中,此参数保留供将来使用。

输出

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

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

返回 {} 以允许操作不做更改。SDK 回调钩子使用与 Claude Code shell 命令钩子相同的 JSON 输出格式,后者记录了每个字段和事件特定选项。有关 SDK 类型定义,请参阅 TypeScriptPython SDK 参考。

Note

当多个钩子或权限规则适用时,deny 优先于 deferdefer 优先于 askask 优先于 allow。如果任何钩子返回 deny,无论其他钩子如何,操作都会被阻止。

异步输出

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

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}
const asyncHook: HookCallback = async (input, toolUseID, { signal }) => {
  // Start a background task, then return immediately
  sendToLoggingService(input).catch(console.error);
  return { async: true, asyncTimeout: 30000 };
};
字段类型描述
asynctrue发出异步模式信号。智能体继续而不等待。在 Python 中,使用 async_ 以避免保留关键字。
asyncTimeoutnumber可选的后台操作超时时间(毫秒)
Note

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

示例

修改工具输入

此示例拦截 Write 工具调用并重写 file_path 参数以在前面添加 /sandbox,将所有文件写入重定向到沙箱目录。回调返回带有修改路径的 updatedInputpermissionDecision: '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 {}
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 {};
};
Note

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

添加上下文并阻止工具

此示例阻止对 /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 {}
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 {};
};

自动批准特定工具

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

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 {}
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 {};
};

注册多个钩子

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

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

options = ClaudeAgentOptions(
    hooks={
        "PreToolUse": [
            HookMatcher(hooks=[authorization_check]),
            HookMatcher(hooks=[input_validator]),
            HookMatcher(hooks=[audit_logger]),
        ]
    }
)
const options = {
  hooks: {
    PreToolUse: [
      { hooks: [authorizationCheck] },
      { hooks: [inputValidator] },
      { hooks: [auditLogger] }
    ]
  }
};

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

使用正则表达式模式匹配多个工具。此示例注册了三个具有不同作用域的匹配器:第一个仅对文件修改工具触发 file_security_hook,第二个对任何 MCP 工具(名称以 mcp__ 开头的工具)触发 mcp_audit_hook,第三个对每个工具调用触发 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]),
        ]
    }
)
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] }
    ]
  }
};

跟踪子智能体活动

使用 SubagentStop 钩子监控子智能体何时完成工作。有关完整的输入类型,请参阅 TypeScriptPython 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])]}
)
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] }]
  }
};

从钩子发出 HTTP 请求

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

此示例在每个工具完成后发送 webhook,记录哪个工具运行以及何时运行。钩子捕获错误,因此失败的 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 {}
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);
}

将通知转发到 Slack

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

此示例将每个通知转发到 Slack 频道。它需要一个 Slack 传入 webhook URL,您可以通过向 Slack 工作区添加应用并启用传入 webhook 来创建:

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())
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);
}

修复常见问题

钩子未触发

  • 验证钩子事件名称是否正确且区分大小写(PreToolUse,不是 preToolUse
  • 检查您的匹配器模式是否与工具名称完全匹配
  • 确保钩子在 options.hooks 中的正确事件类型下
  • 对于非工具钩子(如 StopSubagentStop),匹配器匹配不同的字段(请参阅匹配器模式
  • 当智能体达到 max_turns 限制时,钩子可能不会触发,因为会话在钩子执行之前就结束了

匹配器未按预期过滤

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

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
  • 验证匹配器模式是否过于宽泛(空匹配器匹配所有工具)

修改后的输入未被应用

  • 确保 updatedInputhookSpecificOutput 内部,而不是在顶级:

    return {
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "allow",
        updatedInput: { command: "new command" }
      }
    };
    
  • 您还必须返回 permissionDecision: 'allow''ask' 才能使输入修改生效

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

Python 中不可用的会话钩子

SessionStartSessionEnd 可以在 TypeScript 中注册为 SDK 回调钩子,但在 Python SDK 中不可用(HookEvent 省略了它们)。在 Python 中,它们仅作为在设置文件(例如 .claude/settings.json)中定义的 shell 命令钩子可用。要从您的 SDK 应用程序加载 shell 命令钩子,请使用 setting_sourcessettingSources 包含适当的设置源:

options = ClaudeAgentOptions(
    setting_sources=["project"],  # Loads .claude/settings.json including hooks
)
const options = {
  settingSources: ["project"] // Loads .claude/settings.json including hooks
};

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

子智能体权限提示倍增

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

子智能体的递归钩子循环

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

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

systemMessage 未出现在输出中

systemMessage 字段向用户显示消息,而不是向模型显示。默认情况下,SDK 不会在消息流中显示钩子输出,因此除非您设置 includeHookEvents(Python 中的 include_hook_events),否则消息可能不会出现。要改为向模型传递上下文,请返回 additionalContext

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

相关资源