פרק 8
הוקים: אוטומציה ואכיפת חוקים
מערכת ה-Hooks של קלוד קוד מריצה אוטומטית פקודות מעטפת, נקודות קצה של HTTP, קריאות לכלי MCP, פרומפטים של מודל שפה, או סוכני משנה בנקודות מוגדרות במחזור החיים שלו. כשאירוע נורה ו-matcher מתאים, קלוד קוד מעביר לטיפול בהוק הקשר JSON על האירוע. בהוקי פקודה הקלט מגיע ב-stdin. בהוקי HTTP הוא מגיע כגוף בקשת POST. הטיפול יכול לבדוק את הקלט, לבצע פעולה, ולהחזיר החלטה לפי הצורך.
הדבר מעניק שליטה דטרמיניסטית: פעולות מסוימות מתרחשות תמיד. משתמשים בהוקים כדי לאכוף כללי פרויקט, לאוטמט משימות שגרתיות ולחבר את קלוד קוד לכלים קיימים.
קלוד קוד תומך בחמישה סוגי הוקים: פקודות מעטפת (type: "command"), נקודות קצה של HTTP (type: "http"), קריאות לכלי MCP שכבר מחוברים (type: "mcp_tool"), פרומפטים להערכה חד פעמית מבוססת מודל (type: "prompt"), וסוכנים המריצים בדיקות מרובות שלבים עם גישה לקריאת קבצים וחיפוש בקוד (type: "agent"). הוקי סוכן הם ניסיוניים ועשויים להשתנות. סוכן המשנה חוקר עד 50 תורות ואז מחזיר { "ok": true } או { "ok": false, "reason": "..." }. תוסף יכול גם לרשום הוקים כפונקציות JavaScript שקלוד קוד קורא בתהליך שלו, והן יכולות גם לצייר בממשק. תוסף כזה הוא mod, וההוקים שבמדריך הזה ממשיכים לעבוד לצדו.
הוק prompt שולח את קלט ה-JSON ואת הפרומפט למודל, כברירת מחדל המודל שקלוד קוד משתמש בו לעבודה ברקע, ומצפה ל-JSON עם ok ו-reason. מציינים את הקלט ב-$ARGUMENTS. אם ה-placeholder לא מופיע, קלט ה-JSON מצורף לסוף הפרומפט. אפשר לברוח מ-$ בלוכסן הפוך: \$1.00 נשאר הטקסט $1.00. ב-Stop וב-SubagentStop, ok: false ממשיך את התור ו-reason הוא ההוראה הבאה, אלא אם התשובה כוללת גם impossible: true, ואז התור נגמר. ב-PreToolUse, ok: false דוחה את הקריאה. כברירת מחדל התור נגמר והסיבה מופיעה כאזהרה. "continueOnBlock": true מחזיר את הסיבה לקלוד כשגיאת כלי והתור ממשיך, כמו permissionDecision: "deny" בהוק פקודה. לפני v2.1.210 הסיבה חזרה כשגיאת כלי והתור המשיך. ב-PostToolUse ברירת המחדל זהה: התור נגמר והסיבה מופיעה כאזהרה, ו-continueOnBlock: true מחזיר את הסיבה לקלוד והתור ממשיך. ב-PostToolBatch, ב-UserPromptSubmit וב-UserPromptExpansion התור נגמר והסיבה מופיעה כאזהרה. באירועים האלה decision: "block" מסיים את התור בלי קשר ל-continue. ב-PostToolUseFailure וב-TaskCreated הסיבה חוזרת כשגיאת כלי והתור ממשיך, בלי קשר ל-continueOnBlock. ב-TaskCompleted, כשמשימה מסומנת כהושלמה במהלך תור, הסיבה חוזרת כשגיאת כלי והתור ממשיך, בלי קשר ל-continueOnBlock. כשחבר צוות עוצר, האירוע מתנהג כמו TeammateIdle ועוצר את החבר כברירת מחדל. בהוק פקודה, {"continue": false} ב-TaskCompleted עוצר את החבר רק כשהאירוע נורה כי החבר סיים תור עם משימות שעדיין בתהליך. כש-TaskUpdate סימן את המשימה, קלוד קוד מתעלם מ-continue: false, ויציאה 2 עדיין חוסמת את ההשלמה. ב-TaskCreated של הוק פקודה, יציאה 2 או {"decision": "block"} מוחקים את המשימה ומחזירים את ההודעה לקלוד כשגיאת הכלי. continue: false מדולג. ב-TeammateIdle של הוק פקודה, יציאה 2 מחזירה את stderr לחבר והוא ממשיך לעבוד. {"continue": false} עוצר את החבר לגמרי, כמו הוק Stop, ו-stopReason מוצג למשתמש. השדה team_name ב-TaskCreated, ב-TaskCompleted וב-TeammateIdle מיושן ויוסר. מ-v2.1.290, agent_id באירועים האלה מזהה את סוכן המשנה או את חבר הצוות שבתוך התהליך, והוא יכול להיעדר. ב-TeammateIdle של הוק prompt החבר נעצר והסיבה מופיעה כאזהרה. continueOnBlock: true מחזיר את הסיבה לחבר ומשאיר אותו בעבודה. הוק agent ב-ok: false מטופל כמו הוק prompt עם continueOnBlock: true על אותו אירוע. אין לו שדה continueOnBlock, והוא לא תומך ב-impossible. ב-PermissionRequest ל-ok: false אין השפעה. הוק agent על האירוע הזה מדולג, וזרימת ההרשאה נמשכת. ב-PermissionDenied הפלט של הוק prompt או agent נזרק: הם רצים, אבל הפלט היחיד שהאירוע קורא הוא hookSpecificOutput.retry, והם לא יכולים להחזיר אותו.
אירועים שתומכים בכל חמשת הסוגים: PermissionDenied, PostToolBatch, PostToolUse, PostToolUseFailure, PreToolUse, Stop, SubagentStop, TaskCompleted, TaskCreated, TeammateIdle, UserPromptExpansion, UserPromptSubmit. PermissionRequest תומך ב-command, ב-http, ב-mcp_tool וב-prompt, לא ב-agent. אירועים שתומכים ב-command, ב-http וב-mcp_tool, ולא ב-prompt או ב-agent: ConfigChange, CwdChanged, DirectoryAdded, Elicitation, ElicitationResult, FileChanged, InstructionsLoaded, MessageDisplay, Notification, PostCompact, PostModelSwitch, PreCompact, PreModelSwitch, SessionEnd, StopFailure, SubagentStart, WorktreeCreate, WorktreeRemove.
קלוד קוד מפעיל את אותם אירועי הוק בכל סביבה שבה הוא רץ: בטרמינל, בהרחבות ה-IDE, באפליקציית השולחן (Desktop), ובסשנים בענן. האירועים מתחלקים לשלושה קצבים עיקריים:
- פעם בכל סשן:
SessionStartו-SessionEnd. - פעם בכל תור שיחה:
UserPromptSubmit,Stop, ו-StopFailure. - בכל קריאה לכלי בלולאת הסוכן:
PreToolUseו-PostToolUse(למעט קריאות לכליEndConversation, שמדלגות על שניהם).
במחזור החיים יש גם הכנה חד פעמית, הרחבת פקודות slash, הרשאות, כשלים, אצוות כלים, סוכני משנה, משימות, דחיסת הקשר, החלפת מודל, שינויי קבצים ותיקיות, ואירועי תצוגה. הטבלה בהמשך מסכמת מתי כל אירוע נורה. לכל אירוע יש סכמת קלט ואפשרויות לשליטה בהחלטה.
הקלט המשותף כולל בין השאר session_id, cwd ו-hook_event_name, ולעיתים permission_mode ו-prompt_id. מצב שמוצג כ-Manual מגיע כ-"default", לא כ-"manual". prompt_id הוא UUID של הפרומפט הנוכחי, והוא תואם ל-prompt.id באירועי OpenTelemetry. הוא נעדר עד הקלט הראשון של המשתמש. מ-v2.1.257 יכול להופיע scratchpad_dir, הנתיב לתיקיית הטיוטות של הסשן, והוא נעדר כשאין טיוטה או שתיקיית הזמני לא זמינה. באירועים בתוך הקשר של שימוש בכלי, כשהמודל תומך בפרמטר, מופיע אובייקט effort עם level של low, medium, high, xhigh או max, כולל רמה של כישור, של סוכן משנה, או של ultrathink. אם הרמה שנקבעה לא נתמכת במודל הפעיל, level מדווח את הרמה שקלוד קוד הריץ בפועל. רמה ששיניתם יכולה להופיע כאן לפני שהיא מתחילה לחול. אותה רמה זמינה לפקודת ההוק ולכלי Bash כ-$CLAUDE_EFFORT. אין משתנה $CLAUDE_MODEL. אם הגדרתם $ANTHROPIC_MODEL במעטפת, ההוק יכול לקרוא אותו, אבל הערך לא משתנה כשמחליפים מודל עם /model במהלך הסשן. רק הוק SessionStart יכול לקבל שדה model, והוא לא תמיד נכלל. PreModelSwitch ו-PostModelSwitch מקבלים from_model ו-to_model. קובץ transcript_path נכתב באסינכרון ועשוי לפגר אחרי השיחה שבזיכרון. הוק שצריך את טקסט העוזר הסופי של התור הנוכחי קורא last_assistant_message ב-Stop או ב-SubagentStop, לא את קובץ התמליל. ב-StopFailure אותו שדה הוא טקסט שגיאת ה-API עצמו, למשל API Error: Rate limit reached, לא תשובת שיחה.
דרכים נוספות להרחבת קלוד קוד כוללות כישורים (skills) למתן הוראות נוספות ופקודות להרצה, סוכני משנה (subagents) להרצת משימות בהקשרים מבודדים, ותוספים (plugins) לשיתוף הרחבות בין פרויקטים שונים.
לפי התיעוד הרשמי, נובמבר 2026.
#מחזור החיים של הוקים
כשאירוע נורה ו-matcher מתאים, קלוד קוד מעביר לטיפול בהוק JSON עם הקשר על האירוע. הטבלה הבאה מסכמת מתי כל אירוע נורה:
| אירוע | מתי זה נורה |
|---|---|
SessionStart | כשסשן מתחיל או ממשיך |
Setup | כשמפעילים את קלוד קוד עם --init-only, או עם --init או --maintenance במצב -p. מיועד להכנה חד פעמית ב-CI או בסקריפטים. ה-matcher הוא init או maintenance |
UserPromptSubmit | כששולחים פרומפט, לפני שקלוד מעבד אותו. נורה גם בתור שקלוד קוד מתחיל בעצמו: משימה מתוזמנת, כולל איטרציה של /loop, דיווח של סוכן משנה ברקע לסשן שהפעיל אותו, או הודעה שסשן אחר שולח לשיחה הראשית |
UserPromptExpansion | כשפקודה שהמשתמש הקליד מתרחבת לפרומפט, לפני שהיא מגיעה לקלוד. יכולה לחסום את ההרחבה. ה-matcher הוא command_name. expansion_type הוא slash_command לכישור ולפקודה מותאמת, או mcp_prompt לפרומפט של שרת MCP. הקלט כולל גם command_args ו-command_source. הקלדה ישירה של /skillname עוקפת PreToolUse על הכלי Skill, והאירוע הזה מכסה את המסלול הזה |
PreToolUse | לפני ביצוע קריאה לכלי. יכולה לחסום אותה. לא נורה על שם כלי לא מוכר, על קלט שנכשל באימות לפני ההוקים, על EndConversation, או על קובץ שנוסף עם @ בלי קריאת כלי. רץ לפני כל קריאה לכלי, בין אם היא צריכה הרשאה ובין אם לא |
PermissionRequest | כשקריאה לכלי זקוקה להחלטת הרשאה. לא נורה על EndConversation ולא על בקשת רשת של פקודה בסנדבוקס |
PermissionDenied | כשמצב auto דוחה קריאה לכלי, כולל דחיות בלי פסק דין של מסווג. נורה רק במצב auto: לא בדחייה ידנית של דיאלוג, לא כשהוק PreToolUse חוסם, ולא כשכלל deny מתאים. בפלט JSON, hookSpecificOutput.retry: true אומר למודל שאפשר לנסות שוב את הקריאה שנדחתה. קלוד קוד מתעלם מ-retry כשהמסווג לא הפיק פסק דין: התשובה לא נפרסה, או שבדיקת בטיחות נפרדת ממצב auto סירבה לבקשה של המסווג עצמו. בדחיות האלה קלוד קוד כבר אומר למודל בהודעת הדחייה אם לנסות אחר כך או להמשיך. ב-reason, פסק דין של מסווג בדרך כלל שם את הכלל בסוגריים מרובעים, למשל [Data Exfiltration]. דחייה בלי פסק דין מתחילה ב-Auto mode could not evaluate this action and is blocking it for safety. כשהמסווג לא זמין, reason הוא Classifier unavailable |
PostToolUse | אחרי שקריאה לכלי מצליחה. הקלט יכול לכלול duration_ms: משך הריצה במילישניות, בלי זמן בקשת הרשאה והוקי PreToolUse |
PostToolUseFailure | אחרי שקריאה לכלי שהתחילה לרוץ נכשלת. לא נורה על ביטול של כלי שרץ, וגם לא על דחייה לפני ביצוע: שם כלי לא מוכר, קלט שנכשל באימות, או דחיית הרשאה. ב-Bash וב-PowerShell שרצו, השורה הראשונה של error היא בדרך כלל Exit code N, ואחריה הפלט כבלוק אחד שבו stdout ו-stderr מעורבבים. ייתכן גם כשל בלי שורת קוד יציאה, כשלא ניתן להפעיל את המעטפת. קלוד קוד יכול להוסיף שורה משלו, למשל Command timed out after 2m 0s, ולחתוך מחרוזת ארוכה באמצע סביב ... [N characters truncated] .... is_interrupt הוא true כשהכשל הגיע כ-abort ולא כשגיאה שהכלי דיווח. גם כאן duration_ms הוא אופציונלי ובאותו מובן |
PostToolBatch | אחרי שאצווה מלאה של קריאות כלים במקביל מסתיימת, לפני הקריאה הבאה למודל |
Notification | כשקלוד קוד שולח התראה |
MessageDisplay | בזמן שטקסט הודעת העוזר מוצג. אירוע תצוגה בלבד, בזמן שהטקסט זורם |
SubagentStart | כשסוכן משנה נוצר, כשממשיכים סוכן משנה, ובכל פעם שחבר צוות בתוך התהליך מטפל בהודעה חדשה |
SubagentStop | כשסוכן משנה מסיים. נורה גם כשסוכן פנימי מסיים, למשל הצעות פרומפט או /btw. אז agent_type הוא שם הסוכן של הסשן, או מחרוזת ריקה אם אין כזה. matcher שמציין סוג סוכן לא מתאים למחרוזת ריקה. matcher שמושמט, מחרוזת ריקה, "*", או ביטוי רגולרי שמתאים למחרוזת ריקה, רץ גם עליה |
TaskCreated | כשמשימה נוצרת דרך TaskCreate. בסשן בלי כלי Task האירוע לא נורה |
TaskCompleted | כשמשימה מסומנת כהושלמה, או כשחבר צוות מסיים תור עם משימות שעדיין בתהליך |
Stop | כשקלוד מסיים להגיב. לא רץ אם העצירה היא מהפרעה של המשתמש |
StopFailure | כשהתור מסתיים בגלל שגיאת API. ה-matcher הוא סוג השגיאה: rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, account_on_hold, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, cloud_credential_error או unknown |
TeammateIdle | כשחבר בצוות סוכנים עומד להיות במצב idle |
InstructionsLoaded | כשקובץ CLAUDE.md או .claude/rules/*.md נטען להקשר. ה-matcher הוא load_reason: session_start, nested_traversal, path_glob_match, include או compact. נורה בתחילת סשן, כשקבצים נטענים בעצלתיים במהלך הסשן, וכשהם נטענים מחדש אחרי דחיסה. טעינה עצלה כוללת כניסה לתיקיית משנה עם CLAUDE.md מקונן, והתאמה של כללי paths: ב-frontmatter. לא נורה כשקלוד קורא AGENTS.md ישירות דרך הגדרת Project instructions. כן נורה כש-CLAUDE.md מייבא את AGENTS.md, עם load_reason של include, או כש-CLAUDE.md הוא symlink אליו. הקלט כולל גם memory_type: User, Project, Local או Managed. בטעינת path_glob_match יכול להופיע globs. בטעינה עצלה יכול להופיע trigger_file_path. בטעינת include יכול להופיע parent_file_path |
ConfigChange | כשקובץ הגדרות משתנה במהלך סשן |
CwdChanged | כשפקודת מעטפת בשיחה הראשית משנה את תיקיית העבודה, למשל כשקלוד מריץ פקודת cd. שימושי לניהול סביבה תגובתי עם כלים כמו direnv. הקלט כולל old_cwd ו-new_cwd |
DirectoryAdded | כשתיקיית עבודה נוספת באמצע סשן דרך /add-dir או בקשת הבקרה register_repo_root ב-SDK. ה-matcher הוא slash_command או register_repo_root. לא נורה על --add-dir בהפעלה, על הוספה בלשונית Workspace של /permissions, או על תיקייה שכבר נמצאת בתיקיות העבודה או בתוך אחת מהן |
FileChanged | כשקובץ במעקב משתנה בדיסק. שדה matcher מציין אילו שמות קבצים לעקוב |
WorktreeCreate | כשנוצר worktree: --worktree, כלי EnterWorktree, isolation: "worktree" בסוכן משנה, סוכן של workflow ב-worktree משלו, סשן רקע מבודד, או claude remote-control במצב worktree. מחליף את התנהגות git המוגדרת כברירת מחדל. לא נורה כשקלוד מריץ git worktree add דרך Bash |
WorktreeRemove | כשקלוד קוד מנקה worktree שהוק WorktreeCreate יצר: ביציאה מסשן אינטראקטיבי, כולל הסרה בלי דיאלוג מסשן בלי שם כשאין קבצים ששונו או לא במעקב, במחיקת סשן רקע, ב-ExitWorktree, כשסשן ש-claude remote-control פתח במצב worktree מסתיים בלי קריסה, או כשעוצרים את claude remote-control בזמן שסשנים כאלה עדיין רצים, ואין קבצים ששונו או לא במעקב ואין commits חדשים. לא נורה כשסוכן משנה או סוכן workflow מסיים |
PreCompact | לפני דחיסת הקשר. matcher auto כולל את חלון הדחיסה האוטומטית וגם דחיסה בזמן idle |
PostCompact | אחרי שדחיסת הקשר מסתיימת. הקלט כולל compact_summary |
PreModelSwitch | לפני שקלוד קוד מחיל החלפת מודל שביקשתם או שביקש לקוח. יכולה לחסום את ההחלפה. לא נורה על החלפה שקלוד קוד עושה בעצמו, כמו נפילה אוטומטית למודל או שחזור המודל בהמשך סשן. דורש v2.1.251 ומעלה |
PostModelSwitch | אחרי שמודל הסשן משתנה, כולל נפילה אוטומטית, כניסה או יציאה ממצב תוכנית עם opusplan, ושחזור המודל כשממשיכים סשן. לא נורה כשמודל משרשרת fallback מגיש תור אחד בלי לשנות את מודל הסשן. דורש v2.1.251 ומעלה |
Elicitation | כששרת MCP מבקש קלט מהמשתמש במהלך קריאה לכלי. ה-matcher הוא שם שרת ה-MCP |
ElicitationResult | אחרי שהמשתמש עונה ל-elicitation של MCP, לפני שהתשובה נשלחת לשרת. ה-matcher הוא שם שרת ה-MCP, כמו ב-Elicitation |
SessionEnd | כשסשן מסתיים. ערכי matcher של הסיבה: clear, resume, logout, prompt_input_exit, other. הערך bypass_permissions_disabled הוסר ב-v2.1.234 וקלוד קוד לא שולח אותו |
Setup הוא שלב הכנה אופציונלי שנכנס ל-SessionStart. ה-matcher הוא init עבור claude --init-only או claude -p --init, ו-maintenance עבור claude -p --maintenance. ב-Setup רצים רק הוקים מסוג command. הוק mcp_tool על Setup מדולג תמיד. גם SessionStart בהפעלה, כולל --continue או --resume, נורה לפני ששרתי MCP של הסשן זמינים להוקים, וקלוד קוד מדלג על הוקי mcp_tool בלי לקרוא לכלי. ה-debug log רושם mcp_tool hooks are not available for the 'SessionStart' hook event (no MCP client context), או אותה הודעה עם Setup. כש-SessionStart נורה שוב מאוחר יותר בסשן, אחרי /clear או דחיסה, הוקי mcp_tool שלו רצים. SessionStart ו-Setup לא תומכים בהוקי http, prompt או agent.
באירוע שבו הוק mcp_tool יכול לחסום או לשנות תוצאה, כמו PreToolUse או Stop, קלוד קוד ממתין לשרת שעדיין מתחבר, לכל היותר MCP_TIMEOUT ובתוך ה-timeout של ההוק. באירוע תצפית, כמו Notification או SessionEnd, הוא לא ממתין. שרת במצב cached מתחבר כשההוק קורא לכלי. אם השרת לא מחובר באותו רגע, זו שגיאה לא חוסמת והביצוע ממשיך. ההוק לא מתחיל זרימת OAuth, לכן מאמתים את השרת מ-/mcp לפני כן. אם הכלי מחזיר isError: true, גם זו שגיאה לא חוסמת.
claude --init-only מריץ הוקי Setup והוקי SessionStart עם matcher startup, ואז יוצא בלי שיחה. בהצלחה הוא לא מדפיס דבר לטרמינל. לאימות מריצים claude --debug-file <path> --init-only ובודקים בלוג את רשומות Setup ו-SessionStart. ב-Setup, בכל קוד יציאה, קלוד קוד זורק שדות JSON כמו systemMessage, continue ו-hookSpecificOutput.additionalContext. ב--p עם --output-format stream-json --verbose, stdout, stderr וקוד היציאה מופיעים כאירועי hook_response. כשמתחילים או ממשיכים שיחה עם -p, צריך גם פרומפט, כארגומנט או ב-stdin, אלא אם הוק SessionStart מספק initialUserMessage, או שממשיכים סשן עם קריאת כלי שנדחתה ב-defer.
כי Setup לא נורה בכל הפעלה, תוסף שצריך תלות מותקנת לא יכול להסתמך רק עליו. הדפוס המעשי הוא לבדוק את התלות בשימוש הראשון ולהתקין אם היא חסרה, למשל הוק או כישור שבודק את ${CLAUDE_PLUGIN_DATA}/node_modules ומריץ npm install אם התיקייה חסרה. אם מפיצים את התוסף דרך marketplace, קלוד קוד מתקין אוטומטית תלויות חבילות Node.js כשירות כשהוא שומר את התוסף במטמון, וייתכן שהדפוס לא נדרש.
כשמתחילים סשן אינטראקטיבי, ממשיכים שיחה בהפעלה עם --continue או --resume, או מריצים /clear, הוקי SessionStart רצים ברקע. אפשר להקליד מיד, ושיחה שהמשכתם מופיעה בלי להמתין להוקים. התשובה הראשונה של קלוד עדיין ממתינה שההוקים יסתיימו, כדי שההקשר שלהם יגיע אליו. פרומפט שנשלח בזמן שהם רצים לא מגיע לקלוד עד שהם מסתיימים. כשמחליפים שיחה עם /resume מתוך סשן, ההחלפה ממתינה לסיום ההוקים. אם מריצים /clear או עוברים לשיחה אחרת בזמן שהוקי רקע עדיין רצים, שום דבר שהם מחזירים לא חל על הסשן. בזמן ההמתנה, Esc מחזיר את הפרומפט לשדה הקלט בלי לשלוח אותו, וההוקים ממשיכים לרוץ.
פרומפט שנחסם ב-UserPromptSubmit לא מגיע לקלוד. כברירת מחדל הודעת החסימה למשתמש נגמרת ב-Original prompt: ואחריו הטקסט שנשלח, וקלוד קוד כותב את ההודעה לקובץ התמליל. "suppressOriginalPrompt": true בתוך hookSpecificOutput משמיט את הטקסט מההודעה, גם כשחוסמים עם decision: "block" וגם ביציאה 2. הטקסט עדיין יכול להופיע בקבצים מקומיים כמו התמליל והיסטוריית הפרומפטים, ולכן הוק חוסם אינו דרך להשאיר סוד מחוץ לדיסק. כשההוק לא חוסם ויוצא 0, stdout בטקסט רגיל ו-additionalContext מוזרקים כתזכורת מערכת שמתחילה בשם ההוק. אין להם שורה נראית בתמליל, וקלוד קורא את שניהם. תוכן שהודבק וכווץ ל-placeholder כמו [Pasted text #N] מגיע בשדה prompt כשהוא מורחב במקום. בסשן שבו קלוד קוד מסמן טקסט מודבק לקלוד, התוכן המורחב יושב בין שורת <pasted_content id="…"> לשורת הסגירה, וההוק שמפרש את הפרומפט מתחשב בשורות האלה. כשיש כותרת מותאמת, הקלט כולל session_title, והפלט יכול לקבוע sessionTitle לפי תוכן הפרומפט. כשהפרומפט הוא כישור או פקודה שהקלדתם, למשל /deploy staging, השדה prompt הוא השורה כפי שהוקלדה, לא התוכן המורחב. אם ההוק חוסם, הפקודה לא רצה, וגם פקודות מעטפת שהכישור מזריק עם !`פקודה` לא רצות. כדי להתאים לשם הפקודה או לארגומנטים משתמשים ב-UserPromptExpansion.
אם לוחצים על Esc או מפריעים לסשן בזמן שהוקי UserPromptSubmit עדיין בודקים פרומפט, הבדיקה נעצרת והפרומפט לא מגיע לקלוד. הטקסט שהוקלד בטרמינל חוזר לשדה הקלט, וההוקים בודקים אותו שוב כששולחים אותו מחדש. אם הוק חוסם באותו רגע שבו מפריעים, הפרומפט נשאר חסום: רואים את הודעת החסימה של ההוק, והטקסט לא חוזר לשדה.
בלולאת התור מופיעים UserPromptSubmit ו-UserPromptExpansion לפקודות slash ולפרומפטי MCP. בתוך לולאת הסוכן המקוננת מופיעים PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart ו-SubagentStop, ו-TaskCreated ו-TaskCompleted. ב-PostToolBatch, tool_response הוא תוכן tool_result המסודר שהמודל רואה, מחרוזת או מערך בלוקים, לא אובייקט ה-Output המובנה של PostToolUse. ב-Read זה טקסט עם מספרי שורות. יציאה 2, decision: "block" או continue: false עוצרים את לולאת הסוכן לפני הקריאה הבאה למודל. הודעת החסימה באה מ-reason, מ-stopReason, או מ-stderr ביציאה 2. היא מופיעה כאזהרה בתמליל ונשארת בשיחה, כך שקלוד רואה אותה כשהשיחה ממשיכה. התור מסתיים ב-Stop או ב-StopFailure. אחרי התור מופיעים TeammateIdle, PreCompact, PostCompact ו-SessionEnd.
כשהוק SubagentStart רץ שוב לאותו סוכן משנה, ההקשר מוזרק רק אם ההקשר של הסוכן לא מחזיק כבר את העותק מהרצה קודמת. העותק מההפעלה נשאר במקום, וכך גם מטמון הפרומפט של הסוכן. אחרי דחיסה אוטומטית שמוחקת אותו, ההרצה הבאה מזריקה שוב.
מ-v2.1.271, סוכן משנה עם הכלי SubagentHandback מוסר את הדוח דרך הכלי לפני העצירה. last_assistant_message ב-SubagentStop הוא טקסט הסיום, אם יש, לא הדוח. הדוח הוא tool_input.message בהוק PreToolUse או PostToolUse על SubagentHandback. בתוצאת completed של כלי Agent, content נושא הערה קצרה על המסירה במקום הדוח. באותה תוצאה בחזית יש גם status, agentId, ו-resolvedModel: המודל שהסוכן התחיל עליו, והוא יכול להיות שונה מה-model שב-tool_input. modelsUsed מופיע רק כשהמודל הוחלף באמצע הריצה, עם כפילויות רצופות מכווצות. יש גם totalTokens ו-usage של בקשת ה-API האחרונה בלבד, לא של כל הריצה, וכן totalDurationMs ו-totalToolUseCount. לסיכום טוקנים ועלות על כל הריצה משתמשים במוני הטוקנים והעלות עם query_source של subagent. בסוכן משנה ברקע אין שדות שימוש: status הוא async_launched, ויש agentId, description, prompt, outputFile ו-resolvedModel. משימה בחזית שקלוד קוד מעביר לרקע באמצע הריצה חוזרת באותו מעבר, ו-resolvedModel הוא המודל שבשימוש אז, כך שהחלפה שקרתה לפני המעבר משתקפת שם. ב-PreToolUse, "ask" על SubagentHandback לא פותח דיאלוג והקריאה רצה גם אם המסווג מתנגד. "deny" עוצר אותה: קלוד קוד אומר לסוכן, עד שלוש פעמים, שהדוח לא נמסר ושעליו לקרוא לכלי שוב. אם הדחייה נמשכת, הסוכן מסיים בלי דוח דרך הכלי, והשיחה שממתינה לו מקבלת הערה על כך. background_tasks ו-session_crons ב-SubagentStop שייכים לסשן האב, לא לסוכן המשנה.
חסימת PreCompact אוטומטי לפני גבול ההקשר מדלגת על הדחיסה והשיחה ממשיכה בלי דחיסה. חסימה אחרי שגיאת גבול הקשר שכבר חזרה מה-API מעלה את השגיאה, והבקשה הנוכחית נכשלת. ב-/compact ידני, stderr ביציאה 2 מוצג למשתמש. אפשר גם לחסום עם {"decision": "block"}. matcher auto חל גם על דחיסה בזמן idle. ב-manual, custom_instructions הוא מה שהמשתמש מעביר ל-/compact, או null אם לא העביר כלום. ב-auto השדה הוא null.
Elicitation ו-ElicitationResult מקוננים בתוך הרצת כלי MCP. כשהוק Elicitation עונה לבקשה, קלוד קוד שולח את התשובה לשרת בלי להריץ ElicitationResult. hookSpecificOutput של שני האירועים חל רק ביציאה 0, ומתעלמים ממנו בכל קוד יציאה אחר. עונים ביציאה 0 עם action: accept ושדה content לערכי הטופס, decline, או cancel. בלי פלט ביציאה 0 הבקשה נשארת לדיאלוג. בדיאלוג, Decline שולח decline ו-Esc שולח cancel. בבקשת URL, accept מדלג על הדיאלוג והכתובת לא נפתחת. קלוד קוד זורק reason, systemMessage ו-continue, והמשתמש והשרת לא רואים למה ההוק דחה. ב-ElicitationResult, content מחליף את כל אובייקט ה-content של המשתמש, ולכן כוללים גם שדות שלא משנים. בלי action קלוד קוד מתעלם מ-hookSpecificOutput. action של ההוק מחליף את פעולת המשתמש: בודקים שהקלט הוא accept לפני שמחזירים accept, אחרת דחייה של המשתמש הופכת לאישור. cancel נשלח לשרת יחד עם הערכים שהמשתמש שלח. כדי לא לשלוח אותם מחזירים decline. יציאה 2 או "decision": "block" דוחים את הבקשה. דחייה מהוק אחד גוברת על accept או cancel מהוק אחר. מ-v2.1.105 עד התיקון ב-v2.1.284 קלוד קוד התעלם מ-decision ברמה העליונה בשני האירועים. PermissionDenied הוא ענף צדדי מ-PermissionRequest לדחיות במצב auto.
האירועים העצמאיים האסינכרוניים הם WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, FileChanged ו-DirectoryAdded. PreModelSwitch הוא אירוע עצמאי סידרתי שרץ לפני החלפת מודל שביקשתם או שביקש לקוח: /model ובורר המודל, כולל Option+P או Alt+P, הגדרת Model ב-/config, הפעלת fast mode כשהיא משנה את מודל הסשן, ו-set_model או שינוי מודל בבקשת apply_flag_settings ממארח Agent SDK או מ-Remote Control. PostModelSwitch הוא אירוע עצמאי אסינכרוני אחרי שמודל הסשן משתנה. קלוד קוד משווה את ה-matcher של PreModelSwitch ושל PostModelSwitch לשם הקנוני של מודל היעד, בלי סופית [1m]. כינוי כמו opus, מזהה מודל מתוארך ומזהה של ספק, למשל מזהה Amazon Bedrock, מתאימים לאותו שם קנוני שהם נפתרים אליו. כשאין שם קנוני ליעד, למשל מזהה מודל מותאם שרק שער LLM מכיר, כל הוקי PreModelSwitch רצים בלי קשר ל-matcher, ולכן הוק שחוסם בודק את to_model בקלט. הקלט כולל גם requested_model, source (command, picker או sdk), context_tokens, prompt_cache_warm, cache_ttl, estimated_cache_write_usd ו-pricing (configured, catalog או default). "allow" מדלג על אישור שקלוד קוד מציג כשמטמון הפרומפט עדיין חם. "ask" מוצג רק ב-/model בסשן אינטראקטיבי. בכל משטח אחר, כולל -p, /config ובקשת set_model, "ask" נחשב סירוב. systemMessage מהוק PreModelSwitch מוצג למשתמש בלי קשר להחלטה. permissionDecisionReason ב-"deny" מוצג למשתמש כסיבת החסימה, או חוזר כשגיאה לבקשת set_model. ב-"ask" הוא מוצג בבקשת האישור. ב-"allow" מתעלמים ממנו. PostModelSwitch מעביר stdout בטקסט רגיל, או additionalContext, לבקשה הבאה אחרי ההחלפה. אם ההוק לא הסתיים בתוך חמש שניות מרגע ששולחים את הפרומפט הבא, הבקשה יוצאת בלי הפלט והוא מצטרף לבקשה שאחריה. אם המודל משתנה כמה פעמים לפני הבקשה הבאה, מועבר רק הפלט של יעד ההחלפה האחרונה. ב-PostModelSwitch, source יכול להיות גם auto או resume. requested_model הוא null כש-source הוא auto. כש-source הוא resume, זה המודל השמור שקלוד קוד שחזר.
MessageDisplay הוא אירוע תצוגה בלבד בזמן שטקסט הודעת העוזר זורם. קלוד קוד מציג את ההודעה במנות: בכל פעם שמנה של שורות שהושלמו מוכנה, ההוק רץ פעם אחת עם השורות האלה ומציג את טקסט ההחלפה במקומן. הודעה ארוכה מייצרת כמה קריאות. ברירת המחדל ל-timeout היא 10 שניות, וקלוד קוד מחזיק כל מנה עד שההוק חוזר. אם ההוק נכשל או מגיע ל-timeout, מוצג הטקסט המקורי. כשל של הסקריפט, למשל jq חסר, נרשם רק ב-debug log, לא בסשן. displayContent מחליף רק את מה שעל המסך. התמליל, מה שקלוד רואה, ומצב verbose נשארים המקור. ההוק מקבל רק טקסט של הודעת העוזר, ולכן תוצאות כלים והטקסט שאתם מקלידים לא משתנים. אין לו matcher, והודעה בלי טקסט, למשל קריאה לכלי בלבד, לא מפעילה אותו. המנות הן שורות שלמות, חוץ מהמנה האחרונה שיכולה להיגמר באמצע שורה. בריצה אינטראקטיבית, כשההודעה נגמרת בשורה חדשה, ה-delta של המנה האחרונה ריק. סימן סוף ההודעה הוא final, לא delta לא ריק. בריצה לא אינטראקטיבית, כולל שאילתות Agent SDK ו-claude -p, ההוק רץ פעם אחת לכל הודעת עוזר אחרי שהיא הושלמה: index הוא 0, final הוא true, ו-delta מכיל את כל ההודעה. הוק שאוסף את delta של כל הודעה מקבל אותו סך טקסט בשני המצבים.
DirectoryAdded רץ ברקע אחרי שההוספה כבר הושלמה, עם timeout ברירת מחדל של 600 שניות, ואינו יכול לחסום. ה-matcher הוא slash_command או register_repo_root. האירוע נורה אחרי רענון מצב הסנדבוקס וההרשאות, כך שכלים בסנדבוקס כבר רואים את התיקייה. פקודות ההוק עצמן רצות מחוץ לסנדבוקס. עם מקור slash_command, systemMessage מגיע לקלוד כהקשר בתור הבא, וספירת הוקים שנכשלו מופיעה בתמליל. פלט הכשל המלא הולך ל-debug log. עם register_repo_root, systemMessage ופלט הכשל נכתבים רק ל-debug log.
#הגדרת ההוק הראשון שלך
הוקי פקודה רצים עם הרשאות המשתמש המלאות. הם יכולים לשנות, למחוק או לקרוא כל קובץ שהחשבון יכול לגשת אליו. בודקים ובודקים את הפקודות לפני שמוסיפים אותן להגדרה.
כדי ליצור הוק, מוסיפים בלוק hooks לקובץ הגדרות. המיקום קובע את הטווח:
| מיקום | טווח | אפשר לשתף |
|---|---|---|
~/.claude/settings.json | כל הפרויקטים שלכם | לא, מקומי למחשב |
.claude/settings.json | פרויקט אחד | כן, אפשר להכניס לריפו |
.claude/settings.local.json | פרויקט אחד | לא, נכנס ל-gitignore כשקלוד קוד שומר אליו הגדרה |
| הגדרות מדיניות מנוהלות | כל הארגון | כן, בשליטת מנהל |
hooks/hooks.json של תוסף | כשהתוסף מופעל | כן, ארוז עם התוסף |
| frontmatter של כישור | שאר הסשן מרגע שהכישור הופעל | כן, בקובץ הכישור |
| frontmatter של סוכן משנה | בזמן שסוכן המשנה רץ | כן, בקובץ סוכן המשנה |
סשן בענן לא קורא את ~/.claude/settings.json המקומי. בסביבה באירוח עצמי קלוד קוד מריץ גם הוקים שהמפעיל זרע מ-~/.claude/ של מחשב ה-runner, וגם הוקים בקובץ ההגדרות המנוהלות של תמונת ה-runner, כשהקובץ הזה נכלל במקורות המנוהלים שקלוד קוד מחיל. כברירת מחדל זה קורה רק כשאין הגדרות מנוהלות מהשרת ואין מדיניות קלוד קוד שהגיעה ב-MDM. $CLAUDE_CODE_REMOTE הוא "true" בסביבת ווב מרוחקת, ולא מוגדר ב-CLI המקומי. מ-v2.1.199, $CLAUDE_CODE_BRIDGE_SESSION_ID הוא מזהה סשן Remote Control כל עוד לסשן המקומי יש חיבור Remote Control פעיל.
רשומות הוקים מרמות ההגדרות מתמזגות ולא מחליפות זו את זו: הגדרות משתמש, פרויקט והגדרות מקומיות מוסיפות את ההוקים שלהן בלי להסיר הוקים מנוהלים. הוקים מקובצי הגדרות, ממדיניות מנוהלת ומתוספים רצים גם בתוך סוכני משנה. כשסוכן משנה קורא לכלי, אירועי כלים כמו PreToolUse ו-PostToolUse מפעילים את אותם הוקים, והקלט כולל agent_id ו-agent_type. agent_type מופיע גם כשהסשן התחיל עם --agent. בתוך סוכן משנה סוג הסוכן גובר על ערך --agent של הסשן.
בסשן אינטראקטיבי קלוד קוד משהה הוקים מכל קובץ הגדרות, כולל ~/.claude/settings.json שלכם, עד שמאשרים את דיאלוג האמון בתיקייה, או בתיקיית אב שהאמון בה חל גם עליה. בסשן -p או SDK אין דיאלוג והתיקייה נחשבת מהימנה, ולכן הוקים שב-.claude/settings.json של הריפו רצים גם בתיקייה שלא אישרתם. לפני שמריצים claude -p בסקריפט על ריפו שלא כתבתם, עברו על קובצי .claude/, התחילו עם --bare, או כבו הוקים להרצה הזו. הוקים ב-frontmatter של כישור בפרויקט כפופים לאותו כלל, ונרשמים כשאתם או קלוד מפעילים את הכישור, כולל בריצת -p בתיקייה שלא אושרה. הוקים ב-frontmatter של סוכן משנה בפרויקט רצים רק אחרי אישור הדיאלוג לתיקייה שממנה הגיע קובץ הסוכן. סשן -p לא נחשב אישור. לפני v2.1.218 הוקים כאלה יכלו לרוץ מתיקייה שלא אושרה.
הוק של כישור עם "once": true מוסר אחרי הרצה מוצלחת ראשונה. הרצה שנכשלת, חוסמת ביציאה בקוד 2, או מגיעה ל-timeout, משאירה את ההוק למקום, והוא רץ שוב באירוע המתאים הבא. השדה מכובד רק ב-frontmatter של כישור. בקובצי הגדרות וב-frontmatter של סוכן הוא מדולג. הוק Stop ב-frontmatter של סוכן משנה מומר ל-SubagentStop.
אם הוקים ב-frontmatter של כישור לא נורים אחרי שאתם או קלוד מפעילים את הכישור, מריצים /hooks באותו סשן. אם הם ברשימה, הבעיה היא בהפעלת האירוע, כמו בסעיף פתרון הבעיות. אם אף הוק של הכישור לא ברשימה, ייתכן שהכישור תחת נעילה לתוספים בלבד או שעבר מגבלת הוקים, והוא רץ בלי הוקי ה-frontmatter.
- נעילה לתוספים בלבד: מנהל הארגון יכול לנעול הוקים לתוספים ולהגדרות מנוהלות עם
strictPluginOnlyCustomization. כישור שומר על הוקי ה-frontmatter תחת הנעילה רק אם הוא כישור של תוסף, כישור מובנה, או כישור בתיקיית ההגדרות המנוהלות. גם ההוקים בקובצי ההגדרות שלכם לא רצים. לבדיקה מקלידים/status. אם שורתSetting sourcesכוללתEnterprise managed settings, שואלים את המנהל אםstrictPluginOnlyCustomizationנועל הוקים. התיקון: מעבירים את הכישור לתוסף, כי כישור של תוסף שומר על הוקי ה-frontmatter. רושמים את התוסף ב-marketplace ומתקינים משם. אם קלוד קוד מסרב ל-marketplace עםis blocked by enterprise policy, מבקשים מהמנהל לאפשר אותו. - מגבלת הוקים: מ-Claude Code v2.1.304 לכל מקור כישורים יש מגבלה משלו בסשן. הפרויקט, תיקיית הכישורים האישית, וכל תוסף הם מקורות נפרדים. כישורים ממקור אחד יכולים לרשום לכל היותר 1,024 הוקים. ה-matchers שלהם יכולים להגיע יחד לכל היותר ל-16,384 תווים. matcher עם כמה הוקים תחתיו נספר פעם אחת לכל הוק. לבדיקה חוזרים על הסשן עם לוג: מתחילים סשן חדש עם
claude --debug, או מקלידים/debugאם לא מתחילים מהמעטפת. מפעילים את אותם כישורים באותו סדר, כולל כישורים שקלוד הפעיל, כי שני הסיכומים נבנים כישור אחר כישור. מבקשים מקלוד להפעיל כישור שאי אפשר להקליד כפקודה. ב-debug log מחפשיםregisters none of its hooks. שורה עם הטקסט הזה מציינת את הכישור שעבר את המגבלה. התיקון: פחות הוקים או matchers קצרים יותר בכישור הזה או בכישורים אחרים מאותו מקור, ואז סשן חדש. אם הכישורים לא שלכם, מבקשים את השינוי מהמחבר. עד אז מתחילים סשן חדש ומפעילים את הכישור הדרוש לפני כל כישור אחר מאותו מקור.
אם שני המקרים לא מתאימים, בודקים frontmatter שלא נפרס.
ההליך הבא מגדיר הוק להתראות שולחן עבודה, כדי לקבל התראה בכל פעם שקלוד ממתין לקלט במקום לצפות בטרמינל:
- הוספת ההוק להגדרות:
פתחו את הקובץ
~/.claude/settings.jsonוהוסיפו הוק עבור אירועNotification. אם הקובץ אינו קיים, צרו אותו. הדוגמה הבאה משתמשת ב-osascriptעבור macOS:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}אם קובץ ההגדרות כבר כולל מפתח hooks, הוסיפו את Notification לצד מפתחות האירועים הקיימים. כל שם אירוע הוא מפתח בתוך אובייקט ה-hooks היחיד:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }]
}
],
"Notification": [
{
"matcher": "",
"hooks": [{ "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" }]
}
]
}
}עריכה ישירה של הוקים בקובצי ההגדרות נקלטה בדרך כלל אוטומטית על ידי ה-file watcher. אם אחרי כמה שניות ההוק לא מופיע בתפריט, ייתכן שה-watcher פספס את השינוי: מתחילים את הסשן מחדש.
ניתן גם לבקש מקלוד לכתוב את ההוק על ידי תיאור הפעולה הרצויה ב-CLI. אפשר להוסיף statusMessage לטיפול, והטקסט מוצג כהודעת ספינר בזמן שההוק רץ.
אימות ההגדרה: הקלידו
/hooksכדי לפתוח תפריט עיון לקריאה בלבד. כל הוק מסומן לפי המקור שלו: הגדרות משתמש, הגדרות פרויקט, הגדרות מקומיות, תוסף, או הסשן הנוכחי. בחרו ב-Notificationכדי לוודא שההוק החדש מופיע ברשימה. בחירת ההוק מציגה את הטקסט המלא של מה שהוא מריץ, ואיפה הוא מוגדר, למשל נתיב קובץ ההגדרות או שם התוסף. כדי לעיין בכל אירועי ההוק, כולל אירועים בלי הוקים מוגדרים, בחרו ב-All eventsבסוף הרשימה. תפריט/hooksמיועד לעיון. כדי להוסיף, לערוך או להסיר הוקים, עורכים את קובץ ה-JSON ישירות או מבקשים מקלוד לבצע את השינוי. כדי להשבית זמנית את כל ההוקים, ניתן להגדיר"disableAllHooks": trueבקובץ ההגדרות. הערך שנשאר אחרי קדימות ההגדרות הוא זה שחל:"disableAllHooks": falseב-.claude/settings.jsonשל הפרויקט גובר עלtrueבהגדרות המשתמש. להרצה אחת, בלי קשר להגדרות הפרויקט וההגדרות המקומיות, מעבירים--settings '{"disableAllHooks": true}'. אין דרך להשבית הוק בודד ולהשאיר אותו בהגדרה. הוקים מהגדרות מדיניות מנוהלות ממשיכים לרוץ, אלא אםdisableAllHooksמוגדר גם ברמת ההגדרות המנוהלות.disableAllHooksמחוץ להגדרות המנוהלות לא מכבה הוקים מנוהלים. אם התפריט מציגOnly hooks from managed settings run here, הארגון הגדירallowManagedHooksOnly: הוקים בהגדרות המשתמש, הפרויקט, ההגדרות המקומיות והתוספים לא רצים ולא מופיעים ברשימה. הוקים מתוספים שמופעלים בכוח דרךenabledPluginsבהגדרות המנוהלות פטורים, חוץ מתוספים עם מקורcommand: אותם קלוד קוד מכבה, כולל כאלה שמופעלים בכוח, אלא אםdisableCommandPluginSourcesמוגדר במפורש ל-false. מקורותcommandדורשים Claude Code v2.1.229 ומעלה. באותו מצב קלוד קוד גם מצמצם אתstatusLine,fileSuggestionו-subagentStatusLineלהגדרות המנוהלות, וחוסם פקודותheadersHelperשל marketplace אלא אםdisableCommandPluginSourcesהואfalse, למעט marketplace שההגדרות המנוהלות עצמן מכריזות עליו.בדיקת ההוק: לחצו על
Escכדי לחזור ל-CLI. לחצו עלShift+Tabעד ששורת המצב מציגה⏸ manual mode on, בקשו מקלוד לבצע פעולה הדורשת הרשאה, ועברו לחלון אחר מחוץ לטרמינל. כעת אמורה להתקבל התראת שולחן עבודה.
#איך הוק נפתר
האירוע, ה-matcher והטיפול בהוק עובדים יחד. הדוגמה הבאה חוסמת פקודות מעטפת הרסניות ב-PreToolUse.
ב-macOS וב-Linux, ה-matcher מצמצם לקריאות לכלי Bash, ותנאי if מצמצם עוד לפקודות משנה שמתאימות ל-rm *. הסקריפט block-rm.sh רץ רק כששני המסננים מתאימים:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}הסקריפט קורא את קלט ה-JSON מ-stdin, מחלץ את הפקודה, ומחזיר permissionDecision מסוג "deny" אם היא מכילה rm -rf. שמרו אותו ב-.claude/hooks/block-rm.sh בפרויקט והפכו אותו לבר הרצה עם chmod +x .claude/hooks/block-rm.sh כדי שקלוד קוד יוכל להריץ אותו:
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0 # no decision; normal permission flow applies
fiהסקריפט הזה, כמו דוגמאות Bash אחרות במדריך שמפענחות קלט JSON, משתמש ב-jq. התקינו את jq וודאו שהוא ב-PATH לפני ההרצה.
ב-Windows ה-matcher Bash|PowerShell מכסה גם את כלי PowerShell וגם את Bash. כלל if יחיד מתאים לקריאות של כלי אחד בלבד, ולכן לכל כלי יש טיפול משלו: הראשון מצמצם לפקודות משנה של Bash שמתאימות ל-rm *, והשני לפקודות PowerShell שמתאימות ל-Remove-Item *. שניהם מריצים את אותו סקריפט דרך powershell.exe:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|PowerShell",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"
]
},
{
"type": "command",
"if": "PowerShell(Remove-Item *)",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"
]
}
]
}
]
}
}הדגל -NoProfile מדלג על טעינת פרופיל PowerShell כדי שההוק יתחיל מהר, ו--ExecutionPolicy Bypass מאפשר ל-PowerShell להריץ את קובץ הסקריפט המקומי.
הסקריפט קורא את קלט ה-JSON מ-stdin, מחלץ את הפקודה, ומחזיר permissionDecision מסוג "deny" אם היא מכילה rm -rf או Remove-Item ואחריו -Recurse. שמרו אותו ב-.claude/hooks/block-rm.ps1 בפרויקט:
# .claude/hooks/block-rm.ps1
$callInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
$command = $callInput.tool_input.command
if ($command -match 'rm -rf|Remove-Item.*-Recurse') {
@{
hookSpecificOutput = @{
hookEventName = "PreToolUse"
permissionDecision = "deny"
permissionDecisionReason = "Destructive command blocked by hook"
}
} | ConvertTo-Json
} else {
exit 0 # no decision; normal permission flow applies
}נניח שקלוד קוד מחליט להריץ Bash "rm -rf /tmp/build" מול הגדרת macOS/Linux. כך זה נפתר:
- האירוע נורה:
אירוע
PreToolUseנורה. קלוד קוד שולח את קלט הכלי כ-JSON ב-stdin להוק:
{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }בדיקת matcher: ה-matcher
"Bash"מותאם לשם הכלי, ולכן קבוצת ההוק הזו מופעלת. אם משמיטים את ה-matcher או משתמשים ב-"*", הקבוצה מופעלת בכל מופע של האירוע.בדיקת תנאי
if: התנאי"Bash(rm *)"מתאים כיrm -rf /tmp/buildהיא פקודת משנה שמתאימה ל-rm *, ולכן הטיפול הזה רץ. אם הפקודה הייתהnpm test, הבדיקה הייתה נכשלת ו-block-rm.shלא היה רץ, בלי עלות של יצירת תהליך. השדהifאופציונלי. בלי אותו, כל טיפול בקבוצה שהותאמה רץ.הטיפול בהוק רץ: הסקריפט בודק את הפקודה המלאה, מוצא
rm -rf, ומדפיס החלטה ל-stdout:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}אם הפקודה הייתה וריאנט בטוח יותר כמו rm file.txt, הסקריפט היה מגיע ל-exit 0. יציאה בקוד 0 בלי פלט אומרת שאין להוק החלטה לדווח, והקריאה לכלי ממשיכה בזרימת ההרשאות הרגילה. ההוק יכול לדחות את הקריאה, אבל שתיקה אינה אישור.
- קלוד קוד פועל לפי התוצאה: קלוד קוד קורא את החלטת ה-JSON, חוסם את הקריאה לכלי, ומציג לקלוד את הסיבה.
לפני ההתאמה מוסרות השמות VAR=value שבתחילת פקודת Bash. כך תבניות if נבדקות מול הפקודה:
תבנית if | פקודת Bash | ההוק רץ? | למה |
|---|---|---|---|
Bash(git *) | FOO=bar git push | כן | השמות מובילות מוסרות, ו-git push מתאים |
Bash(git *) | npm test && git push | כן | כל פקודת משנה נבדקת, ו-git push מתאים |
Bash(rm *) | echo $(rm -rf /) | כן | פקודות בתוך $() ובתוך backticks נבדקות, ו-rm -rf / מתאים |
Bash(rm *) | echo $(date) | לא | אין פקודת משנה שמתאימה ל-rm * |
Bash(git push *) | echo $(date) | כן | תבנית שמציינת יותר משם הפקודה מריצה את ההוק בכל מקרה על $(), על backticks, או על $VAR |
מלכודות:
- הוק
commandמדבר רק דרך stdout, stderr וקוד יציאה. הוא לא מפעיל פקודות/ולא קריאות כלים. טקסט ב-additionalContextמוזרק כתזכורת מערכת שקלוד קורא כטקסט רגיל. הוק HTTP מחזיר את התוצאה בגוף התשובה. - יציאה בקוד 0 בלי פלט JSON משאירה את זרימת ההרשאות הרגילה. אובייקט JSON יחיד, בשורה אחת או בכמה שורות ורק עם רווח סביבו, מנותח כפלט JSON. פלט שלא מתחיל ב-
{, או שמתחיל ב-{ולא נגמר ב-}, הוא טקסט רגיל, כולל מערך JSON ומחרוזת מצוטטת. כמה שורות שכל אחת מהן JSON בפני עצמה, הראשונה מתחילה ב-{והאחרונה נגמרת ב-}, הן טקסט רגיל אם אף שורה אינה אובייקט פלט שמגדיר שדה, וכשל פענוח אם אחת כן. ביציאה 0, כשל פענוח או אובייקט שנכשל באימות סכמה הם שגיאה לא חוסמת, וההודעה בתמליל נושאת את הודעת הפענוח או האימות. באירועים שמוסיפים stdout כהקשר, stdout שנכשל בפענוח לא נוסף. - יציאה בקוד 2 חוסמת באירועים שאפשר לחסום, גם אם הודפס JSON עם
"allow". אובייקט JSON שעובר אימות סכמה, עם קוד יציאה שאינו 0 או 2, נחשב הצלחה: קלוד קוד מתעלם מקוד היציאה והשדות לבדם קובעים. עם"onFailure": "block"אותו מקרה נחשב כשל, גם אם ה-JSON מאשר את הפעולה, למשלpermissionDecision: "allow". כדי להחזיר החלטת JSON יוצאים ב-0. בלי JSON תקין ב-stdout, קוד יציאה שאינו 0 או 2 הוא שגיאה לא חוסמת, והפעולה ממשיכה. חריג: ב-Windows בלי Git Bash, הוק מעטפת עלPreToolUseאוPermissionRequestיכול לחסום גם בליonFailure, כמתואר בהמשך. ההודעה בתמליל היא<hook name> hook error. ביציאה 2 על כלי היא יכולה להיראות כמוPreToolUse:Bash hook error: [command]:ואחריו ה-stderr. בשגיאה לא חוסמת היא כוללתFailed with non-blocking status code:ואת השורה הראשונה של stderr. הוק מדיניות צריךexit 2, או"onFailure": "block"על הוקcommandאוhttp. ברירת המחדל שלonFailureהיא"continue"."block"דורש Claude Code v2.1.295 ומעלה. אחרי timeout ההודעה אומרתtimed out. כשל שנספר ל-"block"כולל גם אי הפעלה, timeout, סטטוס HTTP שאינו 2xx או כשל חיבור, ופלט JSON שלא מפוענח או נכשל באימות סכמה. ב-HTTP גם גוף 2xx שאינו ריק ואינו אובייקט JSON. טקסט רגיל ב-stdout של הוק פקודה אינו כשל כשלעצמו. השדה לא חל עלStop,SubagentStop,TaskCompletedו-TeammateIdle, ולא על הוקי פקודה עםasyncאוasyncRewake. ב-PermissionRequest, כשל עם"block"דוחה את הבקשה. יציאה 2 בלי אובייקטdecisionמשאירה את זרימת ההרשאה ללא שינוי. ב-Stop, ב-SubagentStop, ב-TaskCompleted, ובהוקUserPromptSubmitשל תוסף, יציאה 2 בלי stdout כש-stderr אומר שקובץ חסר, למשלNo such file or directory, נחשבת שגיאה לא חוסמת. ב-SessionStart, ב-SubagentStartוב-PostModelSwitch, יציאה 2 מוצגת בתמליל כהודעת שגיאת הוק. קלוד לא רואה אותה, והסשן או סוכן המשנה ממשיכים. ב-SubagentStartההודעה בתמליל של סוכן המשנה, לא בשיחת האב. - ב-Windows בלי Git Bash, הוק פקודה בצורת מעטפת על
PreToolUseאוPermissionRequest, כש-onFailureלא הוגדר, חוסם את הקריאה כשהוא נכשל או מגיע ל-timeout. הודעת השגיאה כוללתblocking because Git Bash wasn't found, so only exit code 0 or 2 counts as this hook's verdict. לפני v2.1.309 ההודעה הייתהblocking because Git Bash wasn't found and this hook doesn't set "shell" to "powershell", so only exit code 0 or 2 counts as its verdict. כל קוד יציאה שאינו 0 או 2 נחשב כשל, גם כשה-JSON לא דוחה את הקריאה. אם הפעולה צריכה להמשיך כשההוק נכשל, מוסיפים"onFailure": "continue". להוק שנכתב ל-Bash: מתקינים Git for Windows, או מגדיריםCLAUDE_CODE_GIT_BASH_PATHאם Git Bash מותקן במקום שקלוד קוד לא מוצא, ומתחילים סשן חדש. צורת מעטפת בליshellרצה ב-PowerShell כש-Git Bash לא מותקן, וסקריפט שנכתב ל-Bash עלול לא לרוץ כפי שנכתב. להרצת.exeאחד, לא קובץ.cmdאו.bat: שמים את התוכנית ב-commandואת הארגומנטים ב-args, ומוסיפים"onFailure": "block". בלי"block", הקריאה יכולה להמשיך כשהתוכנית נכשלת, חסרה, או מגיעה ל-timeout. אם ההוק יוצא 1 כדי להזהיר אבל חייב לחסום בקריסה: עם"onFailure": "continue"הקריאה ממשיכה גם ביציאה 1, באזהרה ובקריסה כאחד. מוסיפים"onFailure": "block"ומסיימים בקוד 0 במקום שמזהירים, כדי שהקריאות האלה ימשיכו. כדי להציג את האזהרה ב-PreToolUseאחרי יציאה 0, מדפיסים אותה ל-stdout בשדהsystemMessageשל אובייקט JSON אחד. להוק שמירה ב-PowerShell שחייב לחסום בכשל: בליonFailureהוא מפסיק לחסום ברגע ש-Git for Windows מותקן וקלוד קוד מוצא את Git Bash. מוסיפים"onFailure": "block"כדי שכשל או timeout יחסמו עם Git Bash ובלעדיו, וגם"shell": "powershell"כדי שההוק ימשיך לרוץ ב-PowerShell ולא ב-Git Bash.shellמתעלמים ממנו כשמוגדרargs, כי צורת exec לא מפעילה מעטפת. אם ההודעה כוללת גםPowerShell started no program, PowerShell הדפיסcommandשמורכב רק ממחרוזות מצוטטות במקום להריץ אותו. התיקונים מפורטים בפתרון הבעיות. - אם הוק פקודה שאינו
asyncמדפיס החלטה ואז מפעיל תהליך רקע בלי לנתב את stdout ואת stderr שלו, קלוד קוד חוסם את הקריאה לכלי. שגיאת ההוק כוללתrefusing to honor the suspect capture. מדפיסים את ההחלטה לפני תהליך הרקע ומנתבים את הפלט שלו, למשל>/dev/null 2>&1 &. אם ההחלטה באה מסקריפט נפרד,|| exit $?מעביר את קוד היציאה שלו להוק, כדי שיציאה 2 עדיין תחסום. דחייה שהתהליך מדפיס אחרי שקלוד קוד הפסיק לקרוא לא חוסמת. קלוד קוד מסיים לקרוא זמן קצר אחרי שתהליך ההוק עצמו יוצא. - timeout של הוק
command,httpאוmcp_toolב-PreToolUseלא חוסם את הקריאה, אלא אםonFailureהוא"block", או במקרה של Windows בלי Git Bash שתואר למעלה. timeout שלPreModelSwitchכן חוסם את החלפת המודל. callback של Agent SDK שחורג מ-timeout ב-PreToolUseחוסם את הקריאה. ברירות המחדל: 600 שניות ל-command,httpו-mcp_tool, 30 ל-prompt, 60 ל-agent. ב-UserPromptSubmit,PreModelSwitchו-PostModelSwitchברירת המחדל של שלושת הסוגים הראשונים יורדת ל-30, וב-MessageDisplayל-10. הוקיSessionEndחולקים תקציב של 1.5 שניות.timeoutארוך יותר בהגדרות מעלה את התקציב עד 60 שניות, לפי ה-timeout הגבוה ביותר בקובצי ההגדרות. הוק בליtimeoutמשלו נשאר על 1.5 שניות. timeout של תוסף לא מעלה את התקציב.CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MSקובע את התקציב במפורש, וגם את ה-timeout של הוק בליtimeoutמשלו. לפני v2.1.268 המשתנה העלה רק את התקציב, והוק בליtimeoutעדיין בוטל אחרי 1.5 שניות. - הוק
UserPromptSubmitמסוגcommand,httpאוmcp_toolשמגיע ל-timeout מבוטל, והפלט, כוללadditionalContext, נזרק. הפרומפט עדיין מגיע לקלוד. התמליל מציג הודעה שמציינת את ההוק, את ה-timeout שנורה, ושהפלט נזרק. כדי לחסום אותו מגדיריםonFailure: "block". callback של Agent SDK שמגיע ל-timeout באירוע הזה חוסם את הפרומפט בהודעה שמציינת את ההוק ואת ה-timeout, והסשן ממשיך. לפני v2.1.208 timeout של callback כזה סיים את התור בשגיאת הרצה. - הוק HTTP לא חוסם דרך סטטוס לבדו. סטטוס שאינו 2xx, כשל חיבור, או גוף 2xx שאינו ריק ואינו אובייקט JSON, הם שגיאה לא חוסמת. הטקסט לא נכנס להקשר. כדי לחסום מחזירים 2xx עם גוף JSON שמכיל את שדות ההחלטה, או מגדירים
onFailure: "block". גוף 2xx ריק שקול ליציאה 0 בלי פלט. כש-allowedHttpHookUrlsמוגדר בכל רמת הגדרות, הוק HTTP רץ רק אם ה-URL שלו מתאים לרשימה הממוזגת, כולל הוק מהגדרות מנוהלות. החלפת משתנים בכותרות דורשת גם אתallowedEnvVarsשל הטיפול עצמו. בלי הרשימה הזו אין החלפה, ומשתנה שאינו בה נמחק למחרוזת ריקה. כש-httpHookAllowedEnvVarsמוגדר בהגדרות, רק המשתנים שברשימה הזו מוזרקים לכותרות. - כלל
ifיחיד מתאים לקריאות של כלי אחד. אין תחביר&&,||או רשימה לצירוף כללים. לכיסוי Bash ו-PowerShell יחד דרושים שני טיפולים, כל אחד עם כללifמשלו. - השדה
ifנבדק רק באירועי כלים:PreToolUse,PostToolUse,PostToolUseFailure,PermissionRequestו-PermissionDenied. באירוע אחר, הוק עםifלא רץ אף פעם. - מסנן
ifאינו מובטח. כשקלוד קוד לא יכול לקבוע אילו פקודות קלט Bash מריץ, ההוק רץ בכל מקרה. לאכיפה קשיחה של אישור או דחייה השתמשו במערכת ההרשאות. - ב-Windows, כשכלי PowerShell מופעל, קלוד מתייחס אליו כמעטפת הראשית ומנתב דרכו פקודות מעטפת. בלי Git Bash הכלי מופעל אוטומטית וקלוד קוד לא רושם את כלי Bash בכלל, ולכן matcher של
Bashלבדו לא נורה שם. להתאמה לשני הכלים כותביםBash|PowerShell. - ערך matcher שמכיל רק אותיות, ספרות,
_,-, רווחים, פסיקים ו-|הוא מחרוזת מדויקת, או רשימת מחרוזות מדויקות מופרדות ב-|או בפסיק, עם רווח אופציונלי סביב המפריד.Edit|Writeו-Edit, Writeשניהם התאמה מדויקת לאחד משני הכלים. כל תו אחר מעביר למסלול ביטוי רגולרי של JavaScript בלי עוגנים: ההתאמה מצליחה בכל מקום בערך.Edit.*מתאים גם ל-Editוגם ל-NotebookEdit. להתאמת המחרוזת כולה עוטפים ב-^וב-$, למשל^Edit$. שם סוכן משנה מתוסף, כמוmy-plugin:reviewer, כולל נקודתיים ולכן נמצא במסלול הרגולרי. להתאמה מדויקת כותבים^my-plugin:reviewer$. "allow"לא עוקף כללי deny ו-ask, כולל רשימות deny מנוהלות. הוא גם לא מוותר על פעולות שאף מצב לא מאשר אוטומטית, ולא על קריאה מנתיב רשת. הוא גם לא מוותר על קריאה לכלי שאפליקציית Agent SDK סימנהalways_ask, אלא אם מצב ההרשאה עצמו מאשר את הקריאה. הוא לא מבטל בקשת אישור לכלי MCP שהשרת סימן ב-_meta["anthropic/requiresUserInteraction"], עם או בליupdatedInput."allow"לבדו לא מספיק ל-AskUserQuestionול-ExitPlanMode: צריך גםupdatedInputעם התשובה.updatedInputמחליף את כל אובייקט הקלט, ולכן כוללים גם שדות שלא משנים. ב-AskUserQuestionמחזירים את מערךquestionsהמקורי ומוסיפיםanswers. בתשובה עםmultiSelectהתוויות מחוברות בפסיקים. כללי הרשאה, וגם הזכאות של פקודת Bash לעבור לרקע אוטומטית, נבדקים מולupdatedInputשההוק החזיר, לא מול הקלט שקלוד שלח. ב-"defer"מתעלמים מ-updatedInputומ-additionalContext."deny"מ-PreToolUseחוסם גם ב-bypassPermissionsועם--dangerously-skip-permissions. אם במחשב אין הגדרות מנוהלות, והסשן לא משתמש בפרטי/loginשל תוכנית Team או Enterprise, מוד יכול לאשר קריאה שהוקPreToolUseחסם. סשן על מפתח API, או עלCLAUDE_CODE_OAUTH_TOKENארוך טווח, לא משתמש ב-login הזה גם כשה-login שמור. במצב תוכנית המוד יכול לאשר קריאה חסומה כזו רק כשהיא לקריאה בלבד, ולכן לא עריכת קובץ. באותו מצב,"allow"מההוק לעריכת קובץ משאיר את אותה בקשה, חסימה או אישור שהעריכה הייתה מקבלת בלי הוק. כדי להרים את המגבלה על אישורי הוק מאשרים את התוכנית או עוברים למצב הרשאה אחר."ask"במצב auto שולח בדרך כלל את הקריאה לדיאלוג, והמסווג לא מחליט אותה. חריג: קריאה שהבדיקות של קלוד קוד עצמן מסמנות, כמו כתיבה לנתיב מוגן או פקודה שמבקשת לרוץ מחוץ לסנדבוקס. שם המסווג יכול לדחות לפני שמופיע דיאלוג, ואם הוא מאשר, הקריאה בדרך כלל עדיין מגיעה לדיאלוג. לקריאתSubagentHandbackאין דיאלוג, כמתואר למעלה ליד מסירת הדוח. הדיאלוג מסומן לפי המקור:[settings],[plugin:<name>], או[skill]. לפני v2.1.211 המסווג יכול היה לאשר פקודת Bash מחוץ לסנדבוקס בלי הדיאלוג שההוק ביקש. ב-PreToolUse,permissionDecisionו-permissionDecisionReasonיושבים בתוךhookSpecificOutput. שדותdecisionו-reasonברמה העליונה מיושנים באירוע הזה."approve"ממופה ל-"allow"ו-"block"ל-"deny".permissionDecisionReasonב-"ask"מוצג בבקשת ההרשאה. בריצת-pבלי מי שיענה, כשקלוד קוד דוחה את הקריאה, קלוד קורא את הסיבה בתוצאת הכלי. ב-"deny"הסיבה מגיעה לקלוד. ב-"allow"וב-"defer"היא נכתבת רק ל-debug log."defer"מכובד רק ב--p. בסשן אינטראקטיבי קלוד קוד רושם אזהרה ומתעלם מתוצאת ההוק. ב--pהכליAskUserQuestionמוצע רק כשיש מארח הרשאות, למשל כלי שמועבר עם--permission-prompt-tool, ולכן מתחילים את הריצה עם מארח כזה."defer"עובד רק כשקלוד מבצע קריאת כלי אחת בתור. כמה קריאות יחד:"defer"מדולג עם אזהרה, והקריאה ממשיכה בזרימת ההרשאות. התהליך יוצא עםstop_reason: "tool_deferred", והקריאה השמורה מופיעה ב-deferred_tool_use. ממשיכים עםclaude -p --resume <session-id>ועם אותו מארח הרשאות. אם הכלי כבר לא זמין, למשל שרת MCP שלא מחובר, היציאה היאstop_reason: "tool_deferred_unavailable"עםis_error: true, לפני שההוק נורה, וה-payload עדיין כלול. כשהכלי לא זמין: אם לא נשלח פרומפט, עם ה-resume או אחריו, הקריאה עדיין ממתינה, וממשיכים שוב כשהכלי זמין כדי שהיא תרוץ ראשונה. אם נשלח פרומפט, עם ה-resume או אחריו, קלוד קוד עונה בלי תוצאת הכלי ומוותר על הקריאה הדחויה. שולחים פרומפט חדש אחרי שהכלי זמין אם עדיין צריך לקרוא לו. אם התשובה שממתינים לה לא מוכנה וממשיכים בלי פרומפט, ההוק יכול להחזיר"defer"שוב. אם נשלח פרומפט וההוק מחזיר"defer"שוב, קלוד קוד עונה בלי תוצאת הכלי ומוותר על הקריאה. ממשיכים בלי פרומפט עד"allow"או"deny". אין timeout. הסשן נשאר על הדיסק עד סריקתcleanupPeriodDays, 30 יום כברירת מחדל. כדי להמשיך סשן דחוי במצב תוכנית מעבירים--permission-prompt-toolיחד עם--resume. זה דורש v2.1.246 ומעלה. בהמשך עם-pקלוד קוד לא משחזר מצב הרשאה שמור אחר, ומתחיל כמוclaude -pחדש, לכן מעבירים שוב--permission-modeאו--dangerously-skip-permissionsאם הסשן הדחוי השתמש בהם. בלי-p,claude --resumeמשחזר את המצב השמור, עם החריגים שבתיעוד של מצב הרשאה בהמשך סשן.- כשכמה הוקי
PreToolUseמחזיריםupdatedInputלאותה קריאה, קלוד קוד לא ממזג את השכתובים ולא מבטיח עם איזה שכתוב הכלי ירוץ. עדיף שלא יותר מהוק אחד ישכתב את הקלט של אותו כלי. ההוקים רצים במקביל. - ב-
PreToolUseבמצב auto,updatedInputבליpermissionDecisionיכול להידחות. השרת בדק את הקלט שקלוד כתב, לא את הקלט שההוק החזיר. בסשן טרמינל אינטראקטיבי ובסשן VS Code על חיבור ישיר ל-API של Anthropic, הדחייה כוללת את הטקסטa hook changed this call's input after auto mode reviewed it. אחרי דחייה כזו קלוד יכול לשלוח שוב את הקריאה ששונתה, כדי שהשרת יבדוק אותה. אם קלוד חוזר בתשובה הבאה בדיוק על אותו קלט ששונה, ההוק עדיין רץ: הוא יכול לדחות את הקריאה או לשלוח אותה לדיאלוג, וכלupdatedInputשהוא מחזיר נזרק, כך שרץ מה שהשרת בדק. בסשנים שמציגים את הטקסט הזה, הוקcommandבקובץ הגדרות או בקובץ ההוקים של תוסף יכול לשנות קריאותBash,PowerShell,Monitor,Edit,Writeו-NotebookEditבלי הדחייה הזו. הדחייה עדיין יכולה להופיע, למשל כשכל הוק שינה קריאה קודמת באותה תשובה של קלוד, או כשפקודת Bash מבקשת לרוץ מחוץ לסנדבוקס. אין צורך לשנות את ההוק בגלל הדחייה, אלא אם קלוד אומר בתשובה שמצב auto לא הצליח לבדוק את הקריאה. updatedToolOutputב-PostToolUseמחליף רק את מה שקלוד רואה. הכלי כבר רץ: קבצים, פקודות ובקשות רשת כבר בוצעו, וטלמטריה רואה את הפלט המקורי. הערך חייב להתאים לצורת הפלט של הכלי. בכלים מובנים, ערך שלא מתאים לסכמה מדולג והפלט המקורי נשאר. פלט כלי MCP עובר בלי אימות סכמה. אפשר גם להחזירupdatedMCPToolOutput, והוא חל רק על כלי MCP.updatedToolOutputעובד לכל הכלים ועדיף.decision: "block"מוסיף אתreasonליד תוצאת הכלי, וקלוד עדיין רואה את הפלט המקורי אלא אם מחליפים אותו.PreToolUseרץ רק כשקלוד קורא לכלי. קובץ שמפנים אליו עם@בפרומפט נוסף כברירת מחדל בלי קריאת כלי, ולכן הוקPreToolUse, כולל matcher שלRead, לא נורה עליו. כדי לחסום נתיבים מסוימים מוסיפים כלל deny שלRead. כדי לכבות את ההוספה מגדיריםCLAUDE_CODE_DISABLE_FILE_MENTIONS=1במעטפת, בהגדרות המשתמש או בהגדרות המנוהלות. קלוד מקבל את הנתיבים עם הערה לקרוא אותם בכלי הקבצים, ואז ההוקים המתאימים רצים. המשתנה דורש v2.1.299 ומעלה, וקלוד קוד מתעלם ממנו בהגדרות פרויקט ובהגדרות מקומיות.- בכלי קבצים, תבנית תיקייה במקטע אחד כמו
Edit(src/**)מתאימה רק לתיקייתsrcבתיקיית העבודה ולקבצים שמתחתיה. לתיקייה בשםsrcבכל עומק כותביםEdit(**/src/**). לפני v2.1.214,Edit(src/**)התאימה לתיקייה בשםsrcבכל עומק מתחת לתיקיית העבודה. - שם כלי MCP הוא
mcp__<server>__<tool>. matcher בלי.*, כמוmcp__memory, הוא התאמה מדויקת ולא מתאים לאף כלי. לכלים של שרת כותביםmcp__memory__.*. שרת שמגיע מתוסף מופיע כ-mcp__plugin_<plugin>_<server>__<tool>, ו-matcher על מפתח השרת לבדו לא נורה. אותו שם מלא נדרש גם בשדהif. בהוק מסוגmcp_tool, שדהserverשל שרת מתוסף הואplugin:<plugin>:<server>, למשלplugin:my-plugin:db, לא מפתח השרת לבדו. מ-v2.1.274 הקלט כוללmcp_serverעםnameו-source. ערכיsourceכולליםplugin,sdk, וטווחי הגדרות כמוuserו-project. מקור שלא מזהים מטופל לפי הרשימה ב-Agent SDK. החלטות אמון לפיsource, לא לפי השם או לפי הקידומתmcp__. - אם מוסיפים שדה
matcherלאירוע בלי תמיכה ב-matcher, הוא מתעלם בשקט. ב-FileChangedוב-StopFailureרק אותיות, ספרות,_ו-|נשארים במסלול ההתאמה המדויקת. מקף, רווח או פסיק מעביר matcher של שני האירועים האלה למסלול ביטוי רגולרי, ורק|מפריד חלופות. התאמה ל-cloud_credential_errorב-StopFailureדורשת v2.1.267 ומעלה. - אם תיקיית העבודה כבר לא קיימת, למשל worktree או תיקייה זמנית שמעטפת אחרת מחקה באמצע הסשן, קלוד קוד מריץ הוקי פקודה מהראשונה שעדיין קיימת מבין אלה: התיקייה שבה הסשן התחיל, שורש הפרויקט, תיקיית הבית, או תיקיית הזמני של המערכת. אזהרה עם שם תיקיית הגיבוי נרשמת ב-debug log. ב-worktree,
${CLAUDE_PROJECT_DIR}נשאר בשורש שבו הסשן התחיל. שדהcwdבקלט ה-JSON הוא שורש ה-worktree אחרי שקלוד נכנס אליו, והתיקייה החדשה אחריcd. - הוק פקודה של
WorktreeCreateמדפיס את הנתיב כשורה האחרונה שאינה ריקה ב-stdout. קלוד קוד מסיר קודי ANSI לפני הקריאה, ולא קורא JSON. כל קוד יציאה שאינו 0 נכשל ביצירה, בלי קשר ל-JSON. הוק HTTP מחזירhookSpecificOutput.worktreePath. נתיב יחסי נפתר מול התיקייה שבה ההוק רץ, וקטעי.ו-..מכווצים. אם התוצאה אינה תיקייה שאפשר להיכנס אליה, הסשן מדפיס שגיאה עם הנתיב ויוצא בקוד 1. נתיב מוחלט עם קטע.או.., או מעבר דרך symlink מתחת לשורש הריפו, נדחה. השגיאה מציינת את הרכיב שנדחה. symlink ששמור בריפו יכול להפנות את ה-worktree אל מחוץ לו. מחזירים נתיב מנורמל שלא עובר דרך symlink בתוך הריפו. לפני v2.1.216 היצירה עקבה אחרי הנתיב בלי הבדיקה הזו. כי ההוק מחליף את התנהגות ברירת המחדל לגמרי,.worktreeincludeלא מעובד. קבצים מקומיים כמו.envמעתיקים מתוך סקריפט ההוק. הקלט כוללname, מזהה slug של ה-worktree, שהמשתמש קבע או שנוצר אוטומטית. הוקWorktreeCreateשל תוסף לא רץ כשמפעילים עם--worktreeוגם--tmux: קלוד קוד יוצר את ה-worktree לפני שהוא טוען תוספים. כדי שההוק ירוץ עם שני הדגלים מגדירים אותו בקובץ הגדרות. לפני v2.1.300 הוקWorktreeCreateשל תוסף לא רץ גם עם--worktreeלבד. ב-WorktreeRemove, יציאה 0 נספרת כהסרה גם אם ההוק לא מחק את התיקייה. יציאה שאינה 0 נכשלת אם התיקייה ב-worktree_pathעדיין קיימת אחר כך, בלי נפילה ל-git. אם ההוק מחק את התיקייה ואז יצא בקוד שאינו 0, זה נספר כהסרה. במחיקת סשן רקע, אם ההוק יוצא בקוד שאינו 0 והתיקייה עדיין קיימת, גם הסשן נשאר. הודעת הסירוב ב-agent view מדווחת איך ההוק הסתיים, למשלexited 1, מצטטת את תחילת ה-stderr, ואומרת אם מחיקה חוזרת של הסשן מסירה את התיקייה בכל זאת. בלי הוקWorktreeRemove, יציאה מסשן אוExitWorktreeמוסרים כמוgit worktree remove --forceworktree ש-git מזהה, ו-worktree ש-git לא מזהה נשאר על הדיסק. בניקויremote-controlבלי הוק כזה ה-worktree נשאר, והטרמינל מציגworktree removal failed, kept: <path>.ExitWorktreeמסרב להסיר worktree שההוק יצר אלא אם הקריאה מעבירהdiscard_changes: true. קלוד קוד מחפש קבצים ששונו עם git, ולכן ב-worktree שאינו checkout של git הוא לא רואה עבודה לא שמורה. במחיקת סשן רקע הנתיב נבדק לפני ההוק: symlink, או מעבר דרך symlink מתחת לשורש הריפו, נדחה. ההוק רץ על worktree שעדיין מכיל קבצים רק אחרי אישור המחיקה ב-agent view.claude rmבמקרה כזה שומר את הסשן ואת ה-worktree. לפני v2.1.216 ההוק רץ על הנתיב השמור בלי הבדיקות האלה. קלוד קוד לא מוחק ענף של worktree שההוק יצר. - ב-Windows, צורת exec (כשמוגדר
args) דורשת ש-commandיזוהה כקובץ הרצה אמיתי, למשל.exe. קובצי.cmdו-.batש-npm, npx ו-eslint מתקינים ב-node_modules/.binאינם כאלה. כדי להריץ אותם בצורת exec קוראים לסקריפט עםnode. כדי להריץ shim בשם.cmdאו.bat, משמיטים אתargsורצים בצורת מעטפת. אםcommandהוא שם בלי מפריד נתיב ומכיל רווח לצדargs, קלוד קוד רושם אזהרה כי אין קובץ הרצה בשם כזה. נתיב מוחלט עם רווחים, כמוC:\Program Files\nodejs\node.exe, הוא קובץ הרצה אחד ולא מפעיל את האזהרה. ב-macOS וב-Linux צורת מעטפת רצה דרךsh -c. ב-Windows זו Git Bash, או PowerShell כש-Git Bash לא מותקן. להוק שמפנה ל-placeholder של נתיב עדיפה צורת exec: כל איבר ב-argsהוא ארגומנט אחד בלי ציטוט. בצורת מעטפת עוטפים כל placeholder בגרשיים כפולים. ב-Windows קלוד קוד מאתרpwsh.exeונופל ל-powershell.exe. השדהshellמפעיל את PowerShell ישירות, בליCLAUDE_CODE_USE_POWERSHELL_TOOL. בצורת מעטפת עם"shell": "powershell", קלוד קוד ממיר${CLAUDE_PROJECT_DIR},${CLAUDE_PLUGIN_ROOT}ו-${CLAUDE_PLUGIN_DATA}ל-${env:NAME}. ההמרה עובדת בתוך גרשיים כפולים, לא בתוך גרשיים בודדים, שבהם PowerShell לא מרחיב משתנים.$CLAUDE_PROJECT_DIRבליenvנקרא כמשתנה מקומי ומקבל$null, וקלוד קוד רושם אזהרה ב-debug log. - בהוק של תוסף,
${user_config.*}מוחלף רק בצורת exec, כמחרוזת פשוטה ב-commandובכל איבר שלargs. הוק בצורת מעטפת שהפקודה שלו מפנה ל-${user_config.*}נכשל בשגיאה במקום לרוץ. כדי לקרוא ערך אפשרות מצורת מעטפת קוראים את$CLAUDE_PLUGIN_OPTION_<KEY>, למשל$CLAUDE_PLUGIN_OPTION_WEBHOOK_URLלאפשרותwebhook_url, או מגדיריםargsועוברים לצורת exec. לפני v2.1.207 גם צורת מעטפת החליפה את${user_config.*}. - כל ההוקים המתאימים רצים במקביל עד הסוף, ואז קלוד קוד ממזג את הפלט. למשל שני הוקי
PreToolUseעלBash: אחד מוסיף את הפקודה ל-~/.claude/bash.logויוצא 0, והשני מריץ סקריפט שיוצא 2 כשהפקודה מכילהrm -rf. עלrm -rf /tmp/buildשניהם רצים במקביל, הדחייה גוברת, והשורה בלוג נכתבת. אם יותר מ-12 מהם הם הוקיpromptאוagent, 12 רצים יחד והשאר מתחילים כשאחד מסתיים. ה-timeoutשל הוק שממתין נספר מרגע שההוק עצמו מתחיל. אותו טיפול שמוגדר ביותר מקובץ הגדרות אחד רץ פעם אחת. עותק של אותו טיפול מתוסף או מכישור נשאר נפרד. ב-PreToolUseההחלטה המגבילה ביותר גוברת, בסדרdeny,defer,ask,allow. ב-PreModelSwitchהסדר הואdeny,ask,allow, בליdefer. דחייה מהוק אחד לא עוצרת הוק אח, ולכן תופעת לוואי כמו כתיבה ללוג עדיין מתרחשת. טקסט מ-additionalContextנשמר מכל הוק ומועבר לקלוד יחד. כל מחרוזת שלadditionalContext,systemMessage,initialUserMessage, וגם stdout בטקסט רגיל, מוגבלת ל-10,000 תווים. כל שדה נמדד בנפרד, ו-stdout בטקסט רגיל נמדד כמכלול. אין הגדרה או משתנה סביבה שמעלים את התקרה. מעל התקרה קלוד קוד שומר קובץ בתיקיית הסשן ומעביר נתיב עם תצוגה מקדימה של עד 2,000 התווים הראשונים, בלי לבקש מקלוד לקרוא את הקובץ. תג<system-reminder>או</system-reminder>בתוך המחרוזת מגיע לקלוד כש-<הוחלף ב-<. כותבים ב-additionalContextעובדות על המצב, לא הוראות מערכת. ניסוח כפקודת מערכת מחוץ לפס יכול להפעיל הגנות הזרקת פרומפט, וקלוד יציג את הטקסט לכם במקום להתייחס אליו כהקשר. באירוע באמצע הסשן, כמוPostToolUseאוUserPromptSubmit, המשך עם--continueאו--resumeמנגן את הטקסט שנשמר ולא מריץ שוב את ההוק על תורות שעברו, ולכן חותמת זמן או SHA של commit מתיישנים. הוקיSessionStartרצים שוב בהמשך, עםsourceשלresume, אוforkאם הוספתם--fork-session. - אחרי שמונה המשכים רצופים מהוקי
Stop, קלוד קוד מסיים את התור בכל זאת. המונה מתאפס כשקלוד קורא לכלי.CLAUDE_CODE_STOP_HOOK_BLOCK_CAPמעלה את התקרה. כדי לא לחסום שוב אחרי שההוק כבר המשיך את התור, קוראים אתstop_hook_activeמהקלט ויוצאים ב-0 כשהואtrue./goalהוא קיצור מובנה להוקStopמבוסס פרומפט ברמת הסשן.additionalContextבהוקStopאוSubagentStopממשיך את השיחה באותן הגנות, כוללstop_hook_activeותקרת שמונה ההמשכים, והתמליל מסמן אותוStop hook feedback, כמשוב של ההוק ולא כשגיאת הוק. הקלט שלStopכוללbackground_tasksו-session_cronsכשאפשר להגיע למרשם המשימות, כדי להבדיל סשן שנגמר מסשן שממתין לעבודה ברקע.descriptionו-promptנחתכים ב-1000 תווים, ועם חיתוך מופיע בתוכם הסמן… [+N chars].commandנחתך ב-1000 תווים.typeהוא תווית כמוshell,subagent,monitor,workflow,teammate,cloud sessionאוMCP task, וסוג לא מוכר נופל למזהה הגולמי.session_cronsמגיע מ-CronCreate, מ-ScheduleWakeupומ-/loop. בשדהrecurring,falseהוא התעוררות חד פעמית ו-trueהוא משימה שחוזרת בכל התאמה. - פרופיל מעטפת שמריץ
echoבלי תנאי מוסיף טקסט לפני ה-JSON, וקלוד קוד מתייחס לכל ה-stdout כטקסט רגיל. Git Bash, וגם הגדרה כמוBASH_ENVשמצביעה על~/.bashrc, עדיין טוענים את הפרופיל. עטפו הודעות כאלה כך שירוצו רק במעטפת אינטראקטיבית ([[ $- == *i* ]]). ב-debug log מחפשיםHook output does not start with {. שדה במקום הלא נכון, למשלpermissionDecisionמחוץ ל-hookSpecificOutput, מנותח ומתעלמים ממנו בלי שגיאה בתמליל. ב-debug log מחפשיםHook JSON output had unrecognized keys.suppressOutputמתקבל אבל לא משנה דבר: stdout של הוק שהצליח לא מוצג בתמליל ונרשם ב-debug log. - תהליך ההוק יורש את סביבת האב, חוץ ממשתני
OTEL_*שקלוד קוד מסיר מכל תהליך בן. כש-CLAUDE_CODE_SUBPROCESS_ENV_SCRUBהוא1, מוסרים גם המשתנים שהוא מנקה. בסשן עם הגדרת HIPAA, קלוד קוד מסיר גם אישורי Anthropic מסביבת ההוק.
#מה אפשר לאוטמט
הוקים מאפשרים להריץ קוד בנקודות מפתח במחזור החיים של קלוד קוד: עיצוב קבצים לאחר עריכה, חסימת פקודות לפני ביצוען, שליחת התראות כשקלוד זקוק לקלט, הזרקת הקשר בתחילת סשן, מעקב אחר שינויי הגדרות, תגובה לשינויי תיקייה וקבצים, אישור הרשאות אוטומטי, והרצת בדיקות ברקע.
דוגמה לייצור היא תוסף security-guidance: הוקים שמריצים סקירת מודל נפרדת ומחזירים את הממצאים לסשן.
#קבלת התראה כשקלוד זקוק לקלט
קבלת התראת שולחן עבודה בכל פעם שקלוד מסיים לעבוד וזקוק לקלט, כדי לעבור למשימות אחרות בלי לבדוק ברציפות את הטרמינל.
הוק זה משתמש באירוע Notification, הנורה כשקלוד ממתין לקלט או להרשאה. יש להוסיף את ההגדרה לקובץ ~/.claude/settings.json בהתאם למערכת ההפעלה:
macOS:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}אם לא מופיעה התראה: הפקודה osascript מנתבת התראות דרך האפליקציה המובנית Script Editor. אם אין לה הרשאת התראות, הפקודה נכשלת בשקט ומערכת macOS לא תבקש אישור. הריצו בטרמינל פעם אחת:
osascript -e 'display notification "test"'דבר לא יופיע עדיין. פתחו את System Settings > Notifications, אתרו את Script Editor ברשימה, והפעילו את Allow Notifications. הריצו שוב את הפקודה כדי לוודא שהתראת הבדיקה מוצגת.
Linux:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
}
]
}
]
}
}אם לא מופיעה התראה: notify-send זקוק ל-daemon של התראות שולחן עבודה, אשר אינו קיים בשרתי headless, בסשנים של SSH וברוב הקונטיינרים. בדקו תחילה את הפקודה ישירות:
notify-send 'Claude Code' 'test'אם הפקודה אינה נמצאת, התקינו את החבילה libnotify-bin ב-Debian וב-Ubuntu, או את המקבילה בהפצה שלכם.
Windows (PowerShell):
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\""
}
]
}
]
}
}אם לא מופיעה תיבת דו שיח: פקודה זו פותחת תיבת הודעה, ולכן היא עשויה להיפתח מאחורי חלון הטרמינל. בדקו את הפקודה תחילה ישירות ב-PowerShell. אם מריצים את קלוד קוד בתוך WSL, הקובץ powershell.exe חייב להיות זמין ב-PATH דרך מנגנון התאימות של Windows.
ערך matcher ריק מפעיל את ההוק בכל סוגי ההתראות. כדי להפעיל את ההוק רק באירועים מסוימים, מגדירים אחד מהערכים הבאים:
| Matcher | מתי זה מופעל |
|---|---|
permission_prompt | קלוד זקוק לאישור לשימוש בכלי או לבקשת רשת של פקודה בסנדבוקס, והבקשה המתינה כ-6 שניות |
idle_prompt | קלוד סיים להגיב לפני כ-60 שניות, לא הוקלד דבר מאז, ואין סוכן ברקע, למשל סוכן משנה, שעדיין רץ. אינו נשלח בזמן המתנה לאיפוס מגבלת שימוש |
auth_success | תהליך האימות הושלם |
elicitation_dialog | שרת MCP פותח טופס elicitation ולא הוקלד דבר במשך כ-6 שניות |
elicitation_url_dialog | שרת MCP מבקש לפתוח כתובת דפדפן ולא הוקלד דבר במשך כ-6 שניות |
elicitation_complete | שרת MCP מדווח שהליך elicitation במצב URL הושלם |
elicitation_response | תשובת elicitation של MCP נשלחת בחזרה לשרת |
agent_needs_input | סשן רקע ממתין לקלט בזמן ש-agent view פתוח בטרמינל. נורה גם כשסשן טרמינל מציג שאלת הגדרת טרמינל של חבר צוות סוכנים, או הודעה של מצב auto על חיוב בקשות המסווג, ולא הוקלד דבר במשך כ-6 שניות |
agent_completed | סשן רקע מסתיים או נכשל. נורה רק כש-agent view פתוח בטרמינל |
quota_auto_resume_fired | קלוד קוד ממשיך את המשימה אחרי שמגבלת שימוש ב-claude.ai עצרה אותה: באיפוס, או מוקדם יותר כשמשהו שעושים בקלוד קוד במהלך ההמתנה, כמו הוספת קרדיט שימוש, שדרוג התוכנית או החלפת מודל, מחזיר זמינות שימוש, עם החריג של הגדרת המודל |
quota_auto_resume_stale | מגבלת שימוש ב-claude.ai התאפסה בזמן שהמחשב ישן יותר מכ-30 דקות. קלוד קוד ממתין ללחיצה על Enter במקום להמשיך. אחרי שינה קצרה יותר הוא ממשיך ונורה quota_auto_resume_fired |
quota_auto_resume_disabled | קלוד קוד מסיים את ההמתנה למגבלת שימוש ב-claude.ai בלי להמשיך את המשימה: autoContinueAtUsageLimit כובה, או שהאיפוס זז ליותר מ-24 שעות במהלך המתנה שקלוד קוד התחיל בעצמו, המשימה שהמשיכה המשיכה לפגוע במגבלה, או שההמשך נחסם לפני שהגיע למודל. אינו נורה בלחיצה על Esc או Ctrl+C, או בבחירה ב-Don't continue automatically |
plugin_notification | מוד מעלה התראה עם $.ui.notify. השדה title בהוק הוא שם המוד, אלא אם המוד קבע כותרת |
ההוק של Notification רץ גם כשהתראות שולחן העבודה כבויות. ההגדרה preferredNotifChannel, כולל הערך notifications_disabled, משנה רק את ערוץ ההתראה, לא את הרצת ההוק.
תזמון permission_prompt בטרמינל שונה מתזמון שלו ב-Claude Desktop, בהרחבת VS Code, ובמארחים שעונים לבקשות הרשאה דרך ה-Agent SDK. בטרמינל מצפים לו אחרי כ-6 שניות בלי הקלדה: הטיימר מתחיל כשהבקשה מופיעה, וכל הקשה דוחה אותו. בסשנים ששולחים את הבקשה ל-callback של canUseTool, מצפים לו כ-6 שניות אחרי שקלוד מבקש הרשאה, בלי דחייה בזמן הקלדה. אם אתם או הוק PermissionRequest עונים מוקדם יותר, permission_prompt לא רץ. בסשנים האלה אפשר לכבות את permission_prompt על ידי CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS=1. לפני v2.1.233, permission_prompt לא נורה בסשנים ששולחים את הבקשה ל-canUseTool.
idle_prompt, elicitation_dialog ו-elicitation_url_dialog חולקים את אותו עיתוי עם התראות שולחן העבודה, ולכן בטרמינל רואים אותם כשנראה שמתרחקים מהמסך. idle_prompt מגיע כ-60 שניות אחרי שקלוד מסיים להגיב, ורק אם לא הוקלד דבר מאז ואין סוכן ברקע, למשל סוכן משנה, שעדיין רץ. בזמן המתנה לאיפוס מגבלת שימוש ב-claude.ai הוא לא נשלח. כשההמתנה נגמרת מעצמה, נורה אחד מסוגי quota_auto_resume_*. לטופס elicitation או לבקשת כתובת דפדפן מצפים אחרי כ-6 שניות בלי הקלדה: הטיימר מתחיל כשהדיאלוג מופיע, וכל הקשה דוחה אותו.
בקשת הרשאה או elicitation שמגיעה בזמן שדיאלוג אחר כבר על המסך שומרת על אותו שער של כ-6 שניות, שנמדד מרגע הגעת הבקשה. ההתראה יכולה להגיע בזמן שהבקשה עדיין ממתינה מאחורי הדיאלוג הפתוח.
quota_auto_resume_fired, quota_auto_resume_stale ו-quota_auto_resume_disabled דורשים v2.1.234 ומעלה. בטרמינל, permission_prompt לבקשת רשת של פקודה בסנדבוקס דורש v2.1.246 ומעלה. agent_needs_input לשאלת הגדרת טרמינל של חבר צוות דורש v2.1.248 ומעלה.
הוקי פקודה ב-macOS וב-Linux רצים בסשן בלי טרמינל שולט, ולא יכולים לפתוח את /dev/tty. ב-Windows אין /dev/tty. כדי להציג התראת שולחן, כותרת חלון או פעמון דרך הטרמינל של קלוד קוד, מחזירים terminalSequence ב-JSON. מותרים רק OSC 0, 1, 2, 9 (כולל 9;4 להתקדמות בשורת המשימות), 99, 777 ו-BEL. הרצף יכול להסתיים ב-BEL או ב-ST. כל דבר מחוץ לרשימה, כולל רצפי צבע, OSC 8, OSC 52 ו-OSC 1337, גורם להתעלמות מהשדה. קלוד קוד כותב את הרצף רק בסשן אינטראקטיבי ורק כשהממשק על המסך. ב--p וב-Agent SDK השדה מדולג. הוא עובד גם באירועים שזורקים systemMessage, כמו Notification ו-StopFailure. הוק פקודה של WorktreeCreate לא יכול להחזיר JSON, כי stdout נקרא כנתיב. הוק HTTP של WorktreeCreate כן מחזיר JSON ויכול לכלול את השדה.
הקלידו /hooks וודאו שההוק מופיע תחת Notification.
#עיצוב קוד אחרי עריכה
הוק PostToolUse עם matcher Edit|Write מריץ Prettier רק אחרי כלי עריכת קבצים. הפקודה שולפת את הנתיב עם jq ומעבירה אותו ל-Prettier. הוסיפו ל-.claude/settings.json בשורש הפרויקט:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}לבדיקה, בקשו מקלוד להוסיף שורה עם מחרוזות בגרשיים בודדים לקובץ JavaScript, ואז פתחו את הקובץ: בהגדרות ברירת המחדל של Prettier ההוק הופך אותן לגרשיים כפולים. כשההוק מצליח, השיחה לא מציגה דבר. הוק PostToolUse עם Edit|Write לא רץ כשפקודת Bash או PowerShell, או תהליך מחוץ לקלוד קוד, משכתבת את אותו קובץ. כדי לראות כל שינוי בקובץ מסוים, כולל השכתובים האלה, השתמשו בהוק FileChanged. לסריקת עץ העבודה פעם בתור, למשל למעקב, הוק Stop יכול להריץ git status --porcelain, שכולל גם קבצים לא במעקב ש-git diff מפספס. לכיסוי לפי קריאה מתאימים גם ל-Bash|PowerShell.
מ-v2.1.269, אחרי פקודת Bash שמשנה קבצים בריפו, PostToolUse יכול לקבל tool_response.bashEditDiff. ההקלטה פועלת בכל מצב הרשאה כשההגדרה bashEditDiffEnabled דולקת. בלי ההגדרה היא פועלת רק במצב auto וב-bypassPermissions, ורק כשקלוד קוד מכוון את קלוד לערוך קבצים דרך Bash. false מכבה את ההקלטה. פקודות רקע ופקודות לקריאה בלבד לא נושאות diff. קבצים ש-Git מתעלם מהם וקבצים ב-submodule לא ברשימה. changedFiles מכיל עד 200 נתיבים מוחלטים, והוא מופיע כש-files מכיל diff או ש-moreFiles גדול מאפס. files מכיל diff של עד 5 קבצים, עם created או deleted כשהפקודה הוסיפה או מחקה קובץ. moreFiles הוא מספר הקבצים שהשתנו בלי diff. unavailable אומר שה-diff חלקי או שלא ניתן היה לקחת אותו. skipped הוא פקודת Git שמזיזה את עץ העבודה, כמו git checkout או git stash, ולכן אין diff. shared אומר שקריאת Bash אחרת, למשל של סוכן משנה, רצה באותו ריפו באותו זמן, ולכן חלק מהשינויים ברשימה עשויים להיות שלה. הרשימה היא best effort ובטא ציבורית: קלוד קוד יכול לפספס שינוי, לכלול קובץ שתהליך אחר שינה באותו זמן, או לעצור בגבול גודל. השדה עשוי להשתנות. משתמשים בה כדי למצוא מה לבדוק, לא כדי לאכוף מדיניות.
מ-v2.1.236 אותו אירוע יכול להחזיר classifierContext: הערה קצרה למסווג של מצב auto, לא לקלוד. התקרה המשותפת לקריאה אחת היא 2,000 תווים, והשאר נחתך. הערה מהגדרות, מתוסף, מכישור או מ-frontmatter של סוכן נחשבת הקשר לא מאומת. callback של Agent SDK בתוך התהליך, בזמן הסשן החי, יכול להעביר אמירת משתמש ככוונה, רק בגבול שאישור מהודעה שלכם היה מרים. אחרי המשך סשן ההערות המשוחזרות לא מאומתות. כששתי הקבוצות מעירות על אותה קריאה, ההערה המשולבת נחשבת לא מאומתת. הוק ברקע לא מעביר את השדה. קריאה שהמסווג לא רושם, כמו קריאת קובץ או חיפוש, נזרקת. כשההערה מתארת פלט שאתם מחליפים ב-updatedToolOutput, מחזירים את שני השדות באותה תשובה. קלוד קוד זורק את ההערה אם השכתוב נדחה או אם שכתוב של הוק אחר מחליף אותו. הערה בלי שכתוב נמסרת גם כשהוק אחר משכתב את הפלט. אל תעתיקו לשדה פלט כלי לא מהימן.
דוגמאות Bash במדריך הזה משתמשות ב-jq. התקינו עם brew install jq ב-macOS, או apt-get install jq ב-Debian וב-Ubuntu.
#חסימת עריכה של קבצים מוגנים
הוק PreToolUse על Edit|Write יכול לחסום נתיבים כמו .env, package-lock.json ו-.git/ ביציאה בקוד 2, וקלוד מקבל את הסיבה.
- יצירת הסקריפט:
שמרו ב-
.claude/hooks/protect-files.sh:
#!/bin/bash
# protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Normalize Windows backslash separators so the patterns below match
FILE_PATH="${FILE_PATH//\\//}"
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2
fi
done
exit 0- הרשאת הרצה ב-macOS וב-Linux:
chmod +x .claude/hooks/protect-files.sh- רישום ההוק ב-
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}- בדיקה:
בקשו מקלוד להוסיף הערה לקובץ
.env. קלוד קוד חוסם את העריכה לפני שהיא רצה, ומעביר לקלוד את הודעתBlocked:.
נתיב של Write, Edit ו-Read מגיע תמיד כמוחלט. ב-Windows המפרידים הם לוכסנים הפוכים, גם כשההוק רץ תחת Git Bash. בלי הנרמול בשורה FILE_PATH="${FILE_PATH//\\//}", השוואה עם / לא תתאים והקריאה תמשיך. כי הנתיב מוחלט, לא מעגנים תבנית ב-^. אחרי הנרמול משווים מקטע כמו /src/.
#הזרקת הקשר מחדש אחרי דחיסה
הוק SessionStart עם matcher compact מחזיר הקשר אחרי כל דחיסה. קלוד קוד מוסיף stdout בטקסט רגיל להקשר של קלוד. הוסיפו ל-.claude/settings.json בשורש הפרויקט:
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"
}
]
}
]
}
}אפשר להחליף את echo בפקודה דינמית, למשל git log --oneline -5. להקשר סטטי בכל התחלת סשן עדיף CLAUDE.md. למשתני סביבה כותבים ל-CLAUDE_ENV_FILE מהוקי SessionStart, Setup, CwdChanged ו-FileChanged. ב-additionalContext כותבים עובדות, למשל איזו פקודת בדיקה חלה, לא הוראות מערכת. ניסוח כפקודה מחוץ לפס יכול להפעיל הגנות הזרקת פרומפט, וקלוד יציג את הטקסט במקום להתייחס אליו כהקשר.
ערכי matcher של SessionStart: startup, resume, clear, compact, fork. fork הוא סשן חדש שמפוצל מקיים: --fork-session עם --resume או --continue, העתק הרקע של /fork, /branch, או שיחה שמעבירים לרקע. לפני v2.1.214 סשן מפוצל דיווח resume. sessionTitle קובע כותרת כש-source הוא startup, resume או fork. הוק שמחזיר sessionTitle יכול לקרוא קודם את session_title בקלט כדי לא לדרוס כותרת מותאמת קיימת. כותרת שנוצרה אוטומטית לסשן בלי שם אינה כותרת מותאמת ולא מופיעה ב-session_title. agent_type מופיע בקלט כשהסשן התחיל עם claude --agent <name>. model יכול להיעדר, למשל אחרי /clear או בשחזור שיחה. initialUserMessage הופך להודעת המשתמש הראשונה ב--p, גם בלי פרומפט בשורת הפקודה. פרומפט שכן הועבר מגיע בתור הבא. אם הוק SessionStart של תוסף מספק initialUserMessage או sessionTitle, מתקינים את התוסף לפני תחילת הסשן. קלוד קוד מתעלם משני השדות אם התוסף מסיים להתקין אחרי שהוקי SessionStart רצו. reloadSkills: true סורק מחדש כישורים ופקודות אחרי שהוקי SessionStart מסתיימים, כדי שכישור שההוק כתב ל-~/.claude/skills/ או ל-.claude/skills/ יהיה זמין באותו סשן. כש-source הוא resume או fork ויש בתמליל לפחות תשובה אחת, מ-v2.1.251 הקלט כולל seconds_since_last_response, context_tokens, prompt_cache_likely_expired ו-estimated_cache_write_usd.
#מעקב אחרי שינויי הגדרות
ConfigChange נורה כשתהליך חיצוני או עורך משנה קובץ הגדרות או כישור במהלך הסשן. הדוגמה מוסיפה שורה ללוג. הוסיפו ל-~/.claude/settings.json:
{
"hooks": {
"ConfigChange": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "jq -c '{timestamp: now | todate, source: .source, file: .file_path}' >> ~/claude-config-audit.log"
}
]
}
]
}
}ה-matcher מסנן לפי מקור: user_settings, project_settings, local_settings, policy_settings, או skills. כדי למנוע את החלת השינוי, יוצאים בקוד 2 או מחזירים {"decision": "block"}. שינוי policy_settings נרשם אבל החלטת חסימה מתעלמת ממנו. חסימה לא מציגה הודעה לכם או לקלוד, גם דרך reason וגם דרך stderr ביציאה 2. השדה reason מתקבל אבל לא מוצג. קלוד קוד כותב שורה ל-debug log.
קלוד קוד מריץ את ההוק על מדיניות מנוהלת רק כשמשתנים managed-settings.json או קובץ ב-managed-settings.d/. הוא לא מריץ ConfigChange כשמגיעות או מתרעננות הגדרות מנוהלות מהשרת, על שינוי בהעדפות מנוהלות של macOS, או על מדיניות ברישום של Windows. ב-WSL עם wslInheritsWindowsSettings הוא גם מחיל קובץ הגדרות מנוהלות מצד Windows בסבב המדיניות בלי להריץ את ההוק.
לאימות, ערכו קובץ הגדרות מעורך אחר בזמן שסשן רץ, ופתחו את ~/claude-config-audit.log: שורת JSON אחת לכל שינוי, עם חותמת זמן, מקור ונתיב.
#טעינת סביבה כשתיקייה או קבצים משתנים
כלי Bash של קלוד לא קולט לבד שינויי direnv. SessionStart טוען את המשתנים של תיקיית ההפעלה, ו-CwdChanged טוען מחדש אחרי cd. שניהם כותבים ל-CLAUDE_ENV_FILE, שקלוד קוד מריץ כפתיח לפני כל פקודת Bash. הוסיפו ל-~/.claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
],
"CwdChanged": [
{
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}הריצו direnv allow פעם אחת בכל תיקייה עם .envrc. עם devbox או nix, אותה תבנית עובדת עם devbox shellenv או devbox global shellenv במקום direnv export bash.
פקודות PowerShell מקבלות משתנים מ-CLAUDE_ENV_FILE החל מ-Claude Code v2.1.296. PowerShell לא מריץ את הקובץ: קלוד קוד קורא ממנו השמות ומעתיק אותן לסביבת הפקודה. זה קורה רק אם כל שורה בקובץ, מכל מה שההוקים בסשן כתבו ומכל סקריפט שהגדרתם אליו את CLAUDE_ENV_FILE לפני ההפעלה, היא שורה ריקה, הערת #, או השמה אחת בתחילת השורה. השמה מותרת היא export NAME=value, declare -x NAME=value, או NAME=value, עם ערך ש-Bash היה משתמש בו בדיוק כפי שהוא כתוב: טקסט לא מצוטט שמכיל רק אותיות, ספרות והתווים _ @ % + = : , . / -, טקסט בגרשיים בודדים, וטקסט בגרשיים כפולים שבו $, backtick או " מוברחים בלוכסן הפוך. הפלט של direnv export bash כולל מחרוזות $'...', וגם שורה כמו export PATH="$PATH:./node_modules/.bin" עם $PATH לא מוברח היא מסוג אחר. במקרה כזה פקודות PowerShell לא מקבלות אף משתנה, ו-claude --debug רושם Session environment is not all plain assignments. פקודות Bash עדיין מקבלות את כולם, כי Bash מריץ את הקובץ כקוד מעטפת. ב-Windows, פקודת PowerShell גם לא מקבלת משתנה שהערך שלו מכיל / או \. פקודת PowerShell בסנדבוקס לא מקבלת את המשתנים. הוק שרץ ב-PowerShell, דרך "shell": "powershell" או כברירת מחדל ב-Windows בלי Git Bash, לא רואה את CLAUDE_ENV_FILE.
לקבצים מסוימים, FileChanged עם matcher של שמות קבצים מופרדים ב-|. בבניית רשימת המעקב קלוד קוד מפצל את הערך לשמות מילוליים, לא לביטוי רגולרי. אותו ערך גם מסנן אילו קבוצות הוק רצות כשקובץ משתנה. הדוגמה עוקבת אחרי .envrc ו-.env בתיקיית העבודה:
{
"hooks": {
"FileChanged": [
{
"matcher": ".envrc|.env",
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}הקלט כולל file_path מוחלט ו-event: change לקובץ ששונה, add לקובץ שנוצר, או unlink לקובץ שנמחק. matcher "*" מתאים לכל קובץ, אבל קלוד קוד רושם אותו ברשימת המעקב כקובץ מילולי בשם *. כדי לטפל בכל קובץ במעקב בלי להוסיף שם לרשימה, משמיטים את ה-matcher. הוק שמשכתב את הקובץ במעקב נורה שוב אחרי כל שכתוב. כלי שמשכתב את הקובץ גם כשאין שינוי, למשל perl -i בלי החלפה בפועל, יוצר לולאה. תנאי השמירה בודק שהשינוי עדיין בקובץ, ורק אז משכתב. הוק CwdChanged או FileChanged יכול להחזיר watchPaths, מערך נתיבים מוחלטים שמחליף את רשימת המעקב הדינמית. נתיבים מה-matcher נשארים במעקב. מערך ריק מנקה את הרשימה הדינמית. גם הוק SessionStart יכול להחזיר watchPaths. ה-watcher מתחיל רק כשמשהו מציין קובץ למעקב: קבוצת FileChanged שה-matcher שלה נותן שם לקובץ אחד לפחות, או watchPaths מ-SessionStart או מ-CwdChanged. הקבוצה שמטפלת בנתיבים דינמיים משמיטה את ה-matcher, וכך היא רצה על כל קובץ במעקב בלי להוסיף שם לרשימה. בסשן אינטראקטיבי systemMessage מהוק CwdChanged או FileChanged מופיע כהתראת טרמינל קצרה. ההודעה לא מגיעה לזרם ההודעות של ה-SDK. קלוד קוד קורא watchPaths ו-systemMessage וזורק את continue.
משתנים שנכתבים ל-CLAUDE_ENV_FILE מ-CwdChanged או מ-FileChanged נשארים עד אירוע CwdChanged הבא, ואז קלוד קוד מנקה אותם. ל-CwdChanged אין matcher. הקלט כולל old_cwd ו-new_cwd.
#אישור אוטומטי של בקשות הרשאה
הוק PermissionRequest רץ כשקלוד קוד עומד לבקש הרשאה, או כשהוא היה דוחה אוטומטית קריאה שאי אפשר להציג עבורה דיאלוג. החזרת "behavior": "allow" עונה במקומכם. הדוגמה מאשרת רק את ExitPlanMode. הוסיפו ל-~/.claude/settings.json:
{
"hooks": {
"PermissionRequest": [
{
"matcher": "ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
}
]
}
]
}
}כשההוק מאשר, קלוד קוד יוצא ממצב תוכנית ומחזיר את מצב ההרשאה שהיה פעיל לפני הכניסה אליו. בתמליל מופיע "Allowed by PermissionRequest hook". הנתיב דרך ההוק משאיר את השיחה הנוכחית: הוא לא מנקה הקשר ופותח סשן מימוש חדש כמו שהדיאלוג יכול.
בקריאת ExitPlanMode הקלט שהמודל שולח בדרך כלל ריק, כי התוכנית נכתבת לקובץ לפני הקריאה. קלוד קוד מזריק plan ו-planFilePath לפני שהקלט מגיע להוקים. ב-PostToolUse קוראים את התוכנית מ-tool_response.plan. באותו אובייקט יש גם filePath ודגלי סטטוס פנימיים. השדה allowedPrompts מתקבל ומתעלמים ממנו. לפני v2.1.205 הוא נשא הרשאות מבוססות פרומפט שקלוד ביקש כדי לממש את התוכנית.
בסשן שלא יכול להציג דיאלוג, למשל סוכן משנה ברקע במצב -p, ההוקים עדיין רצים מחוץ למצב dontAsk. אם אף הוק לא מחזיר החלטה ואין מי שיענה, הקריאה נדחית. כשהקריאה מגיעה ל---permission-prompt-tool או ל-callback של canUseTool, ההוקים רצים לצד המארח, ומי שמחליט ראשון קובע. קלוד קוד לא מריץ PermissionRequest על בקשת רשת של פקודה בסנדבוקס.
כדי לקבוע מצב מסוים, מוסיפים updatedPermissions עם רשומת setMode. destination: "session" חל רק על הסשן הנוכחי. ב-setMode הערך manual הוא כינוי ל-default:
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedPermissions": [
{ "type": "setMode", "mode": "acceptEdits", "destination": "session" }
]
}
}
}updatedInput בתוך decision חל רק על "allow", מחליף את כל אובייקט הקלט, ונבדק מחדש מול כללי deny ו-ask. bypassPermissions חל רק אם הסשן הופעל כשמצב העקיפה כבר זמין: --dangerously-skip-permissions, --permission-mode bypassPermissions, --allow-dangerously-skip-permissions, או permissions.defaultMode: "bypassPermissions" בהגדרות משתמש, ב---settings, או בהגדרות מנוהלות. הוא לא חל אם permissions.disableBypassPermissionsMode מכבה את המצב, או אם הסשן התחיל במצב מוגבל. קלוד קוד לא שומר אותו כ-defaultMode.
matcher של .*, או matcher ריק, יאשר כל בקשת הרשאה לכלי, כולל כתיבת קבצים ופקודות מעטפת. "allow" לא עוקף כלל deny תואם. מערך permission_suggestions בקלט אינו רשימת האפשרויות בדיאלוג: יש דיאלוגים שלא קוראים אותו, דיאלוג יכול להסתיר הצעה שנשארת במערך, למשל כש-allowManagedPermissionRulesOnly מסתיר שמירת כללים, ויכול להציע אפשרות בלי רשומה, כמו מעבר למצב auto. בדחייה, message אומר לקלוד למה נדחתה ההרשאה, ו-interrupt: true עוצר את קלוד. יציאה 2 בלי אובייקט decision משאירה את הזרימה ללא שינוי.
#הרצת בדיקות ברקע
"async": true קיים רק בהוק type: "command". ההוק רץ ברקע וקלוד ממשיך. שדות כמו decision, permissionDecision ו-continue לא משפיעים, כי הפעולה כבר הסתיימה. קלוד קוד לא אוכף timeout על הוק async. על asyncRewake הוא כן אוכף timeout. הוק asyncRewake שיוצא בקוד 2 מעיר את קלוד גם כשהסשן במנוחה, ומציג את stderr, או את stdout אם stderr ריק, כתזכורת מערכת.
שמרו את הסקריפט ב-.claude/hooks/run-tests-async.sh והפכו אותו לבר הרצה:
#!/bin/bash
# run-tests-async.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
exit 0
fi
RESULT=$(npm test 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
MSG="Tests passed after editing $FILE_PATH"
else
MSG="Tests failed after editing $FILE_PATH: $RESULT"
fi
jq -nc --arg msg "$MSG" '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $msg}}'הוסיפו ל-.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",
"args": [],
"async": true
}
]
}
]
}
}אחרי שהתהליך יוצא, additionalContext ו-systemMessage מגיעים לקלוד בתור השיחה הבא. בניגוד ל-systemMessage של הוק סינכרוני, שני השדות לא מוצגים לכם. אם הסשן במנוחה, התשובה ממתינה עד האינטראקציה הבאה, חוץ מ-asyncRewake שיוצא בקוד 2. ב-claude -p, הוק async שעדיין רץ בסיום נהרג ומסומן cancelled. עבודה שצריכה להמשיך אחרי הסשן צריכה תהליך מנותק לגמרי. התראות סיום של הוק async מוסתרות כברירת מחדל. Ctrl+O או --verbose מציגים אותן. מדפיסים את אובייקט ה-JSON לבד ב-stdout, ואז הוא יכול להיפרס על כמה שורות, למשל פלט jq מעוצב, החל מ-Claude Code v2.1.295. אפשר גם להדפיס טקסט אחר אם התשובה יושבת לבד בשורה אחת, למשל עם jq -c. שדה שהטיפוס שלו שגוי, למשל systemMessage שאינו מחרוזת, נזרק ולא נמסר. --debug מציג אזהרה עם שם כל שדה שנזרק. לפני v2.1.202, JSON לא תקין מהוק async יכול היה לקרוס את הסשן, והקריסה חזרה בכל המשך של הסשן. הוק SessionStart עם async רץ בכל פעם שהאירוע נורה. קלוד קוד מדלג על הפלט כשטקסט additionalContext ו-systemMessage זהה למה שקלוד קיבל לאחרונה מאותו הוק, אלא אם הוק SessionStart סינכרוני הוסיף טקסט אחר מאז. הטקסט נמסר כשהוא משתנה, ושוב בהרצה הבאה אחרי דחיסה או /clear. לפני v2.1.295 אותו טקסט נוסף שוב לשיחה בכל המשך.
כדי לאתר הוק איטי, מ-v2.1.296 רשומת Hooks: ב-debug log מציינת את האירוע, נקודתיים, הערך שההתאמה נעשתה מולו, ואז את הפקודה בסוגריים מרובעים, את התוסף אם ההוק הגיע מתוסף, סטטוס ומשך. לדוגמה: Hooks: PostToolUse:Write [.claude/hooks/log-write.sh] finished with status 0 (31ms). ריצה יכולה להסתיים גם כ-timed out after <N>ms, cancelled, moved to the background או failed to start. באירועים כמו Notification, SessionEnd ו-PreCompact הרשומה היא completed with status בלי משך. claude --debug כותב ל-~/.claude/debug/<session-id>.txt ולא לטרמינל. claude --debug-file <path> קובע את הנתיב. באמצע סשן, /debug מפעיל את הלוג ומראה את הנתיב. לפירוט התאמת matcher מגדירים CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose.
#פתרון בעיות
#ההוק לא נורה
- הריצו
/hooksוודאו שההוק מופיע תחת האירוע הנכון. הוק ב-frontmatter של כישור לא מופיע עד שמפעילים את הכישור, או עד שקלוד מפעיל אותו, ואז מריצים/hooksשוב. אם הוא עדיין חסר, בדקו frontmatter שלא נפרס, או את נעילתstrictPluginOnlyCustomizationואת מגבלת 1,024 ההוקים ו-16,384 תווי ה-matcher שתוארו בהגדרה. - ודאו שה-matcher תואם לשם הכלי בדיוק. ההתאמה רגישה לאותיות.
- ודאו שאתם מפעילים את האירוע הנכון:
PreToolUseלפני הביצוע,PostToolUseאחריו.PermissionRequestנורה כשקלוד קוד עומד לבקש הרשאה. - ב-Windows בלי Git Bash, matcher של
Bashלבדו לא נורה, כי כלי Bash לא נרשם. כותביםBash|PowerShell.
#שגיאת הוק בפלט
הודעה כמו PreToolUse hook error בתמליל:
- הריצו את הסקריפט על JSON לדוגמה ובדקו את קוד היציאה:
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
echo $?- אם מופיע
command not found, השתמשו בנתיב מוחלט או ב-${CLAUDE_PROJECT_DIR}. כדי לדלג על ציטוט של המעטפת, הוסיפו"args": []ועברו לצורת exec. - אם מופיע
jq: command not found, התקינו אתjq, או פרסרו JSON ב-Python או ב-Node.js. - אם ההודעה כוללת
blocking because Git Bash wasn't found, אתם ב-Windows, קלוד קוד לא מצא את Git Bash, וההוק שנכשל או שהגיע ל-timeout חסם את הקריאה. מ-v2.1.309 הניסוח הואblocking because Git Bash wasn't found, so only exit code 0 or 2 counts as this hook's verdict. התיקונים מפורטים במלכודות למעלה. - אם ההודעה כוללת גם
PowerShell started no program, PowerShell הדפיסcommandשמורכב רק ממחרוזות מצוטטות, למשל"command": "\"C:\\Tools\\guard.exe\"", במקום להריץ אותו. כדי להדפיס טקסט כמו JSON:"shell": "powershell"וההוק יוצא 0. כדי להריץ קובץ.ps1,.cmdאו.bat, או.exeשהנתיב שלו מכיל משתנה:"shell": "powershell"ו-&לפני הנתיב המצוטט. משאירים את$HOMEכפי שהוא, וכותבים משתני סביבה כמו$env:CLAUDE_PROJECT_DIR. כדי להריץ.exeאחד בנתיב בלי$: מסירים את הגרשיים הפנימיים מ-command, ואז מיישמים את התיקון להרצת.exeאחד, עם"args": []אם אין ארגומנטים. לסקריפט שנכתב ל-Bash מיישמים את תיקון Bash שלמעלה. - הודעת אימות JSON או הודעת פענוח מופיעה גם ביציאה 0: stdout נראה כמו אובייקט אבל נכשל בסכמה, או נראה כמו אובייקט ולא היה JSON תקין. בונים את הגוף עם
jq, לא בשרשור מחרוזות. - אם הסקריפט לא רץ בכלל, הריצו
chmod +x ./my-hook.sh.
#/hooks לא מציג הוקים
- עריכות נקלטו בדרך כלל אוטומטית. אם אחרי כמה שניות אין שינוי, ה-watcher פספס: מתחילים את הסשן מחדש.
- ה-JSON חייב להיות תקין: פסיק עודף והערות לא מותרים.
- קובץ הפרויקט הוא
.claude/settings.json, והקובץ הגלובלי הוא~/.claude/settings.json. - אם התפריט מציג
Only hooks from managed settings run here, הארגון הגדירallowManagedHooksOnly. - הוקים ב-frontmatter של כישור שלא הופעל בסשן לא מופיעים. מפעילים את הכישור ומריצים
/hooksשוב. אם הרשימה עדיין ריקה מהם, בדקו את נעילת התוספים ואת מגבלת ההוקים שלמעלה.
#הוק Stop הגיע לתקרה
אחרי שמונה חסימות רצופות בלי קריאת כלי, קלוד קוד מסיים את התור בכל זאת. בודקים את stop_hook_active ויוצאים מוקדם:
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # Allow Claude to stop
fi
# ... rest of your hook logicאם באמת צריך יותר משמונה המשכים, מעלים את CLAUDE_CODE_STOP_HOOK_BLOCK_CAP.
#JSON של ההוק לא משפיע
- טקסט לפני ה-JSON, בדרך כלל
echoלא מותנה בפרופיל המעטפת, גורם לקלוד קוד לקרוא את הפלט כטקסט רגיל. ב-debug log מחפשיםHook output does not start with {. עוטפים את ה-echoכך שירץ רק במעטפת אינטראקטיבית:[[ $- == *i* ]]. - שדה ברמה הלא נכונה, למשל
permissionDecisionאוadditionalContextמחוץ ל-hookSpecificOutput, מנותח ומתעלמים ממנו בלי שגיאה בתמליל. ב-debug log מחפשיםHook JSON output had unrecognized keys.
#מה ההוק עשה
Ctrl+O פותח את תצוגת התמליל:
- הצלחה: אין שורה, אלא אם ה-JSON מציג משהו כמו
systemMessageאו משוב של הוקStop. מאשרים לפי האפקט, למשל קובץ שעוצב מחדש. - שגיאה חוסמת: ברוב האירועים רואים את הודעת החסימה. ב-
ConfigChangeוב-Elicitationאין הודעה. - שגיאה לא חוסמת: הודעת
<hook name> hook errorעם הסבר קצר, למשל השורה הראשונה של stderr אחריFailed with non-blocking status code:, או הודעת אימות או פענוח. הפעולה המשיכה.
לפרטי הרצה מלאים, כולל קוד יציאה, stdout ו-stderr, קוראים את ה-debug log. claude --debug-file /tmp/claude.log כותב לנתיב ידוע. בלי הדגל, /debug באמצע הסשן מפעיל את הלוג ומראה את הנתיב.