SSD Nodes Learn Hosting plans →
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-08-23

شرح Claude Code hooks: الأحداث وحالة الخروج 2

تعمل Claude Code hooks حتى دون موافقة النموذج. تعرّف إلى مكان إعدادها، والأحداث التي تشغّلها، وكيف تلغي حالة الخروج 2 استدعاء الأداة، وتكلفة الأمان.

ما هو Claude Code hook

عبارة Claude Code hooks عن أوامر shell يشغّلها Claude Code تلقائياً عند نقاط محددة في دورة حياته. هذا هو الفرق الأساسي بين hook وملف القواعد. التعليمات الموجودة في CLAUDE.md هي إرشادات، ويوازنها النموذج مع كل ما يوجد في سياقه. أما hook فهو تعليمات برمجية تُشغَّل سواء وافق النموذج أم لا. إذا استمر الوكيل في تخطي أداة التنسيق التي طلبت منه استخدامها مرتين، فلا تحتاج إلى تعليمات أكثر حزماً. بل تحتاج إلى hook.

الآلية بسيطة. تسجّل أمراً في ملف إعدادات تحت اسم حدث. عند وقوع الحدث، يشغّل Claude Code الأمر ويكتب بيانات الحدث إلى الإدخال القياسي (stdin) بصيغة JSON (ترميز كائنات JavaScript). يقرأ الأمر هذه البيانات، وينفذ عمله، ثم يعيد حالة خروج. تؤدي حالة الخروج 2 من hook من نوع PreToolUse إلى إلغاء استدعاء الأداة قبل تشغيلها، ويُمرَّر أي نص كتبه البرنامج النصي إلى الخطأ القياسي (stderr) إلى النموذج باعتباره السبب.

تأتي أسماء الأحداث وأسماء الحقول الواردة هنا من مرجع hooks في Claude Code، وقد جرى التحقق منها في August 2026 مقابل الإصدار 2.1.232. تتغير هذه الواجهة بسرعة، لذا راجع المرجع الخاص بإصدارك قبل نسخ JSON من أي تدوينة، بما فيها هذه التدوينة. اطبع مرجعك باستخدام claude --version.

مكان وجود إعدادات الـhook

الـhook عبارة عن كتلة JSON داخل ملف إعدادات. يمكن أن يحتوي أحد ستة مواضع على هذه الكتلة، ويحدد نطاق الملف نطاق الـhook.

  • ~/.claude/settings.json: يشمل كل مشروع على جهازك، ولا يشمل أجهزة الآخرين.
  • .claude/settings.json: يشمل مشروعاً واحداً، ويُحفظ في المستودع، لذلك يحصل عليه كل من يستنسخ المشروع.
  • .claude/settings.local.json: يشمل مشروعاً واحداً على جهازك فقط.
  • إعدادات السياسة المُدارة: تشمل المؤسسة بأكملها، ويضبطها المسؤول.
  • hooks/hooks.json داخل plugin: تبقى فعّالة ما دام ذلك الـplugin مفعّلاً.
  • بيانات frontmatter الخاصة بـSkill أو subagent: تبقى فعّالة ما دام ذلك المكوّن نشطاً.

تُدمج إدخالات الـhook من هذه الملفات بدلاً من أن يستبدل بعضها بعضاً. يضيف ملف إعدادات المشروع الـhooks الخاصة به إلى الـhooks الموجودة في إعدادات المستخدم، ولا يستبدلها. لذلك يمكن أن يحتوي حدث واحد على عدة hooks من ملفات متعددة. يؤدي ضبط "disableAllHooks": true إلى إيقافها، باستثناء واحد: تستمر hooks القادمة من إعدادات السياسة المُدارة في العمل، ما لم يُطبَّق ذلك الإعداد أيضاً في الإعدادات المُدارة.

شغّل /hooks داخل جلسة لعرض كل hook مسجّل حالياً، مجمّعة حسب الحدث، مع ملف المصدر وmatcher الخاص بكل منها. القائمة للقراءة فقط، لذلك تغيّر الـhook بتحرير ملف الإعدادات. يلتقط file watcher التعديل عادةً من دون إعادة تشغيل.

أحداث hook المتاحة في Claude Code

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

  • PreToolUse: قبل تنفيذ استدعاء أداة. هذا هو الحدث الذي يمكنه منع التنفيذ.
  • PostToolUse: بعد نجاح استدعاء أداة. يُطلَق PostToolUseFailure بدلاً منه عند فشل الاستدعاء، لذلك يحتاج hook الذي يجب أن يرى كل نتيجة إلى كليهما.
  • PermissionRequest: عندما يحتاج استدعاء أداة إلى قرار صلاحية، أي في اللحظة التي يظهر فيها طلب الموافقة.
  • UserPromptSubmit: عند إرسال prompt، وقبل أن يعالجه Claude. تُضاف أي مخرجات يطبعها هذا hook إلى stdout إلى سياق النموذج.
  • SessionStart وSessionEnd: عند كل نهاية لجلسة. يُطلَق SessionStart أيضاً بعد الضغط، باستخدام قيمة المطابقة compact.
  • Stop: عند انتهاء Claude من الرد. يحدث ذلك مرة واحدة لكل دور، وليس مرة واحدة لكل مهمة مكتملة.

تحتوي كل مجموعة على matcher يحدد الحالات التي تُشغّل hook. وفي أحداث الأدوات، يصفّي هذا الحقل حسب اسم الأداة، لذلك يُطلَق "Edit|Write" عند تعديل الملفات فقط، ولا يُطلَق في أي حالة أخرى. تكون قيم المطابقة حساسة لحالة الأحرف. تؤدي قيمة المطابقة الفارغة إلى تشغيل hook في كل حالة. تُسمّى الأدوات من خادم MCP (بروتوكول سياق النموذج) بالشكل mcp__<server>__<tool>، لذلك تلتقط قيمة مطابقة هي "mcp__github__.*" أدوات خادم واحد، ولا تؤثر في الخوادم الأخرى.

تحتوي hooks من نوع Stop على سلوك مهم يجب معرفته قبل كتابة أحدها. يعيد hook من نوع Stop الذي يمنع التنفيذ النموذج إلى العمل، ويتجاوز Claude Code هذا hook بعد ثماني عمليات منع متتالية. اقرأ الحقل stop_hook_active من إدخال hook واخرج بالرمز 0 عندما تكون قيمته true، وإلا فسيستمر hook في التكرار حتى يبلغ هذا الحد.

ما يتلقاه hook عبر stdin

عندما يكون Claude على وشك تشغيل npm test، يقرأ hook من النوع PreToolUse على Bash ما يلي من stdin:

{
  "session_id": "abc123",
  "cwd": "/home/deploy/myproject",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

يحتوي كل حدث على session_id وcwd وpermission_mode وtranscript_path وhook_event_name. وتضيف أحداث الأدوات tool_name وtool_input وtool_use_id. أما الأحداث الأخرى فتحتوي على حقولها الخاصة: يحصل UserPromptSubmit على نص prompt، ويحصل SessionStart على source من startup أو resume أو clear أو compact أو fork.

يُعد jq الطريقة المعتادة لقراءة ذلك داخل shell script، لكن صورة خادم مصغّرة لا تحتوي عليه. ثبّته أولاً باستخدام sudo apt install -y jq على Ubuntu وDebian.

ما الذي يفعله رمز الخروج باستدعاء الأداة الجاري تنفيذه

هناك ثلاث نتائج.

  • Exit 0 يعني أن hook لم يعترض. في PreToolUse لا يعني ذلك الموافقة، ويستمر مسار الأذونات المعتاد. في UserPromptSubmit وSessionStart، يُضاف stdout إلى سياق النموذج.
  • Exit 2 يحظر الإجراء في الأحداث التي يمكن حظرها، ومنها PreToolUse، ويصبح stderr سبب الحظر المعروض للنموذج. في الأحداث التي لا يمكن حظرها، مثل PostToolUse، يُتجاهل الحظر، لكن يظل stderr يصل إلى النموذج باعتباره ملاحظات.
  • أي رمز خروج آخر هو خطأ لا يحظر الإجراء. يستمر الإجراء. ويعرض السجل إشعاراً بوجود خطأ في hook، يتضمن السطر الأول من stderr بعد النص Failed with non-blocking status code:.

إذا أردت أي سلوك يتجاوز الحظر أو عدم التدخل، فاستخدم Exit 0 واطبع كائن JSON إلى stdout بدلاً من ذلك. يقرر hook من نوع PreToolUse باستخدام permissionDecision:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database drops go through a migration, not through the agent."
  }
}

يتجاوز "allow" المطالبة التفاعلية، ويلغي "deny" الاستدعاء ويرسل السبب إلى النموذج، بينما يعرض "ask" المطالبة كالمعتاد. اختر أسلوباً واحداً لكل hook. يؤدي الجمع بين Exit 2 وقرار JSON في stdout إلى نتيجة تحتاج إلى الرجوع إلى التوثيق لمعرفة سلوكها.

عندما تطابق عدة hooks حدثاً واحداً، فإنها تعمل بالتوازي ويستمر كل منها حتى يكتمل. لا يؤدي deny الصادر عن أحد hooks إلى إيقاف hooks الأخرى، لذلك يكتب hook التسجيل سطره بينما يمنع hook الحماية المكالمة نفسها. يدمج Claude Code الإجابات بعد ذلك، ويحتفظ بالقرار الأكثر تقييداً وفق الترتيب التالي: الرفض، التأجيل، السؤال، السماح.

مثال 1: حظر أمر تخريبي قبل تنفيذه

احفظ هذا الملف باسم .claude/hooks/block-destructive.sh في مشروعك:

#!/bin/bash
# Deny a Bash tool call whose command matches a banned pattern.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

for pattern in 'rm -rf /' 'mkfs' 'dd if=' 'DROP TABLE'; do
  if printf '%s' "$COMMAND" | grep -qiF -- "$pattern"; then
    echo "Blocked by policy: the command matches '$pattern'. A human runs this one." >&2
    exit 2
  fi
done

exit 0

اجعله قابلاً للتنفيذ، ثم سجّله في PreToolUse ضمن .claude/settings.json:

chmod +x .claude/hooks/block-destructive.sh
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive.sh",
            "timeout": 10,
            "statusMessage": "Checking the command against policy"
          }
        ]
      }
    ]
  }
}

اختبر البرنامج النصي يدوياً قبل الوثوق به، لأن hook يتعطل عند معالجة مدخلاته الخاصة يفشل بطريقة تسمح بالمتابعة:

echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /var/lib/postgresql"}}' \
  | .claude/hooks/block-destructive.sh
echo $?

يجب أن ترى السطر Blocked by policy: في stderr ورمز خروج قيمته 2. مرّر إليه أمراً غير ضار، مثل ls -la، ويجب ألا ترى أي مخرجات، مع رمز خروج قيمته 0. في الجلسة، يظهر الاستدعاء المحظور في السجل مع رسالتك سبباً للحظر، ويقرأ النموذج هذه الرسالة ويتكيف معها.

هناك خاصية واحدة تجعل تنفيذ ذلك مفيداً: تعمل hooks الخاصة بـPreToolUse قبل فحص permission mode، وفي كل permission mode، لذلك يستمر الحظر حتى مع bypassPermissions. وهذا ما يجعل hook مفيداً إلى جانب الوضع التلقائي في Claude Code وإعدادات الأذونات الخاصة به، حيث يجري تقليل طلبات التأكيد، بينما يستمر hook في العمل.

كن واضحاً بشأن حدود ذلك. تُعد مطابقة الأنماط في سلسلة أمر وسيلة حماية من إهمال agent، لكنها ليست حداً يمنع agent الماكر، لأن بالإمكان كتابة الأمر نفسه بصيغة لا يراها grep. يجب وضع القواعد الصارمة في نظام الأذونات وفي الحساب الذي تعمل العملية تحته.

مثال 2: طبِّق التنسيق والتدقيق بعد كل تعديل

PostToolUse مع أداة مطابقة Edit|Write يعمل بعد أي أداة لتحرير الملفات. احفظ هذا في .claude/hooks/after-edit.sh:

#!/bin/bash
# Format the edited file, then report lint failures back to the model.
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0

case "$FILE" in
  *.py)
    ruff format "$FILE" >/dev/null 2>&1
    if ! ruff check "$FILE" >&2; then
      exit 2
    fi
    ;;
  *.sh)
    if ! shellcheck "$FILE" >&2; then
      exit 2
    fi
    ;;
esac

exit 0
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/after-edit.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

اطلب من Claude إضافة دالة ذات مسافات بادئة غير صحيحة إلى ملف Python، ثم افتح الملف. سيظهر الملف منسقاً. هذا يؤكد أن الـhook عمل، لأن الـhook الناجح لا يعرض شيئاً في المحادثة.

لا يلغي exit 2 أي شيء هنا. يعمل PostToolUse بعد تنفيذ الأداة فعلياً، لذلك يبقى التعديل على القرص في كلتا الحالتين. ما يتيحه exit 2 هو وصول مخرجات ruff check إلى النموذج كتعليقات، فيصحح الخطأ الذي أدخله للتو بدلاً من المتابعة. هذا هو الفرق بين فشل تدقيق تكتشفه وقت commit وفشل يصلحه الـagent في الدور نفسه.

هناك حدّان مهمان للمطابقة هنا. لا يرى Edit|Write الملفات التي غيّرها أمر shell، ويكتب Claude الملفات عبر Bash بما يكفي لجعل هذه الفجوة فعلية. لتغطية كل استدعاء، طابِق Bash أيضاً، واجعل النص البرمجي يسرد الملفات التي تغيّرت باستخدام git status --porcelain. ولتغطية مرة واحدة في كل دور، ضع الفحص في hook من النوع Stop بدلاً من ذلك.

مثال 3: تسجيل كل استدعاء لأداة لأغراض التدقيق

يطابق الشرط الفارغ في PostToolUse كل أداة. يؤدي إرسال السجل إلى system journal بدلاً من ملف في الدليل الرئيسي إلى إبقائه بعيداً عن shell الخاص بالوكيل:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{time: now|todate, session: .session_id, cwd: .cwd, tool: .tool_name, input: .tool_input}' | logger -t claude-code -p local0.info"
          }
        ]
      }
    ]
  }
}

اقرأ السجل مجدداً باستخدام journalctl -t claude-code -o cat | tail -n 5. يجب أن ترى سطراً واحداً بتنسيق JSON لكل استدعاء لأداة، مع ظهور الأحدث أخيراً. يعني عدم ظهور أي شيء أن hook لم يعمل، ويتناول قسم استكشاف الأخطاء وإصلاحها أدناه هذه الحالة.

أضف الكتلة نفسها ضمن PostToolUseFailure لالتقاط الاستدعاءات التي فشلت، لأن PostToolUse يعمل عند النجاح فقط، بينما يكون الأمر الفاشل هو الأكثر أهمية عادةً. والسبب في استخدام logger بدلاً من الإلحاق بملف في الدليل الرئيسي هو الملكية: يعمل hook باستخدام المستخدم نفسه الذي يعمل به shell الخاص بالوكيل، ولذلك فإن أي شيء يمكن لذلك المستخدم إلحاق البيانات به يمكنه أيضاً تفريغه. يكتب systemd-journald السجل باستخدام حسابه الخاص.

المدة التي يمكن أن يعمل فيها hook

ChartDefault hook timeout in seconds, by hook type and event
The data behind this chart
[
  {
    "label": "command, http or mcp_tool hook",
    "default_timeout_seconds": 600
  },
  {
    "label": "agent hook",
    "default_timeout_seconds": 60
  },
  {
    "label": "prompt hook",
    "default_timeout_seconds": 30
  },
  {
    "label": "command hook on UserPromptSubmit",
    "default_timeout_seconds": 30
  },
  {
    "label": "command hook on MessageDisplay",
    "default_timeout_seconds": 10
  },
  {
    "label": "any hook on SessionEnd",
    "default_timeout_seconds": 1.5
  }
]

يحصل hook للأوامر على 600 ثوانٍ افتراضياً، أي عشر دقائق. وتقلل بعض الأحداث هذه المدة بشدة. تتشارك hooks من نوع SessionEnd ميزانية قدرها 1.5 ثانية فيما بينها، لذلك يجب أن تكون عملية التنظيف في نهاية الجلسة سريعة. لكن ضبط timeout لمدة أطول في hook يرفع هذه الميزانية المشتركة إلى المدة نفسها، بحد أقصى 60 ثانية.

يُلغى hook الذي يتجاوز مهلة الانتظار، ولا ينتج عنه أي قرار. بالنسبة إلى حاجز حماية من نوع PreToolUse، يعني ذلك أنه لا يحظر العملية؛ إذ تتابع استدعاء الأداة مسار الصلاحيات العادي. لذلك اجعل نصوص guardrail صغيرة. أما العمليات البطيئة التي لا ينتظرها أحد، مثل إرسال سجل إلى مكان آخر، فضبط "async": true لها يجعل hook يعمل في الخلفية من دون تعطيل استدعاء الأداة.

الـHooks وملفات القواعد والمهارات وخوادم MCP

تختلط أربعة أشياء معاً لأنها تغيّر جميعاً سلوك الـagent. لكن واحداً منها فقط لا يبقى مجرد اقتراح.

ملف القواعد (CLAUDE.md، أو ملف ضمن .claude/rules/) هو نص يُحمَّل إلى سياق النموذج. وهو يوجّه السلوك، لكنه لا يفرض شيئاً. عند وضعه في مواجهة محادثة طويلة، وdiff كبير، وطلب جديد من المستخدم، قد يفقد سطر واحد منه أولوية التنفيذ. وهذه هي الآلية المعتادة وراء تجاهل الـagents للتعليمات التي كتبتها.

المهارة هي مجلد يضم تعليماتاً وscripts، ويحمّله النموذج عندما يقرر أن المهارة ذات صلة. هذا القرار هو جوهر المهارة، وهو أيضاً حدّها: فما زال النموذج هو من يقرر. يمكنك رؤية الجانبين في مهارة مثل Ponytail، التي تدفع الـagent نحو أصغر تغيير ينجح، لأنها تشكّل طريقة التعامل مع المهمة بأكملها بأسلوب لا يستطيع أي hook تقديمه، وذلك فقط عندما يختار النموذج تحميلها.

يوفّر خادم MCP (model context protocol) للنموذج أدوات جديدة لاستدعائها. وهو يوسّع نطاق ما يستطيع الـagent الوصول إليه. لكنه لا يجعله يستخدم أي أداة، كما أنه عملية منفصلة يجب عليك تشغيلها وإدارتها، وهذا عمل مستقل بحد ذاته: راجع تشغيل خوادم MCP على VPS.

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

الـplugin هو طريقة لتجميع المكونات، وليس آلية خامسة. فهو يجمع hooks مع مهارات في وحدة واحدة قابلة للتثبيت، وهي الطريقة التي يوزّع بها الفريق وسيلة الحماية نفسها على كل machine: راجع كيفية عمل plugins في Claude Code.

قرار الأمان على VPS مشترك

الـhook هو تعليمات برمجية يشغّلها الوكيل، وتعمل بصلاحيات المستخدم الذي بدأ Claude Code. وترث بيئة ذلك المستخدم وأذونات ملفاته. على حاسوب محمول، هذه مسألة تتعلق بسير العمل. أما على VPS يعمل فيه وكيل دون إشراف، فهي مسألة أمنية تتكون من أربعة جوانب عملية.

الـhook الموجود في مستودع هو تعليمات برمجية لم تكتبها أنت. .claude/settings.json يكون مضمّناً في المستودع، لذلك يمكن لاستنساخ مستودع وبدء جلسة داخله أن يسجّل hooks جاءت مع المستودع. يضع Claude Code hooks الخاصة بالمشروع خلف مربع حوار الثقة بمساحة العمل لهذا المجلد، ما يعني أن قبول الثقة هو اللحظة التي تقرر فيها تشغيلها. اقرأ كتلة hooks أولاً.

يمكن للـhook رؤية كامل إدخال الأداة. يسجّل hook للتدقيق يستخدم tool_input كل وسيطة لكل أمر في ملف، بما في ذلك أي token صادف وجوده في سطر الأوامر. ويحتاج هذا السجل بعد ذلك إلى الحماية نفسها التي يحتاج إليها السر، وهذا جزء من المشكلة الأوسع المتمثلة في إبعاد الأسرار عن متناول وكيل الذكاء الاصطناعي.

يمكن للـhook الكتابة في سياق النموذج. كل ما يطبعه hook من النوع SessionStart أو UserPromptSubmit إلى stdout يُضاف إلى المحادثة. والـhook الذي يمرر نصاً من مصدر خارجي، أو من متعقّب issues أو ملف سجل، يسلّم النموذج نصاً غير موثوق كما لو أنك كتبته بنفسك. والـhook الذي يمرر ملاحظة من جلسة Claude Code أخرى على VPS نفسه يفعل الشيء نفسه، ولا تتمتع مخرجات وكيل بأفضلية في الثقة على مخرجات متعقّب issues. تعامل مع stdout هذا على أنه إدخال لا مخرجات.

الامتيازات هي وسيلة التحكم الفعلية. شغّل الوكيل باستخدام مستخدم مخصص غير مميز، ولا تمنحه إلا قواعد sudo التي يحتاج إليها. يستحق رفض PreToolUse الإبقاء عليه، وهو مصمم ليكون إجراءً بأفضل جهد؛ إذ يذكر المرجع الأمر نفسه بشأن مرشح if، ويوجّهك إلى استخدام نظام الأذونات عندما تحتاج إلى رفض قاطع. قواعد الأذونات والحساب الذي تعمل العملية تحته هما العنصران اللذان يصمدان عند الضغط.

هناك خاصية واحدة تسري في كل إعداد. تعمل hooks من نوع PreToolUse قبل فحص وضع الأذونات في كل وضع من أوضاع الأذونات، لذلك يمنع hook يعيد deny الأداة حتى عند استخدام bypassPermissions. يمكن للـhooks تشديد ما تسمح به قواعد الأذونات. لكنها لا تستطيع تخفيفه.

لماذا لا يُنفَّذ hook لدي؟

نفّذ الخطوات بالترتيب. توضّح كل خطوة العَرَض الذي ستراه فعلياً.

  • شغّل /hooks وتحقق من ظهور hook ضمن الحدث المتوقع. يشير غياب hook من القائمة عادةً إلى وجود خطأ في صياغة JSON داخل ملف الإعدادات، لأن الفواصل الزائدة والتعليقات غير مسموحة، أو إلى أن الملف ليس في أحد المواقع الستة المذكورة أعلاه.
  • طابق matcher مع اسم الأداة حرفياً. Matcher حساس لحالة الأحرف، لذلك لا يطابق "bash" الأداة Bash مطلقاً.
  • شغّل السكربت يدوياً باستخدام إدخال نموذجي، كما في المثال 1 أعلاه. يشير رمز خروج غير متوقع إلى وجود خطأ في السكربت، ويعرضه Claude Code على أنه خطأ في hook لا على أنه قرار.
  • يعني الإشعار jq: command not found أن jq غير موجود على ذلك الجهاز. ويعني command not found الخاص بالسكربت الذي كتبته أن المسار تعذّر حله، لذلك استخدم ${CLAUDE_PROJECT_DIR} أو مساراً مطلقاً. إذا لم يُشغَّل السكربت إطلاقاً، فمن المحتمل أنه غير قابل للتنفيذ.
  • يطبع hook JSON صالحاً، لكن لا يحدث شيء. يعمل hook بصيغة shell عبر sh -c، وإذا كان ملف تعريف shell يطبع banner، فستُضاف تلك الرسالة قبل JSON. عندئذ لا يبدأ stdout بـ{، فيقرأ Claude Code المحتوى كله كنص عادي ويتجاهل القرار. عند الخروج بالرمز 0، لا يُبلَّغ عن شيء في أي مكان باستثناء سجل التصحيح. ضع أي echo في ملف تعريفك ضمن شرط لا ينفّذه إلا في جلسات shell التفاعلية.
  • إذا استمرت المشكلة، ابدأ الجلسة باستخدام claude --debug-file /tmp/claude.log وشغّل tail -f /tmp/claude.log في طرفية ثانية. يسجل سجل التصحيح أي hooks طابقت، ورمز الخروج الذي أعاده كل hook، وكل ما كتبته إلى stdout وstderr.

FAQ

ما الفرق بين hook في Claude Code وتعليمة CLAUDE.md؟

تعليمة CLAUDE.md هي نص في سياق النموذج، ولذلك تنافس المحادثة والطلب الحالي على الانتباه، ويمكن للنموذج أن يوازن بينها وبينهما. أما hook فهو أمر shell يشغّله Claude Code عند نقطة محددة في دورة حياته، ولذلك يُنفَّذ عند كل وقوع للحدث المرتبط به، بصرف النظر عما قرره النموذج. استخدم التعليمة للتفضيلات. واستخدم hook لخطوة يجب أن تحدث دائماً أو لإجراء يجب ألا يحدث مطلقاً.

كيف أوقف Claude Code عن تشغيل أمر shell محدد؟

سجّل hook من النوع PreToolUse مع matcher من النوع Bash يقرأ الأمر من .tool_input.command، ويكتب سبب الرفض إلى stderr، ثم ينهي التنفيذ بالرمز 2. يلغي Claude Code الاستدعاء ويعرض السبب للنموذج. يحدث ذلك قبل التحقق من وضع الصلاحيات، ولذلك يظل الرفض سارياً حتى في وضع bypassPermissions. يُعدّ مطابقة نمط لسلسلة أمر إجراءً وقائياً، وليس حداً أمنياً، لأن الأمر نفسه يمكن كتابته بصيغة لا يطابقها النمط. لذلك، ادعمها بقواعد الصلاحيات وبحساب غير مميّز.

يطبع hook لدي JSON صالحاً، لكن لا يحدث شيء. لماذا؟

السبب الأكثر شيوعاً هو ملف تعريف shell. يعمل hook الذي لا يتضمن الحقل args عبر sh -c، وقد تطبع بعض ملفات التعريف banner عند كل تشغيل shell، فيظهر ذلك في stdout قبل JSON. وبما أن الناتج لا يبدأ بالرمز {، يتعامل Claude Code مع الناتج كله كنص عادي ويتجاهل القرار. وعند الخروج بالرمز 0، لا يُبلَّغ عن أي شيء في السجل النصي للمحادثة. ضع أي echo في ملف التعريف ضمن اختبار للتحقق من تشغيل shell التفاعلي، ثم تأكد من الإصلاح بقراءة سجل التصحيح من claude --debug-file /tmp/claude.log.

هل من الآمن تشغيل hooks الخاصة بـClaude Code على خادم مشترك؟

تعمل hooks بحساب المستخدم الذي بدأ Claude Code، وبصلاحيات ملفاته، ولذلك يمكن لـhook تنفيذ أي إجراء يستطيع ذلك الحساب تنفيذه. تغطي عادتان معظم المخاطر: شغّل الوكيل بحساب مخصص غير مميّز مع سياسة sudo مقيّدة، واقرأ كتلة hooks في أي مستودع قبل قبول مربع حوار الثقة في مساحة العمل، لأن hooks الخاصة بالمشروع تأتي داخل .claude/settings.json. عيّن "disableAllHooks": true في ملف الإعدادات عندما تريد منع تشغيل أي منها.