Hooks 讓您在關鍵點攔截代理執行,以新增驗證、日誌記錄、安全控制或自訂邏輯。透過 hooks,您可以:
一個 hook 有兩個部分:
PreToolUse)以及要匹配哪些工具以下範例阻止代理修改 .env 檔案。首先,定義一個檢查檔案路徑的回呼,然後將其傳遞給 query() 以在任何 Write 或 Edit 工具呼叫之前執行:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher
# 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():
async for message in query(
prompt="Update the database configuration",
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])]
}
)
):
print(message)
asyncio.run(main())這是一個 PreToolUse hook。它在工具執行之前執行,可以根據您的邏輯阻止或允許操作。本指南的其餘部分涵蓋所有可用的 hooks、它們的設定選項,以及常見使用案例的模式。
SDK 為代理執行的不同階段提供 hooks。某些 hooks 在兩個 SDK 中都可用,而其他的僅限 TypeScript,因為 Python SDK 不支援它們。
| Hook 事件 | Python SDK | TypeScript SDK | 觸發條件 | 範例使用案例 |
|---|---|---|---|---|
PreToolUse | 是 | 是 | 工具呼叫請求(可阻止或修改) | 阻止危險的 shell 命令 |
PostToolUse | 是 | 是 | 工具執行結果 | 將所有檔案變更記錄到稽核軌跡 |
PostToolUseFailure | 否 | 是 | 工具執行失敗 | 處理或記錄工具錯誤 |
UserPromptSubmit | 是 | 是 | 使用者提示提交 | 將額外上下文注入提示 |
Stop | 是 | 是 | 代理執行停止 | 在退出前儲存工作階段狀態 |
SubagentStart | 否 | 是 | 子代理初始化 | 追蹤平行任務產生 |
SubagentStop | 是 | 是 | 子代理完成 | 彙總平行任務的結果 |
PreCompact | 是 | 是 | 對話壓縮請求 | 在摘要之前封存完整記錄 |
PermissionRequest | 否 | 是 | 將顯示權限對話框 | 自訂權限處理 |
SessionStart | 否 | 是 | 工作階段初始化 | 初始化日誌記錄和遙測 |
SessionEnd | 否 | 是 | 工作階段終止 | 清理臨時資源 |
Notification | 否 | 是 | 代理狀態訊息 | 將代理狀態更新傳送到 Slack 或 PagerDuty |
Hooks 足夠靈活,可以處理許多不同的場景。以下是按類別組織的一些最常見模式。
要為您的代理設定 hook,請在呼叫 query() 時透過 options.hooks 參數傳遞 hook:
async for message in query(
prompt="Your prompt",
options=ClaudeAgentOptions(
hooks={
'PreToolUse': [HookMatcher(matcher='Bash', hooks=[my_callback])]
}
)
):
print(message)hooks 選項是一個字典(Python)或物件(TypeScript),其中:
您的 hook 回呼函式接收關於事件的輸入資料,並回傳一個回應,讓代理知道要允許、阻止或修改操作。
使用匹配器來篩選哪些工具觸發您的回呼:
| 選項 | 類型 | 預設值 | 描述 |
|---|---|---|---|
matcher | string | undefined | 用於匹配工具名稱的正規表達式模式。內建工具包括 Bash、Read、Write、Edit、Glob、Grep、WebFetch、Task 等。MCP 工具使用 mcp__<server>__<action> 模式。 |
hooks | HookCallback[] | - | 必填。當模式匹配時要執行的回呼函式陣列 |
timeout | number | 60 | 逾時秒數;對於進行外部 API 呼叫的 hooks 請增加此值 |
盡可能使用 matcher 模式來鎖定特定工具。使用 'Bash' 的匹配器只會對 Bash 命令執行,而省略模式則會對每個工具呼叫執行您的回呼。請注意,匹配器只按工具名稱篩選,不按檔案路徑或其他引數篩選——要按檔案路徑篩選,請在回呼內部檢查 tool_input.file_path。
匹配器僅適用於基於工具的 hooks(PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest)。對於生命週期 hooks 如 Stop、SessionStart 和 Notification,匹配器會被忽略,hook 會對該類型的所有事件觸發。
探索工具名稱: 在工作階段啟動時檢查初始系統訊息中的 tools 陣列,或新增一個沒有匹配器的 hook 來記錄所有工具呼叫。
MCP 工具命名: MCP 工具始終以 mcp__ 開頭,後接伺服器名稱和操作:mcp__<server>__<action>。例如,如果您設定了一個名為 playwright 的伺服器,其工具將命名為 mcp__playwright__browser_screenshot、mcp__playwright__browser_click 等。伺服器名稱來自您在 mcpServers 設定中使用的鍵。
此範例使用匹配器,僅在 PreToolUse 事件觸發時對檔案修改工具執行 hook:
options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
HookMatcher(matcher='Write|Edit', hooks=[validate_file_path])
]
}
)每個 hook 回呼接收三個引數:
dict / HookInput):事件詳細資訊。請參閱輸入資料了解欄位str | None / string | null):關聯 PreToolUse 和 PostToolUse 事件HookContext):在 TypeScript 中,包含一個 signal 屬性(AbortSignal)用於取消。將此傳遞給 fetch() 等非同步操作,以便在 hook 逾時時自動取消。在 Python 中,此引數保留供未來使用。hook 回呼的第一個引數包含關於事件的資訊。欄位名稱在兩個 SDK 中相同(都使用 snake_case)。
通用欄位存在於所有 hook 類型中:
| 欄位 | 類型 | 描述 |
|---|---|---|
hook_event_name | string | Hook 類型(PreToolUse、PostToolUse 等) |
session_id | string | 目前工作階段識別碼 |
transcript_path | string | 對話記錄的路徑 |
cwd | string | 目前工作目錄 |
Hook 特定欄位因 hook 類型而異。標記 TS 的項目僅在 TypeScript SDK 中可用:
| 欄位 | 類型 | 描述 | Hooks |
|---|---|---|---|
tool_name | string | 被呼叫的工具名稱 | PreToolUse、PostToolUse、PostToolUseFailureTS、PermissionRequestTS |
tool_input | object | 傳遞給工具的引數 | PreToolUse、PostToolUse、PostToolUseFailureTS、PermissionRequestTS |
tool_response | any | 工具執行回傳的結果 | PostToolUse |
error | string | 工具執行失敗的錯誤訊息 | PostToolUseFailureTS |
is_interrupt | boolean | 失敗是否由中斷引起 | PostToolUseFailureTS |
prompt | string | 使用者的提示文字 | UserPromptSubmit |
stop_hook_active | boolean | 停止 hook 是否正在處理中 | Stop、SubagentStop |
agent_id | string | 子代理的唯一識別碼 | SubagentStartTS、SubagentStopTS |
agent_type | string | 子代理的類型/角色 | SubagentStartTS |
agent_transcript_path | string | 子代理對話記錄的路徑 | SubagentStopTS |
trigger | string | 觸發壓縮的原因:manual 或 auto | PreCompact |
custom_instructions | string | 為壓縮提供的自訂指示 | PreCompact |
permission_suggestions | array | 工具的建議權限更新 | PermissionRequestTS |
source | string | 工作階段如何啟動:startup、resume、clear 或 compact | SessionStartTS |
reason | string | 工作階段結束的原因:clear、logout、prompt_input_exit、bypass_permissions_disabled 或 other | SessionEndTS |
message | string | 來自代理的狀態訊息 | NotificationTS |
notification_type | string | 通知類型:permission_prompt、idle_prompt、auth_success 或 elicitation_dialog | NotificationTS |
title | string | 代理設定的可選標題 | NotificationTS |
以下程式碼定義了一個 hook 回呼,使用 tool_name 和 tool_input 來記錄每個工具呼叫的詳細資訊:
async def log_tool_calls(input_data, tool_use_id, context):
if input_data['hook_event_name'] == 'PreToolUse':
print(f"Tool: {input_data['tool_name']}")
print(f"Input: {input_data['tool_input']}")
return {}您的回呼函式回傳一個物件,告訴 SDK 如何繼續。回傳一個空物件 {} 以允許操作不做任何變更。要阻止、修改或為操作新增上下文,請回傳一個包含 hookSpecificOutput 欄位的物件,其中包含您的決定。
頂層欄位(hookSpecificOutput 之外):
| 欄位 | 類型 | 描述 |
|---|---|---|
continue | boolean | 代理是否應在此 hook 之後繼續(預設:true) |
stopReason | string | 當 continue 為 false 時顯示的訊息 |
suppressOutput | boolean | 從記錄中隱藏 stdout(預設:false) |
systemMessage | string | 注入對話中供 Claude 查看的訊息 |
hookSpecificOutput 內的欄位:
| 欄位 | 類型 | Hooks | 描述 |
|---|---|---|---|
hookEventName | string | 全部 | 必填。使用 input.hook_event_name 來匹配目前事件 |
permissionDecision | 'allow' | 'deny' | 'ask' | PreToolUse | 控制工具是否執行 |
permissionDecisionReason | string | PreToolUse | 向 Claude 顯示的決定說明 |
updatedInput | object | PreToolUse | 修改後的工具輸入(需要 permissionDecision: 'allow') |
additionalContext | string | PreToolUse、PostToolUse、UserPromptSubmit、SessionStartTS、SubagentStartTS | 新增到對話中的上下文 |
此範例阻止對 /etc 目錄的寫入操作,同時注入系統訊息提醒 Claude 關於安全檔案操作的做法:
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: inject guidance into the conversation
'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 {}當多個 hooks 或權限規則適用時,SDK 按以下順序評估它們:
如果任何 hook 回傳 deny,操作將被阻止——其他 hook 回傳 allow 不會覆蓋它。
回傳拒絕決定以防止工具執行:
async def block_dangerous_commands(input_data, tool_use_id, context):
if input_data['hook_event_name'] != 'PreToolUse':
return {}
command = input_data['tool_input'].get('command', '')
if 'rm -rf /' in command:
return {
'hookSpecificOutput': {
'hookEventName': input_data['hook_event_name'],
'permissionDecision': 'deny',
'permissionDecisionReason': 'Dangerous command blocked: rm -rf /'
}
}
return {}回傳更新後的輸入以變更工具接收的內容:
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。始終回傳一個新物件,而不是修改原始的 tool_input。
將上下文注入對話中:
async def add_security_reminder(input_data, tool_use_id, context):
return {
'systemMessage': 'Remember to follow security best practices.'
}繞過受信任工具的權限提示。當您希望某些操作無需使用者確認即可執行時,這很有用:
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', 'LS']
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 {}permissionDecision 欄位接受三個值:'allow'(自動核准)、'deny'(阻止)或 'ask'(提示確認)。
這些模式幫助您為複雜的使用案例建構更精密的 hook 系統。
Hooks 按照它們在陣列中出現的順序執行。讓每個 hook 專注於單一職責,並串連多個 hooks 以實現複雜邏輯。此範例對每個工具呼叫執行所有四個 hooks(未指定匹配器):
options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
HookMatcher(hooks=[rate_limiter]), # First: check rate limits
HookMatcher(hooks=[authorization_check]), # Second: verify permissions
HookMatcher(hooks=[input_sanitizer]), # Third: sanitize inputs
HookMatcher(hooks=[audit_logger]) # Last: log the action
]
}
)使用正規表達式模式來匹配多個工具:
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])
]
}
)匹配器只匹配工具名稱,不匹配檔案路徑或其他引數。要按檔案路徑篩選,請在 hook 回呼內部檢查 tool_input.file_path。
使用 SubagentStop hooks 來監控子代理完成。tool_use_id 有助於將父代理呼叫與其子代理關聯起來:
async def subagent_tracker(input_data, tool_use_id, context):
if input_data['hook_event_name'] == 'SubagentStop':
print(f"[SUBAGENT] Completed")
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 請求。透過捕獲例外而非拋出例外來優雅地處理錯誤。在 TypeScript 中,將 signal 傳遞給 fetch() 以便在 hook 逾時時取消請求:
import aiohttp
from datetime import datetime
async def webhook_notifier(input_data, tool_use_id, context):
if input_data['hook_event_name'] != 'PostToolUse':
return {}
try:
async with aiohttp.ClientSession() as session:
await session.post(
'https://api.example.com/webhook',
json={
'tool': input_data['tool_name'],
'timestamp': datetime.now().isoformat()
}
)
except Exception as e:
print(f'Webhook request failed: {e}')
return {}使用 Notification hooks 接收來自代理的狀態更新,並將其轉發到外部服務,如 Slack 或監控儀表板:
import { query, HookCallback, NotificationHookInput } from "@anthropic-ai/claude-agent-sdk";
const notificationHandler: HookCallback = async (input, toolUseID, { signal }) => {
const notification = input as NotificationHookInput;
await fetch('https://hooks.slack.com/services/YOUR/WEBHOOK/URL', {
method: 'POST',
body: JSON.stringify({
text: `Agent status: ${notification.message}`
}),
signal
});
return {};
};
for await (const message of query({
prompt: "Analyze this codebase",
options: {
hooks: {
Notification: [{ hooks: [notificationHandler] }]
}
}
})) {
console.log(message);
}本節涵蓋常見問題及其解決方法。
PreToolUse,而非 preToolUse)options.hooks 中正確的事件類型下SubagentStop、Stop、SessionStart、SessionEnd 和 Notification hooks,匹配器會被忽略。這些 hooks 會對該類型的所有事件觸發。max_turns 限制時,hooks 可能不會觸發,因為工作階段在 hooks 可以執行之前就結束了匹配器只匹配工具名稱,不匹配檔案路徑或其他引數。要按檔案路徑篩選,請在 hook 內部檢查 tool_input.file_path:
const myHook: HookCallback = async (input, toolUseID, { signal }) => {
const preInput = input as PreToolUseHookInput;
const filePath = preInput.tool_input?.file_path as string;
if (!filePath?.endsWith('.md')) return {}; // Skip non-markdown files
// Process markdown files...
};HookMatcher 設定中的 timeout 值AbortSignal 來優雅地處理取消PreToolUse hooks 是否有 permissionDecision: 'deny' 的回傳permissionDecisionReason確保 updatedInput 在 hookSpecificOutput 內部,而非頂層:
return {
hookSpecificOutput: {
hookEventName: input.hook_event_name,
permissionDecision: 'allow',
updatedInput: { command: 'new command' }
}
};您還必須回傳 permissionDecision: 'allow' 才能使輸入修改生效
在 hookSpecificOutput 中包含 hookEventName 以識別輸出對應的 hook 類型
SessionStart、SessionEnd 和 Notification hooks 僅在 TypeScript SDK 中可用。由於設定限制,Python SDK 不支援這些事件。
當產生多個子代理時,每個子代理可能會分別請求權限。子代理不會自動繼承父代理的權限。為避免重複提示,請使用 PreToolUse hooks 自動核准特定工具,或設定適用於子代理工作階段的權限規則。
產生子代理的 UserPromptSubmit hook 如果這些子代理觸發相同的 hook,可能會建立無限迴圈。為防止這種情況:
parent_tool_use_id 欄位來偵測您是否已在子代理上下文中systemMessage 欄位將上下文新增到模型可見的對話中,但它可能不會出現在所有 SDK 輸出模式中。如果您需要將 hook 決定呈現給您的應用程式,請單獨記錄它們或使用專用的輸出通道。
Was this page helpful?