SpyBara
Go Premium

agent-sdk/hooks.md 2026-06-29 23:02 UTC to 2026-06-30 23:02 UTC

19 added, 11 removed.

2026
Tue 30 23:02 Mon 29 23:02 Sat 27 01:01 Fri 26 23:00 Thu 25 23:58 Wed 24 22:02 Tue 23 22:00 Mon 22 23:59 Fri 19 22:58 Thu 18 22:00 Wed 17 17:02 Tue 16 21:57 Mon 15 23:02 Sat 13 21:59 Fri 12 22:00 Thu 11 23:01 Wed 10 23:57 Tue 9 06:34 Mon 8 06:52 Sat 6 06:24 Fri 5 06:45 Thu 4 06:52 Wed 3 06:53 Tue 2 06:51

使用 hooks 攔截和控制代理行為

在代理執行的關鍵點使用 hooks 攔截和自訂代理行為

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 SDK TypeScript SDK 觸發條件 使用案例範例
PreToolUse 工具呼叫請求(可以阻止或修改) 阻止危險的 shell 命令
PostToolUse 工具執行結果 將所有檔案變更記錄到審計追蹤
PostToolUseFailure 工具執行失敗 處理或記錄工具錯誤
PostToolBatch 一整批工具呼叫解決,每批一次,在下一個模型呼叫之前 為整個批次注入約定
UserPromptSubmit 使用者提示提交 將額外上下文注入提示
MessageDisplay 助手訊息包含文字完成,每則訊息一次,包含完整訊息文字 編輯或重新格式化顯示的文字,不改變記錄
Stop 代理執行停止 在退出前保存會話狀態
SubagentStart 子代理初始化 追蹤平行任務生成
SubagentStop 子代理完成 聚合來自平行任務的結果
PreCompact 對話壓縮請求 在摘要前存檔完整記錄
PermissionRequest 權限對話將顯示 自訂權限處理
SessionStart 會話初始化 初始化記錄和遙測
SessionEnd 會話終止 清理臨時資源
Notification 代理狀態訊息 將代理狀態更新傳送到 Slack 或 PagerDuty
Setup 會話設定/維護 執行初始化任務
TeammateIdle 隊友變為空閒 重新分配工作或通知
TaskCompleted 背景任務完成 聚合來自平行任務的結果
ConfigChange 配置檔案變更 動態重新載入設定
WorktreeCreate Git worktree 已建立 追蹤隔離的工作區
WorktreeRemove Git 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|EditWrite, Edit 各自精確匹配這兩個工具,而 code-reviewer 只匹配該代理類型。匹配器 *、空字串或完全省略匹配器會匹配事件的每次出現。

包含任何其他字元的匹配器會被評估為未錨定的正規表達式,因此 ^mcp__ 匹配每個 MCP 工具,而 Edit.* 同時匹配 EditNotebookEdit。當您需要全字符串匹配時,請用 ^$ 包裝正規表達式。

mcp__memorymcp__brave-search 這樣的匹配器只包含精確匹配字元,因此會被比較為精確字串且不匹配任何工具;使用 mcp__memory__.* 來匹配來自該伺服器的每個工具。

精確匹配集中的連字號需要 Claude Code 執行時版本 v2.1.195 或更新版本。在較早版本中,像 code-reviewer 這樣的連字號名稱會被評估為未錨定的正規表達式,必須錨定為 ^code-reviewer$ 才能精確匹配。

選項 類型 預設值 描述
matcher string undefined 針對事件的篩選欄位匹配的模式,遵循上述比較規則。對於工具 hooks,這是工具名稱。內建工具包括 BashReadWriteEditGlobGrepWebFetchAgent 等(請參閱工具輸入類型以取得完整列表)。MCP 工具使用模式 mcp__<server>__<action>
hooks HookCallback[] - 必需。當模式匹配時執行的回調函數陣列
timeout number 60 超時時間(秒)

盡可能使用 matcher 模式來針對特定工具。帶有 'Bash' 的匹配器只針對 Bash 命令執行,而省略模式會針對事件的每次出現執行您的回調。

對於工具型 hooks,匹配器只按工具名稱篩選,不按檔案路徑或其他參數篩選。要按檔案路徑篩選,請在回調內檢查 tool_input.file_path

回調函數

輸入

每個 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 參考。

非同步輸出

預設情況下,代理在您的 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}
欄位 類型 描述
async true 表示非同步模式。代理無需等待即可繼續。在 Python 中,使用 async_ 以避免保留關鍵字。
asyncTimeout number 背景操作的可選超時時間(毫秒)

範例

修改工具輸入

此範例攔截 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 {}

新增上下文並阻止工具

此範例阻止寫入 /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,如 NotificationSubagentStop,匹配器匹配不同的欄位,而 Stop 完全忽略匹配器(請參閱匹配器模式
  • 當代理達到 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 決定呈現給您的應用程式,請分別記錄它們或使用專用輸出頻道。