ينفّذ الـ hook Stop في Claude Code أمرًا في كل مرة ينتهي فيها وكيل Claude الرئيسي من الرد، ما يجعله المكان المناسب لإرسال إشعار "تم" أو تسجيل النتيجة أو تشغيل الفحوصات. لا يعمل عندما تقاطع Claude، والدور الذي ينتهي بخطأ في الـ API يُطلق StopFailure بدلًا منه. يتلقى الـ hook الرسالة الأخيرة من Claude في last_assistant_message، ويمكنه إرجاع decision: "block" مع reason ليجعل Claude يواصل بدلًا من التوقف.
01متى يُطلَق Stop ومتى لا يُطلَق
يُطلَق Stop في نهاية كل دور، وليس فقط عند اكتمال مهمة كاملة. فـ Claude Code لا يعرف شيئًا اسمه "المهمة"؛ كل ما يعرفه أن Claude توقف عن الرد وينتظرك. سؤال قصير يحصل على Stop واحد، وكذلك إعادة هيكلة تستغرق ساعتين.
لا يُطلَق في الحالات التالية:
- عندما تضغط Esc أو تقاطع الدور بأي طريقة أخرى؛
- عندما ينتهي الدور بسبب خطأ في الـ API مثل تجاوز حد الطلبات أو الضغط الزائد أو فشل المصادقة. في هذه الحالات يُطلق Claude Code الحدث
StopFailure، مع حقلerrorيمكنك المطابقة عليه (rate_limitوoverloadedوserver_errorوغيرها)؛ - عندما ينتهي subagent، فذلك هو
SubagentStop.
لا يدعم Stop الـ matchers، لذا يُتجاهل أي matcher تضيفه ويعمل الـ hook في كل دور.
02أبسط Stop hook
أضف ما يلي إلى ~/.claude/settings.json لتنفيذ أمر كلما انتهى Claude. هذا المثال يشغّل صوتًا على macOS:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
]
}
}
لأوامر إشعارات سطح المكتب على Mac وWindows وLinux، راجع كيف تحصل على إشعار عندما ينتهي Claude Code. يتناول باقي هذا الدليل ما يمكنك فعله بمدخلات الـ hook ومخرجاته.
03ما يتلقاه الـ hook
إضافة إلى الحقول المشتركة (session_id وtranscript_path وcwd وpermission_mode وhook_event_name)، يتلقى الـ hook Stop ما يلي:
last_assistant_messageنص الرد الأخير من Claude في هذا الدورstop_hook_activetrue إذا كان Claude يواصل بالفعل لأن Stop hook منع التوقفbackground_tasksالمهام التي لا تزال قيد التشغيل، مثل الـ shells في الخلفية أو الـ subagentssession_cronsمواعيد الاستئناف المجدولة في هذه الجلسة، مثل تلك الناتجة عن /loopاستخدم last_assistant_message بدلًا من قراءة ملف الـ transcript، فهو يُكتب بشكل غير متزامن وقد لا يحتوي الرسالة الأخيرة بعد عند تشغيل الـ hook.
يخبرك background_tasks وsession_crons ما إذا كانت الجلسة قد انتهت فعلًا أم أنها متوقفة مؤقتًا إلى أن يوقظها عمل في الخلفية. يمكن لسكربت الإشعار التحقق من أن كليهما فارغ قبل أن يعلن "انتهى".
04ضع الرسالة الأخيرة من Claude في الإشعار
هذا السكربت (لـ macOS وLinux، ويتطلب jq) يعرض بداية رد Claude ويبقى صامتًا إذا كان هناك عمل لا يزال يجري في الخلفية:
#!/bin/bash
# ~/.claude/hooks/notify-done.sh
input=$(cat)
# Skip if background tasks are still in flight
busy=$(jq '(.background_tasks // []) | length' <<<"$input")
[ "$busy" -gt 0 ] && exit 0
msg=$(jq -r '(.last_assistant_message // "Claude has finished")
| gsub("\\s+"; " ") | .[0:140]' <<<"$input")
if command -v osascript >/dev/null 2>&1; then
osascript -e 'on run argv' \
-e 'display notification (item 1 of argv) with title "Claude has finished"' \
-e 'end run' "$msg"
elif command -v notify-send >/dev/null 2>&1; then
notify-send "Claude has finished" "$msg"
fi
exit 0
احفظه، ثم شغّل chmod +x ~/.claude/hooks/notify-done.sh، ووجّه الـ hook إليه:
{
"hooks": {
"Stop": [
{
"hooks": [{ "type": "command", "command": "~/.claude/hooks/notify-done.sh" }]
}
]
}
}
ينتهي السكربت دائمًا بالرمز 0. وهذا مهم: ففي Stop يعني رمز الخروج 2 "لا تتوقف"، لذا فإن سكربت إشعار ينتهي بالرمز 2 عن طريق الخطأ سيُبقي Claude يعمل.
05جعل Claude يواصل العمل
Stop من الأحداث التي يمكنها المنع. ومنع التوقف يعني أن Claude يواصل الدور مستخدمًا السبب الذي تقدمه كتعليمته التالية. يمكنك فعل ذلك بطريقتين:
- الخروج بالرمز
2، فيتلقى Claude محتوى stderr كسبب؛ أو - الخروج بالرمز
0وطباعة JSON:
{
"decision": "block",
"reason": "The test suite is failing. Run npm test, fix the failures, then finish."
}
إذا كان الـ hook يقدّم توجيهًا لا إبلاغًا عن خطأ، فإن hookSpecificOutput.additionalContext يُبقي المحادثة مستمرة أيضًا، لكن الـ transcript يصنّفه كملاحظات من Stop hook بدلًا من خطأ في الـ hook:
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Please run the test suite before finishing"
}
}
تجنّب الحلقة اللانهائية
الـ Stop hook الذي يمنع التوقف دائمًا لن يسمح لـ Claude بالتوقف أبدًا. هناك أمران يحميانك:
- تصبح قيمة
stop_hook_activeهيtrueبمجرد أن يواصل Claude بسبب Stop hook. تحقّق منها واخرج مبكرًا. - ينهي Claude Code الدور على أي حال بعد أن تمنع الـ Stop hooks التوقف ثماني مرات متتالية دون أي استدعاء لأداة بينها. ويمكنك رفع هذا الحد عبر متغير البيئة
CLAUDE_CODE_STOP_HOOK_BLOCK_CAP.
إليك hook يجعل Claude يصلح الاختبارات الفاشلة مرة واحدة، ثم يسمح له بالتوقف:
#!/bin/bash
# ~/.claude/hooks/tests-must-pass.sh
input=$(cat)
[ "$(jq -r '.stop_hook_active' <<<"$input")" = "true" ] && exit 0
if ! npm test --silent >/dev/null 2>&1; then
jq -nc '{decision: "block", reason: "npm test is failing. Fix the failures, then finish."}'
fi
exit 0
تعمل الـ hooks في مجلد العمل الخاص بالجلسة، لذا يُنفَّذ npm test داخل مشروعك. اجعل الفحوصات سريعة: افتراضيًا يمكن أن يستمر command hook حتى 600 ثانية، وClaude ينتظره.
إذا كنت تريد فقط أن يواصل Claude العمل نحو هدف معيّن دون كتابة سكربت، فإن الأمر المدمج /goal في Claude Code يُعِدّ لك Stop hook خاصًا بالجلسة.
06إشعارات عند الإخفاق أيضًا
بما أن أخطاء الـ API تتخطى Stop، فلن يخبرك تنبيه "اكتمل" بأن Claude توقف بسبب تجاوز حد الطلبات. أضف السكربت نفسه تحت StopFailure إن أردت معرفة ذلك. يُتجاهَل مخرجه ورمز خروجه، لذا يقتصر دوره على الإشعار أو التسجيل. في StopFailure يحتوي last_assistant_message على نص الخطأ، مثل "API Error: Rate limit reached".
07أين يأتي دور Claude Code Notifier
يُعدّ Stop hook يستدعي خدمة push طريقة ممتازة لمعرفة أن Claude انتهى أثناء غيابك. إن أردت تنفيذ ذلك بنفسك، فإن دليل ntfy بداية جيدة.
يستخدم Claude Code Notifier الـ hook نفسه Stop، مع فرق واحد: يمكنه إرسال رد. أداة سطر الأوامر المجانية الخاصة به، ccn، تعمل على Mac وWindows وLinux وتعرض إشعارًا على سطح المكتب بمفردها. وعند ربط تطبيق iPhone أو Android، يصلك إشعار push بأن "Claude انتهى" فور انتهاء الدور، ويمكنك كتابة التعليمة التالية من الهاتف. يعيدها ccn من Stop hook بوصفها التعليمة التالية لـ Claude، فتستمر الجلسة دون أن تكون أمام لوحة المفاتيح. إذا كتبت شيئًا خلال آخر 30 ثانية، فلن ينتظر شيء الهاتف. الأداة مستقلة وليست من إنتاج Anthropic. راجع الابتعاد عن المهام الطويلة لهذا الأسلوب في العمل، وصفحة ccn للتثبيت.
08الأسئلة الشائعة
هل يعمل Stop hook عندما ينهي Claude المهمة كلها، أم بعد كل رد؟
بعد كل رد. ينطلق Stop كلما أنهى الوكيل الرئيسي رده، مهما كان حجم الطلب. افحص background_tasks وsession_crons في المدخلات إن احتجت إلى معرفة ما إذا كان هناك عمل آخر مجدول.
لماذا لم يعمل Stop hook الخاص بي؟
ربما قاطعت الدور، وهذا يتخطى Stop، أو انتهى بخطأ API، فيُطلَق StopFailure بدلًا منه. وإلا فتحقق من أن /hooks يعرض الـ hook الخاص بك وأن السكربت قابل للتنفيذ. تغطي قائمة استكشاف الأخطاء بقية الحالات.
لماذا يواصل Claude العمل بعد تشغيل Stop hook الخاص بي؟
الـ hook الخاص بك يمنع التوقف، إما بالخروج برمز 2 أو بطباعة decision: "block". تأكد من أن الـ hook المخصص للإشعار فقط يخرج دائمًا بـ 0، وأن أي hook مانع يفحص stop_hook_active أولًا.
كيف أحصل على الإجابة النهائية لـ Claude داخل الـ hook؟
اقرأ last_assistant_message من JSON الوارد عبر stdin، مثلًا باستخدام jq -r '.last_assistant_message'. لا تعتمد على transcript_path: فقد لا يحتوي الملف على الرسالة الأخيرة بعد عند انطلاق Stop.
ما الفرق بين Stop وSubagentStop؟
Stop مخصص للوكيل الرئيسي. أما SubagentStop فينطلق عند انتهاء وكيل فرعي، ويطابق على نوع الوكيل، ويدعم حقول المنع نفسها. مكان تنبيه "Claude انتهى" هو Stop.