تخطّى إلى المحتوى الرئيسي
خطافات Claude Code: كيف نحوّل قواعد وكلاء الذكاء الاصطناعي إلى حواجز حماية حتمية

خطافات Claude Code: كيف نحوّل قواعد وكلاء الذكاء الاصطناعي إلى حواجز حماية حتمية

October 14, 2026
الذكاء الاصطناعي وأدوات المطورين
8 دقيقة قراءة

أهم النقاط

خطافات Claude Code (hooks) هي معالِجات يحددها المستخدم، مثل أمر shell أو نقطة HTTP أو أداة MCP أو موجّه LLM أو وكيل فرعي، يشغّلها Claude Code عند أحداث ثابتة في دورة الحياة مثل PreToolUse وPostToolUse وStop.

رمز الخروج 2 وحده هو الذي يمنع التنفيذ. الخطاف الذي يخرج بالرمز 1 يُعدّ خطأً غير مانع، فيستمر استدعاء الأداة.

PreToolUse هو حدث الأدوات الوحيد القادر على منع الإجراء. أما PostToolUse فيعمل بعد تنفيذ الأداة ولا يملك إلا إعطاء ملاحظات لـ Claude.

يستطيع خطاف Stop منع Claude من إنهاء الرد حتى تنجح الفحوصات، لكن يجب أن يقرأ الحقل stop_hook_active لتجنّب حلقة إصلاح وفشل لا تنتهي.

تعمل الخطافات بكامل صلاحيات المستخدم، وفي تشغيل claude -p أو Agent SDK تُنفَّذ الخطافات المحفوظة في المستودع دون نافذة تأكيد الثقة بمساحة العمل.

لماذا نقلنا القواعد من CLAUDE.md إلى الخطافات؟

القاعدة المكتوبة في CLAUDE.md طلبٌ يلتزم به النموذج غالبًا، أما خطاف Claude Code فمعالِجٌ تشغّله البيئة في كل مرة يقع فيها الحدث. كانت ملفات CLAUDE.md لدينا تقول "لا تلمس ملفات .env" و"شغّل أداة التنسيق بعد التعديل"، والتزم Claude Code بالقاعدتين في معظم الأحيان. "معظم الأحيان" يكفي لنصائح الأسلوب، لكنه لا يكفي لحاجز حماية.

في Devya نضع الآن كل ما لا يجوز تخطّيه أبدًا، مثل حظر ملفات الأسرار وتنسيق الشيفرة المعدّلة ورفض الإنهاء مع فشل فحص الأنواع، داخل الخطافات، ونُبقي CLAUDE.md للإرشاد والسياق. لم يكن الحل تعليمات أعلى صوتًا، بل نقل القاعدة خارج الموجّه حتى لا يعود انتباه النموذج في دورة معيّنة عاملًا مؤثرًا.

ما هو خطاف Claude Code، وما الفرق بينه وبين CLAUDE.md؟

خطاف Claude Code معالِجٌ تنفّذه بيئة Claude Code نفسها، لا النموذج، عند وقوع حدث محدد في الجلسة. يمرّر الحدث حمولة JSON تصف ما يجري (اسم الأداة ومدخلاتها ومعرّف الجلسة)، ويستطيع المعالِج فحصها والتصرف بناءً عليها وإرجاع قرار اختياريًا. أما CLAUDE.md فيعمل بالعكس: هو نص يُحمَّل في سياق النموذج، فيعتمد الالتزام بالقاعدة على تقدير النموذج لها في تلك الدورة.

يصنّف فريقنا كل قاعدة ضمن واحدة من ثلاث طبقات:

CLAUDE.md: يفرضه النموذج بشكل احتمالي. الأنسب للأعراف البرمجية وملاحظات المعمارية ونصائح "فضّل X على Y".

قواعد الصلاحيات (قوائم allow وdeny في الإعدادات): تفرضها البيئة عبر الأنماط. الأنسب للسماح أو المنع القاطع للأدوات والأوامر.

الخطافات: تفرضها البيئة بتشغيل شيفرتنا الخاصة. الأنسب للقواعد التي تحتاج منطقًا، مثل فحص المحتوى أو تشغيل أداة تنسيق أو اشتراط شروط قبل الإنهاء.

الأحداث التي نعتمد عليها أكثر هي SessionStart (عند بدء الجلسة أو استئنافها)، وPreToolUse (قبل استدعاء الأداة، ويمكنه منعه)، وPostToolUse (بعد نجاح استدعاء الأداة)، وStop (عندما ينهي Claude رده). وتسرد وثائق Claude Code الحالية أحداثًا أكثر بكثير، منها أحداث الوكلاء الفرعيين والضغط وworktree والصلاحيات.

أين توجد خطافات Claude Code، وكيف يختارها الـ matcher؟

يقرأ Claude Code الخطافات من ملفات الإعدادات: الملف ~/.claude/settings.json يسري على كل مشاريع الجهاز، والملف .claude/settings.json يُحفَظ في المستودع ويُشارَك مع الفريق، والملف .claude/settings.local.json يبقى محليًا لنسخة واحدة. ويمكن للإضافات والمهارات والوكلاء الفرعيين أن تحمل خطافات أيضًا، وتُدمَج الخطافات من المستويات المختلفة بدل أن يستبدل بعضها بعضًا.

يوضع الخطاف تحت المفتاح "hooks" مجمّعًا حسب اسم الحدث. خطاف حماية الأسرار لدينا يقع تحت PreToolUse مع matcher قيمته Read|Edit|Write، ومعالِج واحد من نوع "command" يشغّل ${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-secrets.sh.

يُختبَر الـ matcher مقابل اسم الأداة. الـ matcher المكوّن فقط من حروف وأرقام وشرطات سفلية وشرطات ومسافات وفواصل والرمز | يُعامَل كمطابقة تامة أو قائمة مطابقات تامة، فقيمة Edit|Write تطابق هاتين الأداتين فقط. وأي رمز آخر يحوّله إلى تعبير نمطي JavaScript غير مقيّد بالبداية والنهاية، ولهذا تطابق القيمة Edit.* الأداة NotebookEdit أيضًا. نكتب ^Edit$ عندما نقصد أداة واحدة.

يمكن تضييق المعالِج أكثر بحقل "if" بصيغة قواعد الصلاحيات، مثل Bash(git *). وتصف وثائق Claude Code هذا المرشّح بأنه أفضل جهد فقط، لذلك نستخدم قواعد الصلاحيات لا "if" للمنع القاطع.

كيف نمنع استدعاء أداة خطِر بخطاف PreToolUse؟

يمنع خطاف PreToolUse استدعاء الأداة بإحدى طريقتين: الخروج بالرمز 2 مع كتابة السبب في stderr، أو الخروج بالرمز 0 مع طباعة JSON يضبط hookSpecificOutput.permissionDecision على "deny". وفي الحالتين يتلقى Claude السبب ويستطيع تغيير مساره.

يقرأ سكربت protect-secrets لدينا مدخلات الخطاف من stdin، ويستخرج tool_input.file_path باستخدام jq، ويطابقه مع ملفات .env ومتغيراتها .env.* وملفات .pem و.key. يسمح بالملف .env.example، ولأي تطابق آخر يطبع رسالة "قد يحتوي على أسرار" في stderr ويخرج بالرمز 2.

الخطأ الذي وقعنا فيه أولًا: كان فرع المنع ينتهي بـ exit 1. يعامل Claude Code أي رمز خروج غير 0 و2 كخطأ غير مانع، فيعرض إشعار خطأ في الخطاف ويترك استدعاء الأداة يستمر. وحده الخروج بالرمز 2 أو قرار deny بصيغة JSON يمنع فعلًا.

عندما تختلف قرارات عدة خطافات PreToolUse، تكون الأولوية لـ deny ثم defer ثم ask ثم allow، فيتغلب خطاف صارم واحد دائمًا على خطاف متساهل.

هذا الخطاف لا يوقف استدعاء Bash مثل cat .env لأنه يمر عبر أداة مختلفة، لذلك نقرنه بقواعد صلاحيات deny للمسارات نفسها. ويمكن لـ PreToolUse أيضًا إعادة كتابة الاستدعاء عبر updatedInput، لكن updatedInput يستبدل كائن المدخلات بالكامل، فالخطاف الذي ينسى حقلًا لم يتغير يُسقطه بصمت.

كيف ننسّق الملفات تلقائيًا بعد كل تعديل يجريه Claude؟

يعمل خطاف PostToolUse مع matcher قيمته Edit|Write بعد كل تعديل ناجح لملف، ما يجعله المكان المناسب لأداة التنسيق. ننسّق الملف الذي تغيّر فقط بقراءة tool_input.file_path من stdin، لأن تنسيق المشروع كله بعد كل تعديل بطيء وينتج فروقات مزعجة.

يفحص خطاف التنسيق لدينا امتداد الملف (TypeScript أو JavaScript أو JSON أو CSS)، ويشغّل npx --no-install biome format --write على ذلك المسار وحده، ويتجاهل المخرجات، ثم يخرج بالرمز 0.

تنطبق قاعدتان على PostToolUse. لا يستطيع التراجع عن أي شيء لأن الملف كُتب بالفعل، فالخروج بالرمز 2 يعرض stderr لـ Claude كملاحظة فقط. ولا يرى Claude أبدًا stderr من خطاف يخرج بالرمز 0، لذلك عندما نريد أن يتفاعل Claude مع خطأ lint يخرج الخطاف بالرمز 2 ويضع مخرجات lint في stderr.

كيف نمنع Claude من الإنهاء بينما يفشل فحص الأنواع؟

يعمل خطاف Stop عندما ينهي Claude رده، وطباعة كائن JSON فيه "decision": "block" ونص "reason" تجعل Claude يواصل العمل ويعامل السبب كتعليمته التالية. وبهذا يتحول "شغّل فاحص الأنواع قبل أن تقول انتهيت" من اقتراح إلى بوابة إلزامية.

يخرج سكربت البوابة لدينا بالرمز 0 فورًا إذا كان الحقل stop_hook_active في المدخلات يساوي true. وإلا فيشغّل npx --no-install tsc --noEmit، وعند الفشل يطبع قرار block يتضمن سببه آخر 30 سطرًا من مخرجات المترجم.

فحص stop_hook_active مهم. تكون قيمة الحقل true عندما يكون Claude مستمرًا أصلًا بسبب خطاف Stop، فالخروج المبكر في تلك المرة يمنح Claude محاولة إجبارية واحدة فقط بدل حلقة لا تنتهي من الإصلاح والفشل. ويضع Claude Code أيضًا حدًا أقصى من ثماني مواصلات متتالية بسبب خطافات Stop، لكننا نفضّل ألا نعتمد على هذا الحد الاحتياطي.

نُبقي البوابة سريعة. تشغيل مجموعة الاختبارات كاملة عند كل توقف يبطئ كل دورة، لذلك نعتمد على tsc --noEmit ونترك الاختبارات لخط CI.

ما مخاطر الأمان ونقاط التصحيح في خطافات Claude Code؟

تنفّذ خطافات الأوامر في Claude Code بكامل صلاحيات المطوّر، فالخطاف بقوة أي سكربت يشغّله يدويًا. نراجع سكربتات الخطافات كما نراجع شيفرة الإنتاج: نضع كل متغير shell بين علامتي اقتباس، ونرفض مسارات الملفات التي تحتوي على ".."، ونشير إلى السكربتات بمسار مطلق عبر ${CLAUDE_PROJECT_DIR}.

في الجلسة التفاعلية يحجب Claude Code خطافات ملفات الإعدادات حتى يقبل المستخدم نافذة الثقة بمساحة العمل للمجلد. أما في جلسة claude -p أو Agent SDK فلا توجد نافذة ويُعامَل المجلد كموثوق، فتعمل الخطافات المحفوظة في .claude/settings.json لأي مستودع مستنسخ فورًا. قبل تشغيل claude -p آليًا على مستودع لم نكتبه، نقرأ مجلد .claude فيه أو نمرّر --settings مع ضبط disableAllHooks على true.

للتصحيح، تذهب مخرجات stdout لمعظم الأحداث إلى سجل التصحيح لا إلى المحادثة. ومن الاستثناءات UserPromptSubmit وSessionStart، حيث يُضاف stdout النصي إلى سياق Claude، ما يجعل خطاف SessionStart طريقة نظيفة لحقن اسم الفرع الحالي أو تاريخ اليوم.

تشغيل claude --debug يكتب مخرجات كل خطاف في سجل داخل ~/.claude/debug/ باسم معرّف الجلسة، ويفتح الأمر /hooks قائمة للقراءة فقط بكل الخطافات المضبوطة والملف الذي جاء منه كل خطاف.

الأسئلة الشائعة

س: هل تحل خطافات Claude Code محل CLAUDE.md؟

ج: لا. تفرض خطافات Claude Code القواعد التي يجب أن تتحقق في كل مرة، بينما يحمل CLAUDE.md الأعراف والسياق الذي يقدّره النموذج. تحتاج معظم المشاريع إلى الاثنين، مع نقل القواعد غير القابلة للتفاوض فقط إلى الخطافات.

س: لماذا لا يمنع خطاف PreToolUse استدعاء الأداة؟

ج: السبب الأكثر شيوعًا هو الخروج بالرمز 1 بدل 2. يعامل Claude Code أي رمز خروج غير صفري غير 2 كخطأ غير مانع فيستمر الاستدعاء؛ اخرج بالرمز 2 أو اطبع JSON بقيمة permissionDecision تساوي "deny".

س: هل يستطيع خطاف PostToolUse التراجع عن تعديل ملف؟

ج: لا. يعمل خطاف PostToolUse بعد تنفيذ الأداة، فلا يملك إلا إضافة ملاحظات أو تغيير ما يراه Claude. لمنع التعديل استخدم خطاف PreToolUse.

س: هل تعمل خطافات Claude Code داخل الوكلاء الفرعيين؟

ج: نعم. تعمل الخطافات القادمة من ملفات الإعدادات والسياسات المُدارة والإضافات على استدعاءات أدوات الوكلاء الفرعيين أيضًا، وتتضمن مدخلات الخطاف الحقلين agent_id وagent_type ليعرف السكربت أي وكيل فرعي أجرى الاستدعاء.

س: هل من الآمن حفظ الخطافات في مستودع مشترك؟

ج: نعم، فالملف .claude/settings.json مصمَّم ليُحفَظ في المستودع حتى يتشارك الفريق كله الحواجز نفسها. راجع تلك السكربتات كشيفرة قابلة للتنفيذ، لأنها تعمل بصلاحيات كل مطوّر.