跳轉到主要內容
Hooks 是回調函數,在代理事件發生時執行您的程式碼,例如工具被呼叫、會話啟動或執行停止。使用 hooks,您可以:
  • 阻止危險操作在執行前,例如破壞性的 shell 命令或未授權的檔案存取
  • 記錄和審計每個工具呼叫以進行合規性、除錯或分析
  • 轉換輸入和輸出以清理資料、注入認證或重定向檔案路徑
  • 要求人工批准敏感操作,例如資料庫寫入或 API 呼叫
  • 追蹤會話生命週期以管理狀態、清理資源或傳送通知
本指南涵蓋 hooks 的工作原理、如何配置它們,並提供常見模式的範例,例如阻止工具、修改輸入和轉發通知。

Hooks 如何工作

1

事件觸發

代理執行期間發生某事,SDK 觸發事件:工具即將被呼叫(PreToolUse)、工具返回結果(PostToolUse)、子代理啟動或停止、代理空閒或執行完成。請參閱完整事件列表
2

SDK 收集已註冊的 hooks

SDK 檢查為該事件類型註冊的 hooks。這包括您在 options.hooks 中傳遞的回調 hooks 和來自設定檔案的 shell 命令 hooks,當相應的 settingSourcessetting_sources 項目啟用時(預設 query() 選項就是這樣)。
3

匹配器篩選哪些 hooks 執行

如果 hook 有 matcher 模式(例如 "Write|Edit"),SDK 會針對事件的目標(例如工具名稱)測試它。沒有匹配器的 hooks 會針對該類型的每個事件執行。
4

回調函數執行

每個匹配的 hook 的回調函數接收有關正在發生的事情的輸入:工具名稱、其參數、會話 ID 和其他事件特定的詳細資訊。
5

您的回調返回決定

執行任何操作(記錄、API 呼叫、驗證)後,您的回調返回輸出物件,告訴代理該做什麼:允許操作、阻止它、修改輸入或將上下文注入對話。
以下範例將這些步驟組合在一起。它註冊一個 PreToolUse hook(步驟 1),帶有 "Write|Edit" 匹配器(步驟 3),因此回調只針對檔案寫入工具觸發。觸發時,回調接收工具的輸入(步驟 4),檢查檔案路徑是否針對 .env 檔案,並返回 permissionDecision: "deny" 以阻止操作(步驟 5):
import asyncio
from claude_agent_sdk import (
    AssistantMessage,
    ClaudeSDKClient,
    ClaudeAgentOptions,
    HookMatcher,
    ResultMessage,
)


# 定義一個接收工具呼叫詳細資訊的 hook 回調
async def protect_env_files(input_data, tool_use_id, context):
    # 從工具的輸入參數中提取檔案路徑
    file_path = input_data["tool_input"].get("file_path", "")
    file_name = file_path.split("/")[-1]

    # 如果針對 .env 檔案,阻止操作
    if file_name == ".env":
        return {
            "hookSpecificOutput": {
                "hookEventName": input_data["hook_event_name"],
                "permissionDecision": "deny",
                "permissionDecisionReason": "Cannot modify .env files",
            }
        }

    # 返回空物件以允許操作
    return {}


async def main():
    options = ClaudeAgentOptions(
        hooks={
            # 為 PreToolUse 事件註冊 hook
            # 匹配器篩選為僅 Write 和 Edit 工具呼叫
            "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():
            # 篩選助手和結果訊息
            if isinstance(message, (AssistantMessage, ResultMessage)):
                print(message)


asyncio.run(main())

可用的 hooks

SDK 為代理執行的不同階段提供 hooks。某些 hooks 在兩個 SDK 中都可用,而其他則僅限 TypeScript。
Hook 事件Python SDKTypeScript SDK觸發條件使用案例範例
PreToolUse工具呼叫請求(可以阻止或修改)阻止危險的 shell 命令
PostToolUse工具執行結果將所有檔案變更記錄到審計追蹤
PostToolUseFailure工具執行失敗處理或記錄工具錯誤
PostToolBatch一整批工具呼叫解決,每批一次,在下一個模型呼叫之前為整個批次注入約定
UserPromptSubmit使用者提示提交將額外上下文注入提示
MessageDisplay助手訊息包含文字完成,每則訊息一次,包含完整訊息文字編輯或重新格式化顯示的文字,不改變記錄
Stop代理執行停止在退出前保存會話狀態
SubagentStart子代理初始化追蹤平行任務生成
SubagentStop子代理完成聚合來自平行任務的結果
PreCompact對話壓縮請求在摘要前存檔完整記錄
PermissionRequest權限對話將顯示自訂權限處理
SessionStart會話初始化初始化記錄和遙測
SessionEnd會話終止清理臨時資源
Notification代理狀態訊息將代理狀態更新傳送到 Slack 或 PagerDuty
Setup會話設定/維護執行初始化任務
TeammateIdle隊友變為空閒重新分配工作或通知
TaskCompleted背景任務完成聚合來自平行任務的結果
ConfigChange配置檔案變更動態重新載入設定
WorktreeCreateGit worktree 已建立追蹤隔離的工作區
WorktreeRemoveGit worktree 已移除清理工作區資源

配置 hooks

要配置 hook,請在代理選項的 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)
hooks 選項是一個字典(Python)或物件(TypeScript),其中:

匹配器

使用匹配器篩選您的回調何時觸發。matcher 欄位根據 hook 事件類型匹配不同的值。例如,工具型 hooks 匹配工具名稱,而 Notification hooks 匹配通知類型。請參閱 Claude Code hooks 參考以取得每個事件類型的完整匹配器值列表。 SDK 匹配器遵循與設定檔案中的匹配器相同的規則:只包含字母、數字、_| 的匹配器會被比較為精確字串,其中 | 分隔替代項,因此 Write|Edit 精確匹配這兩個工具。匹配器 *、空字串或完全省略匹配器會匹配事件的每次出現;包含任何其他字元的匹配器會被評估為正規表達式,因此 ^mcp__ 匹配每個 MCP 工具。像 mcp__memory 這樣的匹配器只包含字母和底線,因此會被比較為精確字串且不匹配任何工具;使用 mcp__memory__.* 來匹配來自該伺服器的每個工具。
選項類型預設值描述
matcherstringundefined針對事件的篩選欄位匹配的模式,遵循上述比較規則。對於工具 hooks,這是工具名稱。內建工具包括 BashReadWriteEditGlobGrepWebFetchAgent 等(請參閱工具輸入類型以取得完整列表)。MCP 工具使用模式 mcp__<server>__<action>
hooksHookCallback[]-必需。當模式匹配時執行的回調函數陣列
timeoutnumber60超時時間(秒)
盡可能使用 matcher 模式來針對特定工具。帶有 'Bash' 的匹配器只針對 Bash 命令執行,而省略模式會針對事件的每次出現執行您的回調。請注意,對於工具型 hooks,匹配器只按工具名稱篩選,不按檔案路徑或其他參數篩選。要按檔案路徑篩選,請在回調內檢查 tool_input.file_path
發現工具名稱: 請參閱工具輸入類型以取得內建工具名稱的完整列表,或新增沒有匹配器的 hook 以記錄您的會話進行的所有工具呼叫。MCP 工具命名: MCP 工具始終以 mcp__ 開頭,後跟伺服器名稱和操作:mcp__<server>__<action>。例如,如果您配置名為 playwright 的伺服器,其工具將被命名為 mcp__playwright__browser_screenshotmcp__playwright__browser_click 等。伺服器名稱來自您在 mcpServers 配置中使用的鍵。

回調函數

輸入

每個 hook 回調接收三個參數:
  • 輸入資料: 一個包含事件詳細資訊的類型物件。每個 hook 類型都有自己的輸入形狀(例如,PreToolUseHookInput 包括 tool_nametool_input,而 NotificationHookInput 包括 message)。請參閱 TypeScriptPython SDK 參考中的完整類型定義。
    • 所有 hook 輸入共享 session_idcwdhook_event_name
    • 當 hook 在子代理內觸發時,agent_idagent_type 會被填充。在 TypeScript 中,這些在基本 hook 輸入上,可供所有 hook 類型使用。在 Python 中,它們僅在 PreToolUsePostToolUsePostToolUseFailure 上。
  • 工具使用 IDstr | None / string | undefined):關聯同一工具呼叫的 PreToolUsePostToolUse 事件。
  • 上下文: 在 TypeScript 中,包含用於取消的 signal 屬性(AbortSignal)。在 Python 中,此參數保留供將來使用。

輸出

您的回調返回一個具有兩類欄位的物件:
  • 頂級欄位在每個事件上的工作方式相同:systemMessage 向使用者顯示訊息,continue(Python 中的 continue_)決定此 hook 後代理是否繼續執行。
  • hookSpecificOutput 控制目前操作。內部的欄位取決於 hook 事件類型。對於 PreToolUse hooks,這是您設定 permissionDecision"allow""deny""ask""defer")、permissionDecisionReasonupdatedInput 的地方。返回 "defer" 會結束查詢,以便您可以稍後繼續。對於 PostToolUse hooks,您可以設定 additionalContext 以將資訊附加到工具結果。要在 Claude 看到之前替換工具的輸出,請設定 updatedToolOutput,這適用於兩個 SDK 中的任何工具。較舊的 updatedMCPToolOutput 欄位僅替換 MCP 工具輸出,已被棄用。
返回 {} 以允許操作而不進行變更。SDK 回調 hooks 使用與 Claude Code shell 命令 hooks 相同的 JSON 輸出格式,其記錄每個欄位和事件特定選項。對於 SDK 類型定義,請參閱 TypeScriptPython SDK 參考。
當多個 hooks 或權限規則適用時,deny 優先於 deferdefer 優先於 askask 優先於 allow。如果任何 hook 返回 deny,操作將被阻止,無論其他 hooks 如何。

非同步輸出

預設情況下,代理在您的 hook 返回前等待。如果您的 hook 執行副作用(記錄、傳送 webhook)並且不需要影響代理的行為,您可以改為返回非同步輸出。這告訴代理立即繼續,無需等待 hook 完成:
async def async_hook(input_data, tool_use_id, context):
    # 啟動背景任務,然後立即返回
    asyncio.create_task(send_to_logging_service(input_data))
    return {"async_": True, "asyncTimeout": 30000}
欄位類型描述
asynctrue表示非同步模式。代理無需等待即可繼續。在 Python 中,使用 async_ 以避免保留關鍵字。
asyncTimeoutnumber背景操作的可選超時時間(毫秒)
非同步輸出無法阻止、修改或將上下文注入操作,因為代理已經繼續。僅將它們用於副作用,例如記錄、指標或通知。

範例

修改工具輸入

此範例攔截 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 {}
使用 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 {
            # 頂級欄位:顯示給使用者的訊息
            "systemMessage": "Remember: system directories like /etc are protected.",
            # hookSpecificOutput:阻止操作
            "hookSpecificOutput": {
                "hookEventName": input_data["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 {}

註冊多個 hooks

當事件觸發時,所有匹配的 hooks 並行執行。對於權限決定,最嚴格的結果獲勝:單個 deny 會阻止工具呼叫,無論其他 hooks 返回什麼。由於完成順序是不確定的,請編寫每個 hook 以獨立行動,而不是依賴另一個 hook 已執行。 下面的範例為每個工具呼叫註冊三個獨立檢查:
options = ClaudeAgentOptions(
    hooks={
        "PreToolUse": [
            HookMatcher(hooks=[authorization_check]),
            HookMatcher(hooks=[input_validator]),
            HookMatcher(hooks=[audit_logger]),
        ]
    }
)

使用多工具匹配器篩選

使用多工具匹配器在相關工具間共享一個回調。此範例註冊三個具有不同範圍的匹配器:
  • 管道分隔的精確列表(Write|Edit|Delete)僅針對檔案修改工具觸發 file_security_hook
  • 正規表達式(^mcp__)針對任何名稱以 mcp__ 開頭的 MCP 工具觸發 mcp_audit_hook
  • 省略的匹配器針對每個工具呼叫(無論名稱如何)觸發 global_logger
options = ClaudeAgentOptions(
    hooks={
        "PreToolUse": [
            # 匹配檔案修改工具
            HookMatcher(matcher="Write|Edit|Delete", hooks=[file_security_hook]),
            # 匹配所有 MCP 工具
            HookMatcher(matcher="^mcp__", hooks=[mcp_audit_hook]),
            # 匹配所有內容(無匹配器)
            HookMatcher(hooks=[global_logger]),
        ]
    }
)

追蹤子代理活動

使用 SubagentStop hooks 監控子代理何時完成其工作。請參閱 TypeScriptPython SDK 參考中的完整輸入類型。此範例在每次子代理完成時記錄摘要:
async def subagent_tracker(input_data, tool_use_id, context):
    # 子代理完成時記錄子代理詳細資訊
    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])]}
)

從 hooks 發出 HTTP 請求

Hooks 可以執行非同步操作,例如 HTTP 請求。在您的 hook 內捕捉錯誤,而不是讓它們傳播,因為未處理的異常可能會中斷代理。 此範例在每個工具完成後傳送 webhook,記錄哪個工具執行以及何時執行。hook 捕捉錯誤,以便失敗的 webhook 不會中斷代理:
import asyncio
import json
import urllib.request
from datetime import datetime


def _send_webhook(tool_name):
    """同步幫助程式,將工具使用資料 POST 到外部 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):
    # 僅在工具完成後觸發(PostToolUse),而不是之前
    if input_data["hook_event_name"] != "PostToolUse":
        return {}

    try:
        # 在執行緒中執行阻止 HTTP 呼叫以避免阻止事件迴圈
        await asyncio.to_thread(_send_webhook, input_data["tool_name"])
    except Exception as e:
        # 記錄錯誤但不引發。失敗的 webhook 不應停止代理
        print(f"Webhook request failed: {e}")

    return {}

將通知轉發到 Slack

使用 Notification hooks 接收來自代理的系統通知並將其轉發到外部服務。通知針對特定事件類型觸發,例如:
  • permission_prompt 當 Claude 需要權限時
  • idle_prompt 當 Claude 等待輸入時
  • auth_success 當認證完成時
  • elicitation_dialogelicitation_completeelicitation_response 用於使用者提示引導流程
每個通知都包括帶有人類可讀描述的 message 欄位,以及可選的 title 此範例將每個通知轉發到 Slack 頻道。它需要 Slack 傳入 webhook URL,您可以通過將應用程式新增到 Slack 工作區並啟用傳入 webhooks 來建立:
import asyncio
import json
import urllib.request

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, HookMatcher


def _send_slack_notification(message):
    """同步幫助程式,通過傳入 webhook 將訊息傳送到 Slack。"""
    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:
        # 在執行緒中執行阻止 HTTP 呼叫以避免阻止事件迴圈
        await asyncio.to_thread(_send_slack_notification, input_data.get("message", ""))
    except Exception as e:
        print(f"Failed to send notification: {e}")

    # 返回空物件。通知 hooks 不修改代理行為
    return {}


async def main():
    options = ClaudeAgentOptions(
        hooks={
            # 為通知事件註冊 hook(不需要匹配器)
            "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
  • 檢查您的匹配器模式是否與工具名稱完全匹配
  • 確保 hook 在 options.hooks 中的正確事件類型下
  • 對於非工具 hooks,如 StopSubagentStop,匹配器匹配不同的欄位(請參閱匹配器模式
  • 當代理達到 max_turns 限制時,hooks 可能不會觸發,因為會話在 hooks 可以執行前結束

匹配器未按預期篩選

匹配器只匹配工具名稱,不匹配檔案路徑或其他參數。要按檔案路徑篩選,請在您的 hook 內檢查 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 {}; // 跳過非 markdown 檔案
  // 處理 markdown 檔案...
  return {};
};

Hook 超時

  • 增加 HookMatcher 配置中的 timeout
  • 在 TypeScript 中使用第三個回調參數中的 AbortSignal 以優雅地處理取消

工具意外被阻止

  • 檢查所有 PreToolUse hooks 是否返回 permissionDecision: 'deny'
  • 將記錄新增到您的 hooks 以查看它們返回的 permissionDecisionReason
  • 驗證匹配器模式不會太寬泛(空匹配器匹配所有工具)

修改的輸入未應用

  • 確保 updatedInputhookSpecificOutput 內,而不是在頂級:
    return {
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "allow",
        updatedInput: { command: "new command" }
      }
    };
    
  • 返回 permissionDecision: 'allow' 以自動批准修改的輸入,或 'ask' 以向使用者顯示以供批准
  • hookSpecificOutput 中包括 hookEventName 以識別輸出適用於哪個 hook 類型

Python 中不可用會話 hooks

SessionStartSessionEnd 可以在 TypeScript 中註冊為 SDK 回調 hooks,但在 Python SDK 中不可用(HookEvent 省略它們)。在 Python 中,它們僅作為shell 命令 hooks 在設定檔案中定義(例如 .claude/settings.json)。要從您的 SDK 應用程式載入 shell 命令 hooks,請使用 setting_sourcessettingSources 包括適當的設定來源:
options = ClaudeAgentOptions(
    setting_sources=["project"],  # 載入 .claude/settings.json 包括 hooks
)
要改為執行初始化邏輯作為 Python SDK 回調,請使用 client.receive_response() 的第一條訊息作為您的觸發器。

子代理權限提示倍增

生成多個子代理時,每個子代理可能會分別請求權限。子代理不會自動繼承父代理權限。要避免重複提示,請使用 PreToolUse hooks 自動批准特定工具,或配置適用於子代理會話的權限規則。

子代理的遞迴 hook 迴圈

生成子代理的 UserPromptSubmit hook 如果這些子代理觸發相同的 hook,可能會建立無限迴圈。要防止這種情況:
  • 在生成子代理前檢查 hook 輸入中的子代理指示器
  • 使用共享變數或會話狀態來追蹤您是否已在子代理內
  • 將 hooks 範圍限制為僅針對頂級代理會話執行

systemMessage 未出現在輸出中

systemMessage 欄位向使用者顯示訊息,而不是模型。預設情況下,SDK 不會在訊息流中呈現 hook 輸出,因此除非您設定 includeHookEvents(Python 中的 include_hook_events),否則訊息可能不會出現。要改為將上下文傳遞給模型,請返回 additionalContext 如果您需要可靠地將 hook 決定呈現給您的應用程式,請分別記錄它們或使用專用輸出頻道。

相關資源