Skip to main content

ควบคุมพฤติกรรม 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

  1. Event ถูก fire - บางอย่างเกิดขึ้นระหว่าง agent execution และ SDK fire event: tool กำลังจะถูกเรียก (PreToolUse), tool return ผลลัพธ์ (PostToolUse), subagent เริ่มหรือหยุด, agent ว่าง, หรือ execution จบลง

  2. SDK รวบรวม registered hooks - SDK ตรวจสอบ hooks ที่ลงทะเบียนสำหรับ event type นั้น

  3. Matchers กรอง hooks ที่จะรัน - หาก hook มี matcher pattern (เช่น "Write|Edit") SDK จะทดสอบกับ event target (เช่น ชื่อ tool)

  4. Callback functions execute - แต่ละ hook callback รับ input เกี่ยวกับสิ่งที่กำลังเกิดขึ้น: ชื่อ tool, arguments, session ID, และ details อื่นๆ ตาม event

  5. 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 EventPython SDKTypeScript SDKสิ่งที่ trigger มันตัวอย่างการใช้งาน
PreToolUseใช่ใช่Tool call request (สามารถบล็อกหรือแก้ไขได้)บล็อก shell commands ที่อันตราย
PostToolUseใช่ใช่Tool execution resultLog การเปลี่ยนแปลงไฟล์ทั้งหมดใน audit trail
PostToolUseFailureใช่ใช่Tool execution failureจัดการหรือ log tool errors
PostToolBatchไม่ใช่tool calls ทั้งหมดใน batch resolve ครั้งเดียวต่อ batch ก่อน model call ถัดไปInject conventions ครั้งเดียวสำหรับ batch ทั้งหมด
UserPromptSubmitใช่ใช่การส่ง user promptInject context เพิ่มเติมใน prompts
MessageDisplayไม่ใช่assistant message ที่มีข้อความเสร็จสมบูรณ์Redact หรือ reformat ข้อความที่แสดง
Stopใช่ใช่Agent execution stopSave session state ก่อน exit
SubagentStartใช่ใช่Subagent initializationติดตามการ spawn parallel task
SubagentStopใช่ใช่Subagent completionรวมผลลัพธ์จาก parallel tasks
PreCompactใช่ใช่Conversation compaction requestArchive transcript ก่อน summarize
PermissionRequestใช่ใช่Permission dialog จะถูกแสดงCustom permission handling
SessionStartไม่ใช่Session initializationInitialize logging และ telemetry
SessionEndไม่ใช่Session terminationClean 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

OptionTypeDefaultคำอธิบาย
matcherstringundefinedPattern ที่ match กับ filter field ของ event สำหรับ tool hooks นี่คือชื่อ tool
hooksHookCallback[]-Required Array ของ callback functions ที่ execute เมื่อ pattern match
timeoutnumber60Timeout เป็นวินาที

Callback Functions

Inputs

ทุก hook callback รับ arguments สามตัว:

  • Input data: typed object ที่มี event details แต่ละ hook type มี input shape ของตัวเอง
  • Tool use ID (str | None / string | undefined): เชื่อมโยง PreToolUse และ PostToolUse events สำหรับ 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 ถูกบล็อกโดยไม่คาดคิด

  • ตรวจสอบ PreToolUse hooks ทั้งหมดสำหรับ 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