ควบคุมพฤติกรรม Agent ด้วย Hooks
ดักจับและปรับแต่งพฤติกรรมของ agent ณ จุดสำคัญในการ execute ด้วย hooks
Hooks คือ callback functions ที่รัน code ของคุณในการตอบสนองต่อ agent events เช่น เมื่อ tool ถูกเรียก, session เริ่มต้น, หรือ execution หยุด ด้วย hooks คุณสามารถ:
- บล็อกการดำเนินการที่อันตราย ก่อนที่จะ execute เช่น shell commands ที่ทำลายข้อมูลหรือการเข้าถึงไฟล์ที่ไม่ได้รับอนุญาต
- Log และ audit ทุก tool call เพื่อ compliance, debugging, หรือ analytics
- แปลง inputs และ outputs เพื่อ sanitize ข้อมูล, inject credentials, หรือ redirect file paths
- ขอการอนุมัติจากมนุษย์ สำหรับการดำเนินการที่ sensitive เช่น การเขียนฐานข้อมูลหรือ API calls
- ติดตาม session lifecycle เพื่อจัดการ state, ทำความสะอาด resources, หรือส่ง notifications
วิธีการทำงานของ Hooks
-
Event ถูก fire - บางอย่างเกิดขึ้นระหว่าง agent execution และ SDK fire event: tool กำลังจะถูกเรียก (
PreToolUse), tool return ผลลัพธ์ (PostToolUse), subagent เริ่มหรือหยุด, agent ว่าง, หรือ execution จบลง -
SDK รวบรวม registered hooks - SDK ตรวจสอบ hooks ที่ลงทะเบียนสำหรับ event type นั้น
-
Matchers กรอง hooks ที่จะรัน - หาก hook มี
matcherpattern (เช่น"Write|Edit") SDK จะทดสอบกับ event target (เช่น ชื่อ tool) -
Callback functions execute - แต่ละ hook callback รับ input เกี่ยวกับสิ่งที่กำลังเกิดขึ้น: ชื่อ tool, arguments, session ID, และ details อื่นๆ ตาม event
-
Callback ของคุณ return การตัดสินใจ - หลังจากทำการดำเนินการ (logging, API calls, validation) callback ของคุณ return output object ที่บอก agent ว่าจะทำอะไร: อนุญาต, บล็อก, แก้ไข input, หรือ inject context เข้าในการสนทนา
ตัวอย่างต่อไปนี้นำขั้นตอนเหล่านี้มารวมกัน ลงทะเบียน PreToolUse hook ด้วย matcher "Write|Edit" เพื่อให้ callback fire เฉพาะสำหรับ file-writing tools:
import asyncio
from claude_agent_sdk import (
AssistantMessage,
ClaudeSDKClient,
ClaudeAgentOptions,
HookMatcher,
ResultMessage,
)
# กำหนด hook callback ที่รับ tool call details
async def protect_env_files(input_data, tool_use_id, context):
# ดึง file path จาก tool input arguments
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 empty object เพื่ออนุญาตการดำเนินการ
return {}
async def main():
options = ClaudeAgentOptions(
hooks={
# ลงทะเบียน hook สำหรับ PreToolUse events
# Matcher กรองเฉพาะ Write และ 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():
if isinstance(message, (AssistantMessage, ResultMessage)):
print(message)
asyncio.run(main())
import { query, HookCallback, PreToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";
const protectEnvFiles: 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;
const fileName = filePath?.split("/").pop();
if (fileName === ".env") {
return {
hookSpecificOutput: {
hookEventName: preInput.hook_event_name,
permissionDecision: "deny",
permissionDecisionReason: "Cannot modify .env files"
}
};
}
return {};
};
for await (const message of query({
prompt: "Update the database configuration",
options: {
hooks: {
PreToolUse: [{ matcher: "Write|Edit", hooks: [protectEnvFiles] }]
}
}
})) {
if (message.type === "assistant" || message.type === "result") {
console.log(message);
}
}
Hook Events ที่ใช้ได้
SDK มี hooks สำหรับ stages ต่างๆ ของ agent execution
| Hook Event | Python SDK | TypeScript SDK | สิ่งที่ trigger มัน | ตัวอย่างการใช้งาน |
|---|---|---|---|---|
PreToolUse | ใช่ | ใช่ | Tool call request (สามารถบล็อกหรือแก้ไขได้) | บล็อก shell commands ที่อันตราย |
PostToolUse | ใช่ | ใช่ | Tool execution result | Log การเปลี่ยนแปลงไฟล์ทั้งหมดใน audit trail |
PostToolUseFailure | ใช่ | ใช่ | Tool execution failure | จัดการหรือ log tool errors |
PostToolBatch | ไม่ | ใช่ | tool calls ทั้งหมดใน batch resolve ครั้งเดียวต่อ batch ก่อน model call ถัดไป | Inject conventions ครั้งเดียวสำหรับ batch ทั้งหมด |
UserPromptSubmit | ใช่ | ใช่ | การส่ง user prompt | Inject context เพิ่มเติมใน prompts |
MessageDisplay | ไม่ | ใช่ | assistant message ที่มีข้อความเสร็จสมบูรณ์ | Redact หรือ reformat ข้อความที่แสดง |
Stop | ใช่ | ใช่ | Agent execution stop | Save session state ก่อน exit |
SubagentStart | ใช่ | ใช่ | Subagent initialization | ติดตามการ spawn parallel task |
SubagentStop | ใช่ | ใช่ | Subagent completion | รวมผลลัพธ์จาก parallel tasks |
PreCompact | ใช่ | ใช่ | Conversation compaction request | Archive transcript ก่อน summarize |
PermissionRequest | ใช่ | ใช่ | Permission dialog จะถูกแสดง | Custom permission handling |
SessionStart | ไม่ | ใช่ | Session initialization | Initialize logging และ telemetry |
SessionEnd | ไม่ | ใช่ | Session termination | Clean up temporary resources |
Notification | ใช่ | ใช่ | Agent status messages | ส่ง agent status updates ไปยัง Slack |
Configure Hooks
ในการ configure hook ส่งมันใน field hooks ของ agent 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);
}
Matchers
ใช้ matchers เพื่อกรองว่า callbacks จะ fire เมื่อไหร่ field matcher จะ match กับค่าที่แตกต่างกันขึ้นอยู่กับ hook event type
| Option | Type | Default | คำอธิบาย |
|---|---|---|---|
matcher | string | undefined | Pattern ที่ match กับ filter field ของ event สำหรับ tool hooks นี่คือชื่อ tool |
hooks | HookCallback[] | - | Required Array ของ callback functions ที่ execute เมื่อ pattern match |
timeout | number | 60 | Timeout เป็นวินาที |
Callback Functions
Inputs
ทุก hook callback รับ arguments สามตัว:
- Input data: typed object ที่มี event details แต่ละ hook type มี input shape ของตัวเอง
- Tool use ID (
str | None/string | undefined): เชื่อมโยงPreToolUseและPostToolUseevents สำหรับ tool call เดียวกัน - Context: ใน TypeScript มี property
signal(AbortSignal) สำหรับการยกเลิก ใน Python argument นี้สงวนไว้สำหรับการใช้งานในอนาคต
Outputs
Callback ของคุณ return object ที่มีสองหมวดหมู่ของ fields:
- Top-level fields ทำงานเหมือนกันใน event ทุกอัน:
systemMessageแสดง message ให้ user, และcontinue(continue_ใน Python) กำหนดว่า agent ยังทำงานต่อหลังจาก hook นี้หรือไม่ hookSpecificOutputควบคุมการดำเนินการปัจจุบัน fields ภายในขึ้นอยู่กับ hook event type
Return {} เพื่ออนุญาตการดำเนินการโดยไม่มีการเปลี่ยนแปลง
ตัวอย่าง
แก้ไข tool input
ตัวอย่างนี้ดักจับ Write tool calls และเขียน argument file_path ใหม่เพื่อเพิ่ม /sandbox ข้างหน้า:
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 {}
เพิ่ม context และบล็อก tool
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": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "deny",
"permissionDecisionReason": "Writing to /etc is not allowed",
},
}
return {}
Auto-approve tools เฉพาะ
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 {}
ลงทะเบียน multiple hooks
เมื่อ event ถูก fire hooks ที่ match ทั้งหมดจะรัน parallel สำหรับการตัดสินใจ permission ผลลัพธ์ที่ restrictive ที่สุดชนะ
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
HookMatcher(hooks=[authorization_check]),
HookMatcher(hooks=[input_validator]),
HookMatcher(hooks=[audit_logger]),
]
}
)
ส่ง notifications ไปยัง Slack
import asyncio
import json
import urllib.request
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, HookMatcher
def _send_slack_notification(message):
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:
await asyncio.to_thread(_send_slack_notification, input_data.get("message", ""))
except Exception as e:
print(f"Failed to send notification: {e}")
return {}
async def main():
options = ClaudeAgentOptions(
hooks={
"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 ไม่ถูก fire
- ตรวจสอบว่าชื่อ hook event ถูกต้องและ case-sensitive (
PreToolUseไม่ใช่preToolUse) - ตรวจสอบว่า matcher pattern match กับชื่อ tool อย่างถูกต้อง
- ตรวจสอบว่า hook อยู่ภายใต้ event type ที่ถูกต้องใน
options.hooks
Tool ถูกบล็อกโดยไม่คาดคิด
- ตรวจสอบ
PreToolUsehooks ทั้งหมดสำหรับpermissionDecision: 'deny'returns - เพิ่ม logging ใน hooks เพื่อดูว่า
permissionDecisionReasonที่พวกมัน return คืออะไร
Modified input ไม่ถูกนำไปใช้
- ตรวจสอบว่า
updatedInputอยู่ภายในhookSpecificOutputไม่ใช่ที่ระดับบนสุด - Return
permissionDecision: 'allow'เพื่อ auto-approve modified input
เอกสารที่เกี่ยวข้อง
- Claude Code hooks reference: JSON input/output schemas ครบถ้วน, event documentation, และ matcher patterns
- TypeScript SDK reference: hook types, input/output definitions, และ configuration options
- Python SDK reference: hook types, input/output definitions, และ configuration options
- Permissions: ควบคุมสิ่งที่ agent ของคุณทำได้
- Custom tools: สร้าง tools เพื่อขยายขีดความสามารถของ agent