הבעיה: הסוכן הפסיק לשאול
אם בזמן האחרון קלוד מרגיש לכם פחות מדויק, זה לא אתם וזה לא באג. השתנתה לו ההתנהגות: כשהוא לא בטוח מה בדיוק ביקשתם, הוא כבר לא עוצר לשאול. הוא מנחש בביטחון וממשיך.
הסיבה מבנית. מודלים מדורגים לפי מבחנים אוטומטיים, ובמבחנים כאלה תשובה בטוחה ושגויה מקבלת יותר נקודות משאלת הבהרה. זה כמו מועמד בריאיון עבודה שאף פעם לא אומר “אני לא יודע”. הוא נשמע חד, אבל חלק מהתשובות שלו הן ניחוש.
התוצאה בקוד: הסוכן ממציא הנחות, מרחיב לכיוונים שלא ביקשתם, ומחזיר תשובות ארוכות שמכסות כמה אפשרויות במקום לשאול שאלה אחת קצרה.
למה הוראה בקובץ CLAUDE.md לא פותרת את זה
האינסטינקט הראשון הוא לכתוב בקובץ ההוראות משהו כמו “תשאל לפני שאתה מניח”. זה עובד בהתחלה, ומפסיק לעבוד אחרי כמה הודעות.
קובץ ההוראות הוא טקסט שנכנס לתחילת השיחה. ככל שהשיחה מתארכת, נערמים מעליו קוד, פלטים של כלים ותשובות, וההוראה מתפוגגת מול כל מה שקרוב יותר לסוף. היא בקשה, לא מנגנון.
Hook הוא מנגנון. זה קוד שרץ בצד שלכם, לא בתוך הפרומפט. הוא לא מושפע מאורך השיחה, הוא לא נשכח, והוא רץ בכל פעם מחדש.
מה זה Stop Hook
Hooks הם יכולת רשמית של Claude Code: פקודות משלכם שהכלי מריץ אוטומטית בנקודות מוגדרות בחיי השיחה. יש כמה סוגים, לפי הרגע שבו הם נורים.
ה-Stop hook נורה בדיוק ברגע שבו הסוכן סיים לענות ולפני שהתור נסגר. זו נקודת החניה האחרונה, ולכן זה המקום היחיד שממנו אפשר להסתכל על התשובה המוגמרת ולהחליט אם לאשר אותה או להחזיר אותה לכתיבה מחדש.
מה ה-hook מקבל
Claude Code מריץ את הפקודה שלכם ושולח לה JSON ב-stdin. השדות שרלוונטיים לנו:
| שדה | מה יש בו |
|---|---|
last_assistant_message | הטקסט המלא של התשובה שהסוכן הרגע סיים לכתוב. זה מה שאנחנו סופרים. |
prompt_id | מזהה ייחודי של התור הנוכחי. משמש אותנו כדי לא להיתקע בלולאה. |
session_id | מזהה השיחה. גיבוי למקרה שאין prompt_id. |
cwd | התיקייה שממנה רצה השיחה. שימושי אם רוצים כללים שונים לפרויקטים שונים. |
stop_reason | למה הסוכן עצר, למשל end_turn. |
איך ה-hook עונה
שתי דרכים. הפשוטה: לצאת עם קוד יציאה 2 ולכתוב את הסיבה ל-stderr. המדויקת יותר, וזו שנשתמש בה: לצאת עם 0 ולהדפיס JSON ל-stdout.
| קוד יציאה | מה קורה |
|---|---|
0 | הצלחה. אם הודפס JSON ל-stdout, Claude Code קורא ממנו את ההחלטה. בלי JSON, התשובה מאושרת והתור נסגר. |
2 | חסימה. התור לא נסגר, הסוכן ממשיך לעבוד, ומה שכתבתם ל-stderr הופך לסיבת החסימה. |
אחר | שגיאה לא חוסמת. התור נסגר כרגיל ומוצגת הודעת שגיאה קטנה. |
ה-JSON שמחזיר חסימה נראה כך, וה-reason הוא ההוראה שהסוכן מקבל כדי לתקן את עצמו:
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"decision": "block",
"reason": "התשובה ארוכה מדי. קצר אותה, או שאל שאלת הבהרה."
}
}
שלב 1: יוצרים את קובץ ה-hook
צרו תיקייה בשם hooks בתוך תיקיית ההגדרות של Claude Code בבית שלכם, ובתוכה קובץ בשם word-limit.js:
Windows: C:\Users\YOUR_NAME\.claude\hooks\word-limit.js
Mac/Linux: ~/.claude/hooks/word-limit.js
הקוד כתוב ב-Node.js בלי ספריות חיצוניות, כי Node כבר מותקן אצל כל מי שמריץ Claude Code. אין מה להתקין.
זה הקוד המלא. העתיקו אותו כמו שהוא:
#!/usr/bin/env node
/**
* Stop hook - word limit
* חוסם כל תשובה של הסוכן שעוברת את מגבלת המילים,
* ומחזיר לו הוראה לקצר או לשאול שאלה במקום לנחש.
*/
const fs = require("fs");
const os = require("os");
const path = require("path");
const LIMIT = 150; // מגבלת המילים
const MAX_BLOCKS = 2; // כמה פעמים מותר לחסום את אותו תור
const IGNORE_CODE = true; // לא לספור בלוקים של קוד
let raw = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => (raw += chunk));
process.stdin.on("end", () => {
let input;
try {
input = JSON.parse(raw);
} catch (e) {
process.exit(0); // קלט לא תקין - לא חוסמים
}
let text = input.last_assistant_message || "";
if (IGNORE_CODE) {
text = text.replace(/```[\s\S]*?```/g, " ");
}
const words = text.trim().split(/\s+/).filter(Boolean).length;
if (words <= LIMIT) process.exit(0);
// מונע לולאה אינסופית: חוסמים לכל היותר MAX_BLOCKS פעמים בתור
const turnId = String(input.prompt_id || input.session_id || "unknown");
const stateFile = path.join(
os.tmpdir(),
"cc-word-limit-" + turnId.replace(/[^A-Za-z0-9_-]/g, "") + ".json"
);
let blocks = 0;
try {
blocks = JSON.parse(fs.readFileSync(stateFile, "utf8")).blocks || 0;
} catch (e) {}
if (blocks >= MAX_BLOCKS) process.exit(0);
try {
fs.writeFileSync(stateFile, JSON.stringify({ blocks: blocks + 1 }));
} catch (e) {}
const reason =
"התשובה שלך היא " + words + " מילים, מעל המגבלה של " + LIMIT + ". " +
"כתוב אותה מחדש בקצרה. אם היא ארוכה כי לא היית בטוח מה ביקשו ממך " +
"וכיסית כמה אפשרויות - אל תנחש: שאל שאלת הבהרה אחת קצרה ועצור.";
process.stdout.write(
JSON.stringify({
hookSpecificOutput: {
hookEventName: "Stop",
decision: "block",
reason: reason,
},
systemMessage: "Stop hook: נחסם (" + words + "/" + LIMIT + " מילים)",
})
);
process.exit(0);
});
על Mac או Linux, תנו לו הרשאת הרצה:
chmod +x ~/.claude/hooks/word-limit.js
שלב 2: מחברים את ה-hook להגדרות
עכשיו צריך להגיד ל-Claude Code להריץ את הקובץ בכל סיום תשובה. פותחים את קובץ ההגדרות ומוסיפים את הבלוק הבא. אם כבר יש בקובץ תוכן, מוסיפים רק את המפתח hooks ולא מוחקים את השאר.
Windows
הקובץ: C:\Users\YOUR_NAME\.claude\settings.json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "node",
"args": ["C:\\Users\\YOUR_NAME\\.claude\\hooks\\word-limit.js"],
"timeout": 10
}
]
}
]
}
}
Mac / Linux
הקובץ: ~/.claude/settings.json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "node",
"args": ["/Users/YOUR_NAME/.claude/hooks/word-limit.js"],
"timeout": 10
}
]
}
]
}
}
שלוש נקודות שמפילות את רוב האנשים:
- החליפו את YOUR_NAME בשם המשתמש האמיתי שלכם.
- כשמשתמשים ב-args, הנתיב חייב להיות מלא. הקיצור ~ לא מתורגם בצורה הזאת.
- ב-Windows, כל בקסלאש בנתיב נכתב פעמיים בתוך JSON.
שלב 3: מוודאים שזה עובד
הפעילו מחדש את Claude Code, ואז הריצו בתוכו:
/hooks
ייפתח מסך שמראה את כל ה-hooks המוגדרים. אתם אמורים לראות שורה תחת Stop עם התווית User Settings. אם היא שם, החיבור תקין.
בדיקה אמיתית: בקשו מהסוכן משהו שגורר תשובה ארוכה, למשל “תסביר לי לעומק איך עובד מנגנון ההרשאות”. במקום מסה של טקסט, אתם אמורים לראות שהתשובה נחסמת והסוכן מחזיר גרסה קצרה, או שואל שאלת הבהרה.
קיצור דרך: לתת לסוכן להתקין את זה לבד
אם לא בא לכם לגעת בקבצים, אפשר פשוט לבקש. הדביקו לסוכן את המשפט הבא:
“תוסיף לי Stop hook ב-settings.json הגלובלי שלי: סקריפט Node שקורא את last_assistant_message מה-stdin, סופר מילים בלי בלוקים של קוד, ואם יש יותר מ-150 מילים מחזיר JSON עם decision: block וסיבה שמבקשת לקצר או לשאול שאלת הבהרה. תוסיף גם מגן מפני לולאה לפי prompt_id.”
אחרי שהוא מסיים, הריצו /hooks ותוודאו שהשורה מופיעה.
התאמות אישיות
| מה לשנות | איך |
|---|---|
| אורך התשובה | שנו את LIMIT בראש הקובץ. 150 זה נוקשה ומצוין לעבודה יומיומית, 300 נוח יותר להסברים. |
| לספור גם קוד | הפכו את IGNORE_CODE ל-false. כברירת מחדל בלוקים של קוד לא נספרים, כי אין סיבה להעניש על קוד ארוך. |
| כמה נסיונות תיקון | MAX_BLOCKS קובע כמה פעמים ה-hook יחסום את אותו תור לפני שהוא מוותר. 2 זה איזון טוב. |
| רק בפרויקט מסוים | שימו את אותו בלוק בקובץ .claude/settings.json שבתוך הפרויקט, במקום בקובץ הגלובלי. הגדרות משני המקומות מתמזגות. |
| לכבות לרגע | הוסיפו disableAllHooks: true לקובץ ההגדרות, או פשוט הוציאו את בלוק ה-Stop. |
טיפים מהשטח
- הכוח האמיתי הוא לא בקיצור אלא בניסוח ה-reason. זה הטקסט היחיד שהסוכן קורא, ולכן שם כדאי להגיד לו במפורש לשאול במקום לנחש.
- אל תוותרו על מגן הלולאה. בלי MAX_BLOCKS, סוכן שמתעקש על תשובה ארוכה יכול להיתקע במעגל ולשרוף טוקנים.
- התחילו ממגבלה גבוהה יחסית, למשל 250, ותרדו בהדרגה. קפיצה ישר ל-150 מרגישה חונקת ביום הראשון.
- ה-hook רץ אצלכם על המחשב עם ההרשאות שלכם. אל תריצו hooks שלא קראתם, בדיוק כמו כל סקריפט אחר.
- אותו רעיון עובד גם למטרות אחרות: להריץ לינטר בסיום כל תשובה, לחסום סיום כשיש בדיקות שנכשלות, או לשלוח התראה כשהסוכן סיים משימה ארוכה.
- הקובץ הגלובלי חל על כל הפרויקטים. אם אתם עובדים גם על תיעוד ארוך, שקלו להגדיר אותו ברמת הפרויקט בלבד.
מגבלות שכדאי להכיר
- ה-hook לא הופך את המודל לחכם יותר. הוא רק לא נותן לו לסיים תור בתשובה מנופחת, ודוחף אותו לשאול.
- ספירת מילים היא קירוב. תשובה של 149 מילים מלאה בהנחות תעבור.
- אם הפקודה חורגת מה-timeout שהגדרתם, היא מבוטלת והתור נסגר כרגיל, בלי חסימה.
- hooks ברמת פרויקט דורשים אישור אמון לתיקייה בפעם הראשונה.
- מבנה השדות של Hooks מתעדכן מדי פעם. אם משהו מפסיק לעבוד, הדוקומנטציה הרשמית היא מקור האמת.
לינקים
- התיעוד הרשמי של Hooks: code.claude.com/docs/en/hooks
- קובצי ההגדרות של Claude Code: code.claude.com/docs/en/settings
- Claude Code, דף המוצר: claude.com/product/claude-code