תיעוד 186
יירוט ושליטה בהתנהגות הסוכן באמצעות hooks
יירוט והתאמה אישית של התנהגות הסוכן בנקודות ביצוע מרכזיות באמצעות hooks
Hooks הן פונקציות callback שמריצות את הקוד שלך בתגובה לאירועי הסוכן, כמו קריאה לכלי, התחלת סשן, או עצירת ביצוע. באמצעות hooks אפשר:
- לחסום פעולות מסוכנות לפני שהן מתבצעות, כמו פקודות shell הרסניות או גישה לא מורשית לקבצים
- לתעד ולבקר קריאות לכלים לצורך תאימות, ניפוי שגיאות או אנליטיקה
- לשנות קלטים ופלטים כדי לנקות נתונים, להזריק אישורים, או להפנות נתיבי קבצים
- לדרוש אישור אנושי לפעולות רגישות כמו כתיבה למסד נתונים או קריאות API
- לעקוב אחר מחזור החיים של הסשן כדי לנהל מצב, לנקות משאבים, או לשלוח התראות
#איך hooks עובדים
- אירוע מופעל
משהו קורה במהלך ביצוע הסוכן וה-SDK מפעיל אירוע: כלי עומד להיקרא (PreToolUse), כלי החזיר תוצאה (PostToolUse), תת סוכן התחיל או נעצר, הסוכן במצב idle, או שהביצוע הסתיים. ראה את הרשימה המלאה של האירועים.
- ה-SDK אוסף hooks רשומים
ה-SDK בודק hooks שנרשמו לסוג האירוע הזה. זה כולל callback hooks שאתה מעביר ב-options.hooks ו-hooks של פקודות shell מקובצי הגדרות כאשר הרשומה המתאימה settingSources או setting_sources מופעלת, והיא מופעלת עבור אפשרויות ברירת המחדל של query().
- Matchers מסננים אילו hooks רצים
אם ל-hook יש תבנית matcher (כמו "Write|Edit"), ה-SDK בודק אותה מול היעד של האירוע (לדוגמה, שם הכלי). Hooks בלי matcher רצים עבור כל אירוע מהסוג הזה.
- פונקציות callback מתבצעות
פונקציית ה-callback של כל hook תואם מקבלת קלט על מה שקורה: שם הכלי, הארגומנטים שלו, מזהה הסשן, ופרטים נוספים שתלויים באירוע.
- ה-callback שלך מחזיר החלטה
אחרי ביצוע פעולות כלשהן (תיעוד, קריאות API, אימות), ה-callback מחזיר אובייקט פלט שאומר לסוכן מה לעשות: לאפשר את הפעולה, לחסום אותה, לשנות את הקלט, או להזריק הקשר לשיחה.
הדוגמה הזו רושמת hook מסוג PreToolUse (שלב 1) עם matcher של "Write|Edit" (שלב 3) כך שה-callback מופעל עבור הכלים Write ו-Edit. כשהוא מופעל, ה-callback מקבל את הקלט של הכלי (שלב 4), בודק אם נתיב הקובץ מכוון לקובץ .env, ומחזיר permissionDecision: "deny" כדי לחסום את הפעולה (שלב 5):
import asyncio
from claude_agent_sdk import (
AssistantMessage,
ClaudeSDKClient,
ClaudeAgentOptions,
HookMatcher,
ResultMessage,
)
# 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.lower() == ".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():
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])]
}
)
async with ClaudeSDKClient(options=options) as client:
await client.query("Create a .env file with the standard local development database configuration")
async for message in client.receive_response():
# Filter for assistant and result messages
if isinstance(message, (AssistantMessage, ResultMessage)):
print(message)
asyncio.run(main())import { query, HookCallback, PreToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";
// Define a hook callback with the HookCallback type
const protectEnvFiles: HookCallback = async (input, toolUseID, { signal }) => {
// Cast input to the specific hook type for type safety
const preInput = input as PreToolUseHookInput;
// Cast tool_input to access its properties (typed as unknown in the SDK)
const toolInput = preInput.tool_input as Record<string, unknown>;
const filePath = toolInput?.file_path as string;
const fileName = filePath?.split("/").pop();
// Block the operation if targeting a .env file
if (fileName?.toLowerCase() === ".env") {
return {
hookSpecificOutput: {
hookEventName: preInput.hook_event_name,
permissionDecision: "deny",
permissionDecisionReason: "Cannot modify .env files"
}
};
}
// Return empty object to allow the operation
return {};
};
for await (const message of query({
prompt: "Create a .env file with the standard local development database configuration",
options: {
hooks: {
// Register the hook for PreToolUse events
// The matcher filters to only Write and Edit tool calls
PreToolUse: [{ matcher: "Write|Edit", hooks: [protectEnvFiles] }]
}
}
})) {
// Filter for assistant and result messages
if (message.type === "assistant" || message.type === "result") {
console.log(message);
}
}ה-hook דוחה קריאת Write או Edit שה-file_path שלה מסתיים ב-/.env, בכל מצב אותיות. ב-Windows, file_path משתמש בלוכסנים הפוכים, כמו C:\project\.env, וה-hook הזה מאפשר קריאת Write או Edit אל C:\project\.env.
[!WARNING]
המתאם
Write|Editמגביל את ה-hook הזה לכלי Write ו-Edit. אם Claude כותב.envבאמצעות פקודת Bash, ה-hook לא רץ. כדי לחסום כתיבות Bash לקובץ במקום שבו ארגז החול רץ, ראה את הגדרת ארגז החול עבור Python או TypeScript.
#Hooks זמינים
ה-SDK מספק hooks לשלבים שונים של ביצוע הסוכן. חלק מה-hooks זמינים בשני ה-SDK, ואחרים זמינים רק ב-TypeScript.
| אירוע Hook | Python SDK | TypeScript SDK | מה מפעיל אותו | דוגמה לשימוש |
|---|---|---|---|---|
PreToolUse | כן | כן | בקשת קריאה לכלי (אפשר לחסום או לשנות) | חסימת פקודות shell מסוכנות |
PostToolUse | כן | כן | תוצאת ביצוע של כלי | תיעוד שינויי קבצים ביומן ביקורת |
PostToolUseFailure | כן | כן | כשל בביצוע כלי | טיפול בשגיאות כלי או תיעוד שלהן |
PostToolBatch | לא | כן | אצווה מלאה של קריאות לכלים מסתיימת, פעם אחת לכל אצווה לפני קריאת המודל הבאה | הזרקת מוסכמות פעם אחת עבור כל האצווה |
UserPromptSubmit | כן | כן | פרומפט נשלח, כולל תור ש-Claude Code מתחיל בעצמו | הזרקת הקשר נוסף לפרומפטים |
UserPromptExpansion | לא | כן | פקודה שהמשתמש הקליד, או פרומפט MCP, מתרחבת לפרומפט לפני שהיא מגיעה ל-Claude. לא מופעל כש-Claude מפעיל skill בעצמו | חסימת פקודה מהפעלה ישירה או הוספת הקשר כש-skill מוקלד |
MessageDisplay | לא | כן | הודעת assistant עם טקסט מסתיימת, פעם אחת לכל הודעה עם הטקסט המלא של ההודעה | השחרה או עיצוב מחדש של הטקסט המוצג בלי לשנות את התמליל |
Stop | כן | כן | עצירת ביצוע הסוכן | שמירת מצב הסשן לפני יציאה |
StopFailure | לא | כן | התור מסתיים בשגיאת API במקום עצירה רגילה | תיעוד כשלים או שליחת התראות |
SubagentStart | כן | כן | אתחול תת סוכן | מעקב אחרי יצירת משימות במקביל |
SubagentStop | כן | כן | השלמת תת סוכן | צבירת תוצאות ממשימות מקבילות |
PreCompact | כן | כן | בקשת דחיסת שיחה | ארכוב התמליל המלא לפני סיכום |
PostCompact | לא | כן | דחיסת השיחה מסתיימת | תיעוד הסיכום שנוצר |
PreModelSwitch | לא | כן | החלפת מודל שהתבקשה, לפני שהיא מתרחשת (אפשר לחסום) | חסימת מעבר למודל מסוים |
PostModelSwitch | לא | כן | המודל של הסשן משתנה, כולל fallback אוטומטי | מתן הנחיה ל-Claude שמותאמת למודל החדש |
PermissionRequest | כן | כן | קריאה לכלי זקוקה להחלטת הרשאה | טיפול מותאם בהרשאות |
PermissionDenied | לא | כן | מצב Auto דוחה קריאה לכלי, כולל דחיות בלי פסק דין של מסווג | תיעוד דחיות, או אמירה למודל שהוא רשאי לנסות שוב. Claude Code מתעלם מ-retry: true בדחיות בלי פסק דין. ראה PermissionDenied |
SessionStart | לא | כן | אתחול סשן | אתחול תיעוד וטלמטריה |
SessionEnd | לא | כן | סיום סשן | ניקוי משאבים זמניים |
Notification | כן | כן | הודעות סטטוס של הסוכן | שליחת עדכוני סטטוס של הסוכן ל-Slack או ל-PagerDuty |
Setup | לא | כן | הגדרה או תחזוקה של סשן | הרצת משימות אתחול |
TeammateIdle | לא | כן | חבר צוות עובר למצב idle | הקצאה מחדש של עבודה או שליחת התראה |
TaskCreated | לא | כן | משימה נוצרת באמצעות הכלי TaskCreate | אכיפת מוסכמות שמות למשימות |
TaskCompleted | לא | כן | משימה מסומנת כהושלמה | דרישה שבדיקות יעברו לפני שמשימה נסגרת |
Elicitation | לא | כן | שרת MCP מבקש קלט משתמש באמצע משימה | מענה פרוגרמטי לבקשות קלט של MCP |
ElicitationResult | לא | כן | משתמש מגיב ל-elicitation של MCP | שינוי או חסימה של התשובה לפני שהיא חוזרת לשרת |
ConfigChange | לא | כן | שינויים בקובץ תצורה | טעינה מחדש של הגדרות באופן דינמי |
InstructionsLoaded | לא | כן | קובץ CLAUDE.md או קובץ כללים נטען להקשר | ביקורת על אילו קובצי הוראות נטענים |
WorktreeCreate | לא | כן | נוצר worktree של Git | מעקב אחרי סביבות עבודה מבודדות |
WorktreeRemove | לא | כן | worktree שנוצר על ידי hook מסוג WorktreeCreate מוסר | ניקוי משאבי סביבת העבודה |
CwdChanged | לא | כן | ספריית העבודה משתנה במהלך סשן | טעינה מחדש של משתני סביבה לפי ספרייה |
FileChanged | לא | כן | קובץ במעקב משתנה, נוצר, או נמחק | טעינה מחדש של תצורה כשקובצי הפרויקט משתנים |
DirectoryAdded | לא | כן | ספריית עבודה מתווספת במהלך סשן | התקנת תלויות עבור מאגר שנוסף באמצע הסשן |
#הגדרת hooks
כדי להגדיר hook, מעבירים אותו בשדה hooks של אפשרויות הסוכן (ClaudeAgentOptions ב Python, אובייקט options ב TypeScript). קטע הקוד הזה מניח שכבר הגדרתם callback של hook, כמו protect_env_files ב Python או protectEnvFiles ב TypeScript מהדוגמה למעלה:
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);
}אפשרות hooks היא מילון ב Python או אובייקט ב TypeScript, ובו:
- מפתחות: שמות אירועי hook כמו
'PreToolUse','PostToolUse', וגם'Stop' - ערכים: מערכים של matchers, וכל אחד מהם מכיל תבנית סינון אופציונלית ואת פונקציות ה callback שלכם
#Matchers
משתמשים ב matchers כדי לסנן מתי ה callbacks שלכם מופעלים. השדה matcher מותאם לערך שונה לפי סוג אירוע ה hook. לדוגמה, hooks מבוססי כלי מותאמים לשם הכלי, ו hooks מסוג Notification מותאמים לסוג ההתראה.
matchers של ה SDK פועלים לפי אותם כללים כמו matchers בקבצי הגדרות. הסעיף הזה מתעד את מסלולי ההערכה של מחרוזת מדויקת ושל ביטוי רגולרי, את דרישות הגרסה שלהם, ואת ערכי ה matcher לכל סוג אירוע.
| אפשרות | סוג | ברירת מחדל | תיאור |
|---|---|---|---|
matcher | string | undefined | תבנית שמותאמת לשדה הסינון של האירוע, לפי הכללים ל matchers בקבצי הגדרות. עבור hooks של כלים, השדה הזה הוא שם הכלי. כלים מובנים כוללים את Bash, Read, Write, Edit, Glob, Grep, WebFetch, Agent, ואחרים. ראו Tool Input Types לרשימה המלאה. כלי MCP משתמשים בתבנית mcp__<server>__<action>, שבה <server> הוא המפתח שבו משתמשים בהגדרת mcpServers, באיות שמתאר tool naming convention. |
hooks | HookCallback[] | - | חובה. מערך של פונקציות callback להרצה כשהתבנית מתאימה |
timeout | number | undefined | זמן קצוב בשניות. כשמשמיטים אותו, Claude Code מחיל את זמן הקצוב ברירת המחדל של האירוע. ה callbacks של ה SDK שלכם פועלים לפי ברירות המחדל של hook מסוג command |
onFailure | "continue" | "block" | undefined | מה עושה callback שנכשל. הגדירו "block" כדי לחסום את הפעולה כש callback נכשל. ב Python משתמשים ב on_failure |
השתמשו בתבנית matcher כדי לכוון לכלים מסוימים בכל פעם שאפשר. matcher עם 'Bash' רץ רק לפקודות Bash, והשמטת התבנית מריצה את ה callbacks שלכם בכל מופע של האירוע.
#פונקציות callback
#קלטים
כל callback של hook מקבל שלושה ארגומנטים:
- נתוני קלט: אובייקט מטיפוס שמכיל פרטי אירוע. לכל סוג hook יש צורת קלט משלו. לדוגמה,
PreToolUseHookInputכולל אתtool_nameואתtool_input, וNotificationHookInputכולל אתmessage. ראו את הגדרות הטיפוסים המלאות בהפניות ה SDK של TypeScript ושל Python.- כל קלטי ה hook חולקים את
session_id,cwd, וגםhook_event_name. agent_idוagent_typeמתמלאים כשה hook מופעל בתוך subagent. ב TypeScript, אלה נמצאים על קלט ה hook הבסיסי וזמינים לכל סוגי ה hook. ב Python, אלה שדות אופציונליים בPreToolUse,PostToolUse,PostToolUseFailure, וPermissionRequest, ושדות חובה בSubagentStartוSubagentStop.
- כל קלטי ה hook חולקים את
- מזהה שימוש בכלי (
str | None/string | undefined): מקשר אירועיPreToolUseוPostToolUseלאותה קריאת כלי. - הקשר: ב TypeScript, מכיל מאפיין
signal(AbortSignal) לביטול. ב Python, הארגומנט הזה שמור לשימוש עתידי.
#פלטים
ה callback שלכם מחזיר אובייקט עם שתי קטגוריות של שדות:
- שדות ברמה העליונה מתקבלים בכל אירוע:
systemMessageמציג הודעה למשתמש, וcontinue(continue_ב Python) קובע אם הסוכן ממשיך לרוץ אחרי ה hook הזה. חלק מהאירועים משליכים אותם או מעבירים אותם למקום אחר. הסעיף של כל אירוע בדף ה hooks אומר לאן הם מגיעים. hookSpecificOutputשולט בפעולה הנוכחית. השדות שמגדירים בתוכו תלויים בסוג אירוע ה hook:- עבור hooks מסוג
PreToolUse, כאן מגדירים אתpermissionDecision("allow","deny","ask", או"defer"), אתpermissionDecisionReason, ואתupdatedInput. אם מחזירים"defer", התור מסתיים בהודעת תוצאה שבהstop_reasonהוא"tool_deferred", כך שאפשר לחדש את הקריאה מאוחר יותר. - עבור hooks מסוג
PostToolUse, אפשר להגדירadditionalContextכדי לצרף מידע לתוצאת הכלי. כדי להחליף את הפלט של הכלי לפני ש Claude רואה אותו, מגדיריםupdatedToolOutput, שעובד לכל כלי בשני ה SDK. השדה הישן יותרupdatedMCPToolOutputמחליף פלט של כלי MCP בלבד. - ב SDK של TypeScript, callback של
PostToolUseיכול גם להחזירclassifierContext, הערה קצרה על תוצאת קריאת הכלי עבור מסווג ההרשאות של auto mode. מכיוון שה callback שלכם רץ בתהליך של היישום עצמו, המסווג עשוי לשקול אמירת משתמש שאתם מעבירים בהערה ככוונת משתמש. השדה דורש TypeScript Agent SDK מגרסה v0.3.236 ואילך. Annotate a result for the auto mode classifier מכסה את תקרת האורך, את הכלל של סינכרוני בלבד, ומה לא לשים בהערה.
- עבור hooks מסוג
מחזירים {} כדי לאפשר את הפעולה בלי שינויים. hooks של callback ב SDK משתמשים באותו פורמט פלט JSON כמו hooks של פקודת מעטפת ב Claude Code, שמתעד כל שדה ואפשרות ספציפית לאירוע. להגדרות הטיפוסים של ה SDK, ראו את הפניות ה SDK של TypeScript ושל Python.
[!NOTE]
כשמספר hooks מסוג
PreToolUseמחזירים החלטות שונות, לdenyיש עדיפות עלdefer, לdeferיש עדיפות עלask, ולaskיש עדיפות עלallow.
#פלט אסינכרוני
כברירת מחדל, הסוכן ממתין שה hook שלכם יחזור לפני שהוא ממשיך. אם ה hook מבצע תופעת לוואי, כמו תיעוד ביומן או שליחת webhook, ולא צריך להשפיע על התנהגות הסוכן, אפשר להחזיר פלט אסינכרוני. זה אומר לסוכן להמשיך מיד בלי להמתין שה hook יסתיים. בקטע הזה, send_to_logging_service ב Python ו sendToLoggingService ב TypeScript מייצגים כל פונקציית תיעוד שאתם מגדירים:
async def async_hook(input_data, tool_use_id, context):
# Start a background task, then return immediately
asyncio.create_task(send_to_logging_service(input_data))
return {"async_": True, "asyncTimeout": 30000}const asyncHook: HookCallback = async (input, toolUseID, { signal }) => {
// Start a background task, then return immediately
sendToLoggingService(input).catch(console.error);
return { async: true, asyncTimeout: 30000 };
};| שדה | סוג | תיאור |
|---|---|---|
async | true | מסמן מצב אסינכרוני. הסוכן ממשיך בלי להמתין. ב Python משתמשים ב async_ כדי להימנע ממילת המפתח השמורה. |
asyncTimeout | number | זמן קצוב אופציונלי, במילישניות, לפעולת הרקע |
[!NOTE]
פלטים אסינכרוניים לא יכולים לחסום, לשנות, או להזריק הקשר לפעולה, כי הסוכן כבר המשיך הלאה. השתמשו בהם רק לתופעות לוואי כמו תיעוד ביומן, מדדים, או התראות.
#חסימת הפעולה כש callback נכשל
ברוב האירועים, אם callback זורק שגיאה או מחזיר פלט שנכשל באימות, הפעולה ממשיכה והיישום שלכם מקבל אזהרת SDKInformationalMessage שבה tag הוא hook_callback_failed. guard של PreToolUse שקורס מאפשר אז לקריאת הכלי להגיע לזרימת ההרשאות הרגילה. callback שלא מחזיר כלום, כמו נתיב בלי return, נחשב גם הוא לכישלון. ב Python, קוראים את tag מ SystemMessage.data.
כדי לחסום את הפעולה, מגדירים onFailure: "block" ב TypeScript או on_failure="block" ב Python על ה matcher. נדרש TypeScript Agent SDK מגרסה v0.3.317 ואילך, או Python Agent SDK מגרסה v0.2.177 ואילך. הדוגמה הזו חוסמת קריאת Bash כש check_policy ב Python או checkPolicy ב TypeScript נכשל:
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
HookMatcher(matcher="Bash", hooks=[check_policy], on_failure="block")
]
}
)import type { Options } from "@anthropic-ai/claude-agent-sdk";
const options: Options = {
hooks: {
PreToolUse: [{ matcher: "Bash", hooks: [checkPolicy], onFailure: "block" }]
}
};כדי לבדוק את זה, גורמים ל callback להעלות שגיאה ומבקשים מ Claude להריץ פקודת Bash. תוצאת הכלי כוללת את failed; blocking because onFailure is "block", ו Claude לא רואה את טקסט השגיאה שלכם.
מחזירים {} כשל callback אין עמדה. כש "block" מוגדר, נתיב בלי return חוסם את הפעולה. callback שתופס את השגיאה שלו ומחזיר {} לא נכשל, לכן תנו לשגיאה להתפשט מתוך guard.
#אירועים שמתנהגים אחרת
האירועים האלה לא תמיד חוסמים כש "block" מוגדר:
Stop,SubagentStop,TaskCompleted, וTeammateIdle: האירועים האלה מתעלמים מהאפשרות, ולכן הפעולה ממשיכה.PermissionRequest: החסימה נחשבת רק אם היא מגיעה לפני שה callback שלcanUseToolעונה.PostToolUse: הכלי כבר רץ, ולכן Claude מקבל את הכישלון כמשוב.
callback שחורג מזמן הקצוב שלו פועל לפי Hook timeout. כש "block" מוגדר, זמן קצוב חוסם גם את PreCompact, Elicitation, ו WorktreeCreate.
#שגיאות הפעלה עבור onFailure
כש "block" מוגדר, השאילתה נכשלת בהפעלה ולא שולחת prompt כשהיא לא יכולה לכבד את האפשרות:
- Claude Code לא תומך באפשרות: השגיאה כוללת את
does not honor. עדכנו את Claude Code, או הסירו את האפשרות. - הסשן כבר רץ ולא רשם את ה hooks שלכם: השגיאה כוללת את
did not register your hooks. התחילו סשן חדש.
#דוגמאות
כמה דוגמאות בסעיף הזה מציגות רק את פונקציית ה-callback. כדי להריץ אחת, רשום את ה-callback תחת האירוע המתאים בשדה hooks של האפשרויות שלך, כמו שמוצג בהגדרת hooks.
#שינוי קלט של כלי
הדוגמה הזו מיירטת קריאות לכלי Write וכותבת מחדש את הארגומנט file_path כך שיתווסף בתחילתו /sandbox. ה-callback מחזיר updatedInput עם הנתיב ששונה ו-permissionDecision: '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 {}const redirectToSandbox: HookCallback = async (input, toolUseID, { signal }) => {
if (input.hook_event_name !== "PreToolUse") return {};
const preInput = input as PreToolUseHookInput;
const toolInput = preInput.tool_input as Record<string, unknown>;
if (preInput.tool_name === "Write") {
const originalPath = toolInput.file_path as string;
return {
hookSpecificOutput: {
hookEventName: preInput.hook_event_name,
permissionDecision: "allow",
updatedInput: {
...toolInput,
file_path: `/sandbox${originalPath}`
}
}
};
}
return {};
};[!NOTE]
צמד את
updatedInputעםpermissionDecision: 'allow'כדי לאשר אוטומטית את הקלט ששונה, או עםpermissionDecision: 'ask'כדי להציג אותו למשתמש. אם משמיטים אתpermissionDecision, הקלט ששונה עדיין חל ועובר דרך הערכת ההרשאות הרגילה. עם'defer', מתעלמים מ-updatedInput. תמיד החזר אובייקט חדש במקום לשנות אתtool_inputהמקורי.
כדי לאמת את ההפניה, הגדר את הקידומת לנתיב שאפשר לכתוב אליו, למשל ./sandbox או /tmp/sandbox (ב-macOS אי אפשר ליצור ספריית /sandbox ברמת השורש), ואז בקש מהסוכן לכתוב קובץ: תוצאת כלי ה-Write בזרם ההודעות מציינת את הנתיב עם קידומת ה-sandbox שלך ולא את זה ש-Claude ביקש.
#הוספת הקשר וחסימת כלי
הדוגמה הזו דוחה קריאות לכלי שה-file_path שלהן מתחיל ב-/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 {
# Top-level field: message shown to the user
"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 {}const blockEtcWrites: 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?.startsWith("/etc")) {
return {
// Top-level field: message shown to the user
systemMessage: "Remember: system directories like /etc are protected.",
// hookSpecificOutput: block the operation
hookSpecificOutput: {
hookEventName: preInput.hook_event_name,
permissionDecision: "deny",
permissionDecisionReason: "Writing to /etc is not allowed"
}
};
}
return {};
};כדי לאמת את החסימה, רשום את ה-callback תחת PreToolUse עם matcher של Write|Edit ובקש מהסוכן ליצור קובץ תחת /etc: תוצאת כלי ה-Write בזרם ההודעות מכילה Writing to /etc is not allowed, ולא נוצר קובץ.
#אישור אוטומטי של כלים מסוימים
כברירת מחדל, הסוכן עשוי לבקש הרשאה לפני שימוש בכלים מסוימים. הדוגמה הזו מאשרת אוטומטית כלי מערכת קבצים לקריאה בלבד (Read, Glob, Grep) על ידי החזרת permissionDecision: 'allow', ומאפשרת להם לרוץ בלי אישור משתמש, מלבד קריאות מנתיבי רשת, בזמן שכל שאר הכלים נשארים כפופים לבדיקות הרשאה רגילות:
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 {}const autoApproveReadOnly: HookCallback = async (input, toolUseID, { signal }) => {
if (input.hook_event_name !== "PreToolUse") return {};
const preInput = input as PreToolUseHookInput;
const readOnlyTools = ["Read", "Glob", "Grep"];
if (readOnlyTools.includes(preInput.tool_name)) {
return {
hookSpecificOutput: {
hookEventName: preInput.hook_event_name,
permissionDecision: "allow",
permissionDecisionReason: "Read-only tool auto-approved"
}
};
}
return {};
};#רישום כמה hooks
כשאירוע מופעל, ה-hooks התואמים רצים במקביל. בהחלטות הרשאה, התוצאה המגבילה ביותר חלה. deny של hook אחד חוסם את קריאת הכלי. מכיוון שסדר ההשלמה אינו דטרמיניסטי, כתוב כל hook כך שיפעל באופן עצמאי ולא יסתמך על כך ש-hook אחר כבר רץ קודם.
הדוגמה למטה רושמת שלוש בדיקות עצמאיות, כל אחת עם matcher שהושמט. שמות ה-hooks שבה, למשל audit_logger ב-Python או auditLogger ב-TypeScript, מייצגים callbacks שאתה מגדיר:
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
HookMatcher(hooks=[authorization_check]),
HookMatcher(hooks=[input_validator]),
HookMatcher(hooks=[audit_logger]),
]
}
)const options = {
hooks: {
PreToolUse: [
{ hooks: [authorizationCheck] },
{ hooks: [inputValidator] },
{ hooks: [auditLogger] }
]
}
};#סינון עם matchers לכמה כלים
השתמש ב-matchers לכמה כלים כדי לשתף callback אחד בין כלים קשורים. הדוגמה הזו רושמת שלושה matchers עם היקפים שונים, וכל hook שהיא מציינת מייצג callback שאתה מגדיר:
- רשימה מדויקת מופרדת בקו אנכי (
Write|Edit|NotebookEdit) מפעילה אתfile_security_hookעבור הכלים Write, Edit ו-NotebookEdit. - ביטוי רגולרי (
^mcp__) מפעיל אתmcp_audit_hookעבור כל כלי MCP שהשם שלו מתחיל ב-mcp__. - matcher שהושמט מפעיל את
global_loggerבלי סינון לפי שם כלי.
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
# Match file modification tools
HookMatcher(matcher="Write|Edit|NotebookEdit", hooks=[file_security_hook]),
# Match all MCP tools
HookMatcher(matcher="^mcp__", hooks=[mcp_audit_hook]),
# Match everything (no matcher)
HookMatcher(hooks=[global_logger]),
]
}
)const options = {
hooks: {
PreToolUse: [
// Match file modification tools
{ matcher: "Write|Edit|NotebookEdit", hooks: [fileSecurityHook] },
// Match all MCP tools
{ matcher: "^mcp__", hooks: [mcpAuditHook] },
// Match everything (no matcher)
{ hooks: [globalLogger] }
]
}
};#מעקב אחרי פעילות של subagent
השתמש ב-hooks של SubagentStop כדי לעקוב מתי subagents מסיימים את העבודה שלהם. ראה את טיפוס הקלט המלא בעמודי העזר של ה-SDK ב-TypeScript וב-Python. הדוגמה הזו רושמת סיכום בכל פעם ש-subagent מסתיים:
async def subagent_tracker(input_data, tool_use_id, context):
# Log subagent details when it finishes
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])]}
)import { HookCallback, SubagentStopHookInput } from "@anthropic-ai/claude-agent-sdk";
const subagentTracker: HookCallback = async (input, toolUseID, { signal }) => {
// Cast to SubagentStopHookInput to access subagent-specific fields
const subInput = input as SubagentStopHookInput;
// Log subagent details when it finishes
console.log(`[SUBAGENT] Completed: ${subInput.agent_id}`);
console.log(` Transcript: ${subInput.agent_transcript_path}`);
console.log(` Tool use ID: ${toolUseID}`);
console.log(` Stop hook active: ${subInput.stop_hook_active}`);
return {};
};
const options = {
hooks: {
SubagentStop: [{ hooks: [subagentTracker] }]
}
};כדי לאמת שה-hook מופעל, רשום את ה-callback ובקש מהסוכן להאציל משימה קטנה ל-subagent, למשל לרשום את הקבצים בתיקייה הנוכחית: כשה-subagent מסיים, ה-callback מדפיס את השורות [SUBAGENT] Completed: עם ה-ID של ה-subagent ונתיב התמליל.
#שליחת בקשות HTTP מתוך hooks
Hooks יכולים לבצע פעולות אסינכרוניות כמו בקשות HTTP. תפוס שגיאות בתוך ה-hook שלך במקום לתת להן להתפשט.
הדוגמה הזו שולחת webhook אחרי שכל כלי מסתיים, ורושמת איזה כלי רץ ומתי. ה-hook תופס שגיאות מ-webhook שנכשל:
import asyncio
import json
import urllib.request
from datetime import datetime
def _send_webhook(tool_name):
"""Synchronous helper that POSTs tool usage data to an external 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):
# Only fire after a tool completes (PostToolUse), not before
if input_data["hook_event_name"] != "PostToolUse":
return {}
try:
# Run the blocking HTTP call in a thread to avoid blocking the event loop
await asyncio.to_thread(_send_webhook, input_data["tool_name"])
except Exception as e:
# Log the error but don't raise
print(f"Webhook request failed: {e}")
return {}import { query, HookCallback, PostToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";
const webhookNotifier: HookCallback = async (input, toolUseID, { signal }) => {
// Only fire after a tool completes (PostToolUse), not before
if (input.hook_event_name !== "PostToolUse") return {};
try {
await fetch("https://api.example.com/webhook", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
tool: (input as PostToolUseHookInput).tool_name,
timestamp: new Date().toISOString()
}),
// Pass signal so the request cancels if the hook times out
signal
});
} catch (error) {
// Handle cancellation separately from other errors
if (error instanceof Error && error.name === "AbortError") {
console.log("Webhook request cancelled");
}
// Don't re-throw
}
return {};
};
// Register as a PostToolUse hook
for await (const message of query({
prompt: "Refactor the auth module",
options: {
hooks: {
PostToolUse: [{ hooks: [webhookNotifier] }]
}
}
})) {
console.log(message);
}כדי לאמת שה-hook מופעל, כוון את כתובת ה-webhook לנקודת קצה שאפשר לצפות בה ושלח prompt שמשתמש בכלי: ה-hook שולח POST עם שם הכלי וחותמת זמן אחרי שכל כלי מסתיים.
#העברת התראות ל-Slack
השתמש ב-hooks של Notification כדי לקבל התראות מערכת מהסוכן ולהעביר אותן לשירותים חיצוניים. בסשנים של ה-SDK, Claude Code מריץ את ה-hook הזה עבור סוגי ההתראות הבאים:
permission_promptאחרי שבקשת הרשאה המתינה כשש שניות ב-callback שלcanUseTool. דורש TypeScript Agent SDK בגרסה v0.3.233 או חדשה יותר, או Python Agent SDK בגרסה v0.2.139 או חדשה יותרelicitation_completeו-elicitation_responseעבור תהליכי elicitation של בקשות משתמשplugin_notificationכש-mod קורא ל-$.ui.notify
Claude Code פולט את הסוגים האחרים, כמו idle_prompt, auth_success ו-elicitation_dialog, מממשק משתמש אינטראקטיבי שסשנים של ה-SDK לא מריצים.
כל התראה כוללת שדה message עם תיאור קריא לאדם, ולפעמים גם title.
הדוגמה הזו מעבירה כל התראה לערוץ Slack. היא דורשת כתובת incoming webhook של Slack, שיוצרים על ידי הוספת אפליקציה ל-workspace של Slack והפעלת incoming webhooks:
import asyncio
import json
import urllib.request
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, HookMatcher
def _send_slack_notification(message):
"""Synchronous helper that sends a message to Slack via incoming webhook."""
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:
# Run the blocking HTTP call in a thread to avoid blocking the event loop
await asyncio.to_thread(_send_slack_notification, input_data.get("message", ""))
except Exception as e:
print(f"Failed to send notification: {e}")
# Return empty object. Notification hooks don't modify agent behavior
return {}
async def main():
options = ClaudeAgentOptions(
hooks={
# Register the hook for Notification events (no matcher needed)
"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())import { query, HookCallback, NotificationHookInput } from "@anthropic-ai/claude-agent-sdk";
// Define a hook callback that sends notifications to Slack
const notificationHandler: HookCallback = async (input, toolUseID, { signal }) => {
// Cast to NotificationHookInput to access the message field
const notification = input as NotificationHookInput;
try {
// POST the notification message to a Slack incoming webhook
await fetch("https://hooks.slack.com/services/YOUR/WEBHOOK/URL", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `Agent status: ${notification.message}`
}),
// Pass signal so the request cancels if the hook times out
signal
});
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
console.log("Notification cancelled");
} else {
console.error("Failed to send notification:", error);
}
}
// Return empty object. Notification hooks don't modify agent behavior
return {};
};
// Register the hook for Notification events (no matcher needed)
for await (const message of query({
prompt: "Analyze this codebase",
options: {
hooks: {
Notification: [{ hooks: [notificationHandler] }]
}
}
})) {
console.log(message);
}כשאירוע Notification מופעל, ה-hook מפרסם את ה-message של ההתראה, עם הקידומת Agent status:, לערוץ שה-webhook שלך מכוון אליו.
#תיקון בעיות נפוצות
#Hook לא מופעל
- ודאו ששם אירוע ה-hook נכון ורגיש לאותיות גדולות וקטנות (
PreToolUse, לאpreToolUse) - בדקו שתבנית ה-matcher תואמת את שם הכלי בדיוק
- ודאו שה-hook נמצא תחת סוג האירוע הנכון ב-
options.hooks - עבור hooks שאינם כלים ותומכים ב-matchers, כמו
Notificationו-SubagentStop, matchers מותאמים מול שדות שונים, ו-Stopמתעלם מ-matchers לחלוטין (ראו תבניות matcher) - Hooks עשויים לא לפעול כשהסוכן מגיע למגבלת
max_turns, כי הסשן מסתיים לפני ש-hooks יכולים לרוץ
#Matcher לא מסנן כמצופה
Matchers מתאימים רק לשמות כלים, לא לנתיבי קבצים או לארגומנטים אחרים. כדי לסנן לפי נתיב קובץ, בדקו את tool_input.file_path בתוך ה-hook:
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 {}; // Skip non-markdown files
// Process markdown files...
return {};
};#פסק זמן של hook
Claude Code מריץ כל callback עם פסק זמן, שמגדירים בשניות בשדה timeout על ה-HookMatcher שלו. כשלא מגדירים פסק זמן, Claude Code משתמש בברירת המחדל של האירוע: 600 שניות לרוב האירועים, 30 שניות ל-UserPromptSubmit, ל-PreModelSwitch ול-PostModelSwitch, ו-10 שניות ל-MessageDisplay. Claude Code מריץ callbacks של SessionEnd במהלך הכיבוי תחת תקציב פסק הזמן של SessionEnd הקצר יותר, 1.5 שניות כברירת מחדל.
כש-callback חורג מפסק הזמן שלו, Claude Code מבטל אותו ומשליך את הפלט שלו, והסשן ממשיך בלי להיתקע. מה שקורה בהמשך תלוי באירוע:
PreToolUse: Claude Code חוסם את קריאת הכלי. אם כל hook אחר החזיר כלום או רק החלטתallow, תוצאת הכלי אומרת ל-Claude שה-hook לא הגיב לפני פסק הזמן, והתור ממשיך. אם hook אחר שלכם כבר דחה את הקריאה, הדחייה עדיין מגיעה ל-Claude: תוצאת הכלי כוללת את הסיבה של אותו hook, וטקסט פסק הזמן יכול לבוא אחריה.לפני v2.1.210, Claude Code דיווח על פסק הזמן ל-Claude כדחייה של המשתמש, וזה גרם לסשנים ללא השגחה לעצור ולהמתין לקלט.
PostToolUseו-PostToolUseFailure: Claude Code שומר את תוצאת הכלי והתור ממשיך.UserPromptSubmitו-UserPromptExpansion: Claude Code חוסם את הפרומפט עם הודעה שמציינת את ה-hook ואת פסק הזמן, והסשן ממשיך. לפני v2.1.208, Claude Code סיים את השאילתה עםerror_during_executionכש-callback באירועים האלה חרג מפסק הזמן.Stopו-SubagentStop: callback שחרג מפסק הזמן נספר כהחזרה בלי החלטה. הסוכן או תת-הסוכן עוצרים כאילו אותו callback התיר אותם, והחלטה מ-hooks אחרים שלכם על האירוע עדיין חלה. לפני Claude Code v2.1.273, callback שלStopאוSubagentStopשחרג מפסק הזמן נספר כריצת hook שנכשלה, ו-Claude Code השליך את ההחלטות של hooks אחרים שלכם על האירוע.SessionStart: callback שחרג מפסק הזמן נספר כהחזרה בלי פלט, והסשן ממשיך עם הפלט של hooks אחרים שלSessionStart.PreModelSwitch: Claude Code חוסם את החלפת המודל. Hook שלא ענה לא אישר את ההחלפה.אירועים אחרים, כמו
Notification,PreCompactו-PostModelSwitch: Claude Code רושם את הכשל ביומן וממשיך. עםonFailure: "block", פסק זמן ב-PreCompact, ב-Elicitationאו ב-WorktreeCreateחוסם את הפעולה.
בפעם הראשונה ש-callback של Stop או SessionStart חורג מפסק הזמן בסשן הראשי, Claude Code גם מוסיף SDKInformationalMessage לזרם ההודעות, ובו נאמר שהאפליקציה שמניעה את הסשן לא הגיבה. פסקי זמן מאוחרים יותר לא חוזרים על ההודעה הזאת כל עוד האפליקציה נשארת בלי תגובה.
אם קוטעים את השאילתה בזמן ש-callback ממתין, Claude Code מבטל את קריאת הכלי הממתינה. לפני v2.1.208, קריאת הכלי עדיין יכלה להתקדם אם קטעתם במהלך callback ממתין של PreToolUse.
אם ה-callback צריך יותר זמן, הגדירו timeout גבוה יותר על ה-HookMatcher שלו. ב-TypeScript, השתמשו ב-AbortSignal מהארגומנט השלישי של ה-callback כדי לטפל בביטול בצורה מסודרת כשפסק הזמן מופעל.
#כלי נחסם באופן לא צפוי
- בדקו את כל hooks של
PreToolUseלהחזרות שלpermissionDecision: 'deny' - הוסיפו לוגים ל-hooks כדי לראות איזה
permissionDecisionReasonהם מחזירים - ודאו שתבניות matcher אינן רחבות מדי: matcher ריק מתאים לכל הכלים
#קלט שהשתנה לא מוחל
ודאו ש-
updatedInputנמצא בתוךhookSpecificOutput, לא ברמה העליונה:return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow", updatedInput: { command: "new command" } } };אל תצמדו
updatedInputעםpermissionDecision: 'defer', שמשליך את הקלט שהשתנה. השמטתpermissionDecisionתקינה: הקלט שהשתנה עדיין חל דרך הערכת ההרשאות הרגילה. אפשר גם להחזיר'allow'כדי לאשר אוטומטית את הקלט שהשתנה, או'ask'כדי להציג אותו למשתמש לאישורכללו את
hookEventNameב-hookSpecificOutputכדי לזהות לאיזה סוג hook הפלט מיועד
#Hooks של סשן לא זמינים ב-Python
אפשר לרשום את SessionStart ואת SessionEnd כ-hooks מסוג callback של ה-SDK ב-TypeScript, אבל הם לא זמינים ב-SDK של Python כי טיפוס HookEvent שלו משמיט אותם. ב-Python הם זמינים רק כ-hooks של פקודת shell שמוגדרים בקובצי הגדרות כמו .claude/settings.json. אילו קובצי הגדרות אפליקציית ה-SDK טוענת תלוי ב-setting_sources או ב-settingSources. אם מגדירים את האפשרות הזאת, כללו את המקור שמחזיק את ה-hooks:
options = ClaudeAgentOptions(
setting_sources=["project"], # Loads .claude/settings.json including hooks
)const options = {
settingSources: ["project"] // Loads .claude/settings.json including hooks
};כדי להריץ לוגיקת אתחול כ-callback של ה-SDK של Python, השתמשו בהודעה הראשונה מ-client.receive_response() כטריגר.
#בקשות הרשאה של תת-סוכנים שמתרבות
כשמפעילים כמה תת-סוכנים, כל אחד עשוי לבקש הרשאות בנפרד לקריאות הכלים שלו. כדי להימנע מבקשות חוזרות, השתמשו ב-hooks של PreToolUse כדי לאשר אוטומטית כלים מסוימים, או הגדירו כללי הרשאה, שתת-סוכנים יורשים מהשיחה של ההורה.
#לולאות hook רקורסיביות עם תת-סוכנים
Hook של UserPromptSubmit שמפעיל תת-סוכנים יכול ליצור לולאות אינסופיות אם תת-הסוכנים האלה מפעילים את אותו hook. כדי למנוע זאת:
- השתמשו במשתנה משותף או במצב סשן כדי לעקוב אם אתם כבר בתוך תת-סוכן
- תחמו hooks כך שירוצו רק עבור סשן הסוכן ברמה העליונה
#systemMessage לא מופיע בפלט
השדה systemMessage מציג הודעה למשתמש, לא למודל. ב-Claude Code v2.1.227 ואילך, systemMessage של hook יכול להופיע בזרם ההודעות כ-SDKInformationalMessage. אם זה קורה תלוי באירוע. הסעיף של כל אירוע בדף ה-hooks אומר איך הפלט מוצג. כדי להעביר הקשר למודל, החזירו additionalContext.
לפני v2.1.227, ה-SDK הציג פלט של hook בזרם ההודעות רק עבור hooks של SessionStart ו-Setup. לכל אירוע אחר, הפלט הופיע רק באירועי מחזור החיים ש-includeHookEvents (include_hook_events ב-Python) מוסיף. הרשומה של האפשרות הזאת מכסה אילו אירועי מחזור חיים כל אירוע hook מייצר.
אם צריך להציג החלטות hook לאפליקציה באופן אמין, רשמו אותן בנפרד או השתמשו בערוץ פלט ייעודי.
#משאבים קשורים
- מדריך העזר של hooks ב-Claude Code: סכמות JSON מלאות של קלט ופלט, תיעוד אירועים ותבניות matcher
- מדריך hooks של Claude Code: דוגמאות והדרכות של hooks מסוג פקודת shell
- מדריך העזר של TypeScript SDK: טיפוסי hook, הגדרות קלט ופלט ואפשרויות תצורה
- מדריך העזר של Python SDK: טיפוסי hook, הגדרות קלט ופלט ואפשרויות תצורה
- הרשאות: שליטה במה שהסוכן יכול לעשות
- כלים מותאמים: בניית כלים להרחבת יכולות הסוכן