---
title: 'إخفاقات Claude Code Hook: تم اختبار كتلة onFailure'
description: تعمل خطافات Claude Code بنظام الفشل المفتوح في معظم الأحداث. لقد اختبرت كتلة onFailure عبر أربعة إصدارات مع حالات مثل السكربتات المفقودة، وexit 1، ومهلات الانتظار، وأخطاء المخطط.
url: https://nerdleveltech.com/ar/claude-code-hook-fail-closed-onfailure-tested
language: ar
date: '2026-10-13'
category: security
tags:
  - claude code
  - claude code hooks
  - onFailure
  - ai agent safety
  - agentic coding
  - fail closed
  - developer tools
markdown_url: https://nerdleveltech.com/ar/claude-code-hook-fail-closed-onfailure-tested.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/ar/claude-code-hook-fail-closed-onfailure-tested'
terms: https://nerdleveltech.com/llms.txt
---

# إخفاقات Claude Code Hook: تم اختبار كتلة onFailure

أضاف [Claude Code](https://code.claude.com/docs/en/overview) الإصدار 2.1.295 خيار `onFailure: "block"` لخطافات (hooks) الأوامر وHTTP.[^1] وبدونه، في معظم الأحداث، فإن الخطاف الذي يفشل أو ينتهي وقته أو لا يبدأ أبداً يسمح باستمرار الإجراء.[^2]

في اختباراتي، قام الإصداران 2.1.295 و 2.1.296 بحظر هذه الإخفاقات. أما الإصداران 2.1.294 و 2.1.287 فقد تجاهلا هذا الإعداد.

## ملخص

- بشكل افتراضي، فإن خطاف السياسة الذي يحتوي على خطأ مطبعي في مساره لا يوقف الإجراء في معظم الأحداث. لقد أعدت إنتاج هذه الحالة في الإصدار 2.1.296 باستخدام سكربت مفقود، ورمز خروج (exit code) بقيمة 1، وانتهاء وقت التنفيذ، مع السماح المسبق بـ Bash حتى لا يظهر طلب الإذن.
- يقوم `"onFailure": "block"` بتحويل كل حالة من هذه الحالات إلى استدعاء أداة محظور أو طلب محظور. وفي خطاف `UserPromptSubmit`، قام أيضاً باكتشاف ملفات JSON التي يتم تحليلها ولكنها تكسر مخطط الخطاف (hook schema)، والنصوص المغلفة بأقواس والتي لا تعد JSON صالحاً، واتصال HTTP مرفوض.
- الإصداران الأقدم اللذان اختبرتهما، 2.1.294 و 2.1.287، تجاهلا هذا الحقل. وفي حالات Bash، لم تذكر مخرجاتهما هذا الإعداد أبداً، لذا فإن الإعدادات التي تبدو آمنة في تثبيت قديم قد لا تكون كذلك في الواقع.
- رمز الخروج 2 لا يزال يحظر الإجراء عن قصد. استخدمه للرفض الفعلي، واترك `onFailure` للحالات التي يتعطل فيها الخطاف نفسه.
- تتعامل الوثائق مع مخرجات stdout لخطاف الأوامر غير المتزامن (non-async) على أنها JSON فقط عندما تكون كائناً واحداً يبدأ بـ `{` وينتهي بـ `}`. أما الجزء المبتور مثل `{"a":1,` فيعتبر نصاً عادياً، لذا في `UserPromptSubmit`، حيث اختبرته، فإنه يمر حتى مع ضبط `block` إذا خرج السكربت بالقيمة 0.[^2]

## ما ستتعلمه

- ما هو `onFailure` وأي أنواع الخطافات تدعمه
- كيف اختبرته، وما الذي تم تشغيله على كل إصدار
- جداول النتائج الكاملة لخطافات `UserPromptSubmit` وخطافات أدوات Bash
- ثلاثة سلوكيات تستحق المعرفة، بما في ذلك نقطة عمياء لأجزاء JSON ناتجة عن الوثائق
- سكربت حماية صغير وملف إعدادات يمكنك نسخه
- ما الذي لا يغطيه هذا الحقل

## ماذا يفعل onFailure

`onFailure` هو حقل في تعريف الخطاف ضمن إعدادات Claude Code. وهو يقبل القيمة `"continue"` (الافتراضية)، أو `"block"`، وتذكره الوثائق لخطافات `command` و `http` فقط.[^2]

جاء في سجل التغييرات [changelog](https://code.claude.com/docs/en/changelog) الخاص بشركة Anthropic للإصدار 2.1.295، بتاريخ 8 أكتوبر 2026: "تم إضافة `onFailure: "block"` لخطافات الأوامر وHTTP: الخطاف الذي لا يمكنه البدء، أو ينتهي وقته، أو يخرج برمز غير متوقع يقوم بحظر الإجراء بدلاً من السماح بمروره".[^1]

المشكلة التي يحلها مذكورة بوضوح في [مرجع الخطافات](https://code.claude.com/docs/en/hooks): "في معظم الأحداث، عندما يفشل الخطاف أو ينتهي وقته، يستمر Claude Code في تنفيذ الإجراء، لذا فإن خطاف السياسة بمسار خاطئ أو سكربت يتعطل يسمح بمرور كل شيء".[^2]

هذا تصميم يعتمد على "الفشل المفتوح" (fail-open). من وجهة نظري، هذا يناسب خطاف التنبيهات، ولكنه غير مناسب لخطاف وظيفته هي قول "لا".

تعتبر الوثائق خمسة أشياء بمثابة فشل: أمر لا يمكن بدؤه، رمز خروج غير 0 أو 2، خطأ في اتصال HTTP أو حالة غير 2xx، انتهاء وقت التنفيذ، ومخرجات غير صالحة. أما مخرجات stdout النصية العادية من خطاف الأوامر فهي صراحةً لا تعتبر فشلاً.[^2]

عندما يتم تعيين `block`، فإن الفشل يقوم بما يفعله رمز الخروج 2 عند وقوع هذا الحدث، باستثناء في `PermissionRequest`، حيث يتم رفض الطلب. يؤدي فشل خطاف (hook) `PreToolUse` إلى حظر استدعاء الأداة، ويؤدي فشل خطاف `UserPromptSubmit` إلى حظر المطالبة.[^2]

لمعرفة كيفية تكامل الخطافات ومطالبات الموافقة في [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview)، راجع [دليل التدخل البشري (human-in-the-loop)](https://nerdleveltech.com/human-in-the-loop-claude-agent-sdk-typescript-tutorial) الخاص بنا. وبالنسبة للمكافئ من جانب الإضافة (plugin)، راجع ملاحظة `.catch` في منشورنا حول [حالات استخدام تعديلات Claude Code](https://nerdleveltech.com/claude-code-mods-use-cases-examples).

## كيف قمت باختبار ذلك

قمت بتثبيت أربعة إصدارات من [npm](https://www.npmjs.com/package/@anthropic-ai/claude-code) في مجلدات منفصلة: `@anthropic-ai/claude-code` بالإصدارات 2.1.296 و 2.1.295 و 2.1.294 و 2.1.287. ويقوم كل منها بالإبلاغ عن إصداره باستخدام `claude --version`.

يظهر السجل أن الإصدار 2.1.287 نُشر في 1 أكتوبر 2026، و 2.1.294 في 8 أكتوبر، و 2.1.295 في 8 أكتوبر، و 2.1.296 في 9 أكتوبر.[^3] الإصداران الأقدم يسبقان الإصدار الذي أضاف هذا الحقل، لذا فهما يعملان كمجموعات ضابطة.

وكإجراء للتحقق المتقاطع، يظهر النص `blocking because onFailure` في الملفات الثنائية للإصدارين 2.1.295 و 2.1.296 ولا يظهر في أي من الإصدارين الأقدم.

تم تشغيل كل حالة باستخدام `claude -p` مع تمرير ملف إعدادات عبر `--settings`، على نظام Linux x64 مع [Node](https://nodejs.org) إصدار v22.22.0. وكان لكل خطاف في المصفوفة مهلة زمنية قدرها 3 ثوانٍ.

كانت سكربتات الخطاف عبارة عن ملفات Node صغيرة: أحدها يخرج بالرمز 0، وآخر يخرج بالرمز 1 بعد الكتابة في stderr، وآخر يخرج بالرمز 2، وآخر ينتظر لمدة 30 ثانية، وآخر يطبع نصاً عادياً، والعديد منها يطبع مخرجات تشبه JSON بأشكال مختلفة. وهناك حالة إضافية تشير إلى مسار سكربت غير موجود، وأخرى تستخدم خطاف HTTP موجه إلى منفذ مغلق.

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

- **UserPromptSubmit.** يعمل الخطاف قبل أن يعالج النموذج المطالبة.[^2] تعيد المطالبة المحظورة النص `UserPromptSubmit operation blocked by hook`. أما المطالبة المقبولة فتحصل على رد طبيعي.
- **PreToolUse على Bash.** طلبت من النموذج تشغيل `touch <marker file>` باستخدام `--allowedTools Bash`، حتى لا تعترض مطالبة الإذن الطريق. ثم تحققت من شيئين: ما إذا كان ملف العلامة موجوداً بعد ذلك، وماذا قالت `tool_result` في تدفق JSON.

اعتمد التحقق من العلامة على قيام النموذج بنسخ مسار الملف بشكل صحيح. ولهذا السبب تعتبر `tool_result` هي وسيلة التحقق المتقاطع.

المحاولة الأولى التي أعطى فيها التحقق من العلامة إجابات خاطئة كانت بسبب إعدادات الاختبار الخاصة بي، لذا قمت باستبعادها.

إليك ما تم تشغيله وأين. تم تشغيل المصفوفة الكاملة على الإصدارين 2.1.296 و 2.1.287، باستثناء صفوف Bash الخاصة بقرار السماح والخروج بالرمز 2، والتي تم تشغيلها على 2.1.296 فقط.

على الإصدارين 2.1.295 و 2.1.294، قمت بتشغيل مجموعات أقصر.

بالنسبة للإصدار 2.1.295، وهو الإصدار الذي أضاف الحقل، كانت هناك ست سلوكيات لـ `UserPromptSubmit` وأربعة سلوكيات لـ Bash، تشغيل واحد لكل منها، بالإضافة إلى حالة "تعذر البدء" أدناه. وبالنسبة للإصدار 2.1.294، وهو أقرب إصدار قبل التغيير، كانت هناك ثمانية سلوكيات لـ `UserPromptSubmit` وحالات Bash لسكربت مفقود، والخروج بالرمز 1، ومهلة زمنية.

تم تشغيل كل حالة `UserPromptSubmit` على 2.1.296 و 2.1.287 مرة واحدة في المصفوفة، باستثناء الحالات ذات شكل JSON في أسفل الجدول الأول، والتي تم تشغيلها من مرتين إلى خمس مرات. وتأتي نتائج Bash من سكربت الاختبار المصحح، الذي قام بتشغيل كل حالة مرتين بنفس النتيجة في المرتين، باستثناء الحالتين المحددتين بتشغيل واحد.

```bash
npm install @anthropic-ai/claude-code@2.1.296
claude -p "say hi" --settings ./settings.json --output-format stream-json --verbose --include-hook-events < /dev/null
claude -p "Use the Bash tool to run exactly this command and nothing else: touch /tmp/marker" --settings ./settings.json --allowedTools Bash --output-format stream-json --verbose --include-hook-events < /dev/null
```

## النتائج في لمحة سريعة

توضح الشبكة أدناه كل صف من الجدولين.

اللون الفيروزي يعني أن الـ hook أوقف المطالبة (prompt) أو الأمر، والبرتقالي يعني أن هناك فشلاً قد مر، والكهرماني يعني نصاً عادياً، وهو ما لا تعتبره الوثائق فشلاً، والرمادي يعني أن الحالة نجحت كما ينبغي أو أنني لم أختبرها.

القسمان التاليان يحتويان على نفس البيانات الموجودة في الجداول.

![شبكة من نتائج الاختبار. لكل سلوك hook، ما إذا كان Claude Code 2.1.296 و 2.1.287 قد أوقفا مطالبة UserPromptSubmit أو أمر Bash تحت الإعداد الافتراضي وتحت كتلة onFailure. في إصدار 2.1.296 مع block، تم إيقاف السكربت المفقود، و exit 1، و timeout، و JSON صالح ثم exit 1، و JSON غير صالح للمخطط (schema-invalid)، واتصال HTTP مرفوض، و JSON غير صالح محاط بأقواس. في إصدار 2.1.287، أدى exit 2 فقط إلى إيقاف أي شيء.](https://nerdleveltech.com/images/claude-code-hook-fail-closed-onfailure-tested-results.png)

*تم نقل الخلايا من المخرجات المحفوظة لتشغيلات الاختبار الخاصة بي في 13 أكتوبر 2026، وتم التحقق منها. الخلايا المحددة بـ "not tested" هي حالات لم أقم بتشغيلها على إصدار 2.1.287. إصدارات 2.1.295 و 2.1.294 قامت بتشغيل مجموعات أقصر وتطابقت مع 2.1.296 و 2.1.287.*

## النتائج: hooks الخاصة بـ UserPromptSubmit

يعتبر الـ hook من نوع `UserPromptSubmit` أسرع طريقة لرؤية العمل الميداني، حيث أن المطالبة المحظورة تتوقف قبل أن يجيب النموذج. "Passed" تعني أن النموذج أجاب. "Blocked" تعني أن الـ hook أوقف المطالبة.

| سلوك الـ Hook                            | 2.1.296 الافتراضي | 2.1.296 `block` | 2.1.287 الافتراضي | 2.1.287 `block` |
| ---------------------------------------- | ----------------- | --------------- | ----------------- | --------------- |
| Exits 0                                  | Passed            | Passed          | Passed            | Passed          |
| مسار السكربت غير موجود                   | Passed            | Blocked         | Passed            | Passed          |
| Exits 1 مع رسالة stderr                  | Passed            | Blocked         | Passed            | Passed          |
| Exits 2 مع رسالة stderr                  | Blocked           | Blocked         | Blocked           | Blocked         |
| تجاوز مهلة الـ 3 ثوانٍ (Sleeps)          | Passed            | Blocked         | Passed            | Passed          |
| يطبع JSON صالح، ثم exits 1               | Passed            | Blocked         | Passed            | Passed          |
| يطبع JSON يفشل في المخطط، exits 0        | Passed            | Blocked         | Passed            | Passed          |
| HTTP hook، الاتصال مرفوض                 | Passed            | Blocked         | Passed            | Passed          |
| يطبع نصاً عادياً، exits 0                | Passed            | Passed          | Passed            | Passed          |
| يطبع `{not json}`، exits 0               | Passed            | Blocked         | Passed            | Passed          |
| يطبع `{not json` بدون قوس إغلاق، exits 0 | Passed            | Passed          | Passed            | Passed          |
| يطبع نصف كائن، `{"a":1,`، exits 0        | Passed            | Passed          | Passed            | Passed          |

صف "valid JSON, then exit 1" يتطابق مع الوثائق. عند ضبط `block`، فإن أي رمز خروج (exit code) غير 0 أو 2 يعتبر فشلاً حتى لو قام الـ hook بطباعة JSON صالح.[^2]

جدول Bash أدناه يحتوي على الإصدار الذي يطبع قرار السماح (allow decision).

استخدم صف المخطط (schema) القيمة `{"decision":"maybe"}` وفي متغير ثانٍ، قيمة `additionalContext` قدرها `5`. كانت نتيجة الـ hook في تدفق الأحداث (event stream) هي `error` في كلتيهما، وتم حظرهما تحت `block`.

صف `{not json}` هو فشل في التحليل (parse failure) وليس فشلاً في المخطط. كما أبلغ عن `error` وتم حظره.

## النتائج: hooks على استدعاءات أدوات Bash

هذه هي الحالة التي تحدد ما إذا كان الأمر سيعمل. "Ran" تعني أن ملف العلامة (marker file) كان موجوداً وكانت نتيجة الأداة ناجحة. "Not run" تعني عدم وجود ملف علامة ونتيجة أداة تحمل خطأ الـ hook.

| سلوك الـ Hook                                   | 2.1.296 الافتراضي | 2.1.296 `block` | 2.1.287 الافتراضي | 2.1.287 `block` |
| ----------------------------------------------- | ----------------- | --------------- | ----------------- | --------------- |
| خروج بكود 0                                     | تم التشغيل        | تم التشغيل      | تم التشغيل        | تم التشغيل      |
| مسار السكريبت غير موجود                         | تم التشغيل        | لم يتم التشغيل  | تم التشغيل        | تم التشغيل      |
| خروج بكود 1                                     | تم التشغيل        | لم يتم التشغيل  | تم التشغيل        | تم التشغيل      |
| تجاوز مهلة الانتظار (3 ثوانٍ)                   | تم التشغيل        | لم يتم التشغيل  | تم التشغيل        | تم التشغيل      |
| طباعة قرار بالسماح، ثم خروج بكود 1 (تشغيل واحد) | تم التشغيل        | لم يتم التشغيل  | لم يتم اختباره    | لم يتم اختباره  |
| خروج بكود 2 (تشغيل واحد)                        | لم يتم التشغيل    | لم يتم التشغيل  | لم يتم اختباره    | لم يتم اختباره  |

عندما يتم الحظر، يكون نص الخطأ محددًا. بالنسبة للسكريبت المفقود، كان النص `PreToolUse:Bash hook error: [node .../missing.js]: failed; blocking because onFailure is "block"`، متبوعًا بـ stack trace الخاص بـ Node.

بالنسبة للسكريبت الذي يتجاوز المهلة، تم استبدال كلمة `failed` بكلمة `timed out`. وهذا هو الصياغة التي تعد بها الوثائق.[^2]

تستخدم صفوف السكريبت المفقود `node` مع مسار سكريبت غير موجود، لذا يبدأ Node ثم يخرج بكود 1. وهذا يتطابق مع الاختبار الخاص بالوثائق، والذي يترك السكريبت مفقودًا.[^2]

تدرج الوثائق السكريبت المفقود أو الملف التنفيذي المفقود كأمثلة على hook لا يمكنه البدء.[^2] قمت أيضًا بتشغيل مسار أمر غير موجود، على Bash فقط، تشغيل واحد لكل منهما. عند التشغيل عبر الـ shell، خرج بكود 127.

بالنسبة لمسار الأمر غير الموجود، لم يتم تشغيل الأمر تحت `block` في الإصدارين 2.1.296 و 2.1.295، بينما تم تشغيله تحت `block` في الإصدارين 2.1.294 و 2.1.287. ومع الإعداد الافتراضي في 2.1.296، تم التشغيل.

![لقطة شاشة لنص tool\_result الذي أعاده Claude Code 2.1.296 لاستدعاء Bash عندما كان سكريبت الحماية مفقودًا وكان onFailure هو block: is\_error true، والسطر PreToolUse:Bash hook error متبوعًا بـ failed; blocking because onFailure is "block"، ثم MODULE\_NOT\_FOUND stack trace الخاص بـ Node.](https://nerdleveltech.com/images/claude-code-hook-fail-closed-onfailure-tested-blocked-call.png)

*نص `tool_result` من تشغيل حقيقي في 13 أكتوبر 2026، معروض كصورة من المخرجات الملتقطة. لم يتم إنشاء ملف العلامة (marker file).*

أعمدة الإصدارات هي النقطة الأهم. في الإصدار 2.1.295، تطابقت كل حالة قمت بتشغيلها مع 2.1.296.

في حالات 2.1.287 التي قمت بتشغيلها، لم يكن لإعداد `block` أي تأثير. وفي حالات Bash التي أعدت تشغيلها على كلا الإصدارين الأقدم، لم تذكر المخرجات أبدًا `onFailure` وكان stderr فارغًا.

في الإصدار 2.1.294، جميع حالات `UserPromptSubmit` للسكريبت المفقود، والخروج بكود 1، وتجاوز المهلة، وخطأ في المخطط (schema error)، و `{not json}`، ورفض اتصال HTTP قد مرت مع ضبط `block`. أما الخروج بكود 2 فظل محظورًا. وحالات Bash للسكريبت المفقود، والخروج بكود 1، وتجاوز المهلة، تم تشغيلها جميعًا.

إذا كان فريقك يتشارك ملف إعدادات وكان بعض الأشخاص يستخدمون إصدارًا أقدم، مثل الإصدارين اللذين اختبرتهما، فإن الملف سيبدو آمنًا هناك ولكنه ليس كذلك.

## ثلاثة سلوكيات تستحق المعرفة

**تعتبر أجزاء JSON كنص عادي.** تشير مراجع الـ hooks إلى أن stdout الخاص بـ command hook غير متزامن (non-async) يتم تحليله كـ JSON عندما تكون المخرجات بالكامل عبارة عن كائن JSON واحد.[^2] المخرجات التي لا تبدأ بـ `{`، أو تبدأ بـ `{` ولكن لا تنتهي بـ `}`، تعتبر نصًا عاديًا.

المخرجات التي تبدأ بـ `{` وتنتهي بـ `}` ولكنها ليست JSON صالحًا تعتبر فشلاً في التحليل (parse failure).[^2]

تضيف الوثائق قاعدة منفصلة لعدة أسطر يتم تحليل كل منها كـ JSON بشكل مستقل، وهو أمر لم أقم باختباره.[^2]

تتبع نتائجي قاعدة الكائن الواحد في `UserPromptSubmit`. مع ضبط `block` على الإصدار 2.1.296، تم حظر `{not json}` في كلتا محاولتيه. أما `{not json` فقد نجحت في جميع محاولاتها الخمس، ونجحت `{"a":1,` في كلتا محاولتيها.

لذا، فإن خطاف (hook) من نوع `UserPromptSubmit` يطبع جزءاً من قراره ولا يزال يخرج بالقيمة 0 يتم تمريره. وهذا يتطابق مع القاعدة الموثقة، ولكن من السهل إغفال ذلك. اجعل السكريبت يخرج بقيمة غير صفرية عند كل مسار خطأ، واترك `onFailure` يتولى الباقي.

**المطالبة المحظورة لا تزال تخرج بالقيمة 0 في عمليات تشغيل `claude -p` الخاصة بي.** عندما قام خطاف `UserPromptSubmit` بحظر مطالبة، استمر `claude -p` في الخروج بالقيمة 0 في فحصي. وقد انطبق ذلك على خطاف يخرج بالقيمة 1 تحت إعداد `block` وعلى خطاف يخرج بالقيمة 2.

شهد [مدونة المطورين في Classmethod](https://dev.classmethod.jp/en/articles/20261009-cc-updates-v2-1-295/) رمز خروج 0 لمطالبة محظورة أيضاً، مع خطاف يخرج بالقيمة 1، في تقريرهم عن نفس الإصدار.[^4] في بيئة CI، اقرأ المخرجات بدلاً من حالة الخروج.

**يمكن أن يكون مهلة الانتظار الافتراضية طويلة.** تذكر الوثائق أن 600 ثانية هي القيمة الافتراضية لخطافات الأوامر، وHTTP، وأدوات [MCP](https://code.claude.com/docs/en/mcp)، والتي تم خفضها إلى 30 ثانية في `UserPromptSubmit` وحدثي تبديل النموذج، وإلى 10 ثوانٍ في `MessageDisplay`.[^2] لقد قمت بضبطها على 3 ثوانٍ في المصفوفة و5 ثوانٍ في اختبار الحماية أدناه.

## سكريبت حماية يفشل في حالة الإغلاق (fails closed)

إليك سكريبت حماية `PreToolUse` صغير. يقوم برفض `rm -rf` عمداً بالقيمة 2 ويسمح بمرور كل شيء آخر. إذا كانت المدخلات ليست JSON صالحاً، يقوم السكريبت برمي خطأ ويخرج بالقيمة 1، وتحت إعداد `block`، فإن الخروج بالقيمة 1 يوقف الاستدعاء، كما توضح الجداول أعلاه.

```js
let raw = "";
process.stdin.on("data", (chunk) => (raw += chunk));
process.stdin.on("end", () => {
  const input = JSON.parse(raw);
  const command = (input.tool_input && input.tool_input.command) || "";
  if (/\brm\s+-rf\b/.test(command)) {
    process.stderr.write("Blocked: rm -rf is not allowed here\n");
    process.exit(2);
  }
  process.exit(0);
});
```

التعبير النمطي (regex) هو مجرد عرض توضيحي. فهو يغفل متغيرات مثل `rm -fr` و `rm -r -f`، لذا تعامل معه كنقطة بداية. احفظه باسم `.claude/hooks/check-command.js` وقم بتوجيه ملف `.claude/settings.json` الخاص بالمشروع إليه:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node",
            "args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js"],
            "timeout": 5,
            "onFailure": "block"
          }
        ]
      }
    ]
  }
}
```

قمت بتشغيل كلا الملفين على الإصدار 2.1.296 في مجلد [git](https://git-scm.com) جديد. عند التشغيل مباشرة عبر Node، خرج `ls` بالقيمة 0، وخرج `rm -rf build` بالقيمة 2 مع الرسالة، وخرج النص `not json` بالقيمة 1.

من خلال Claude Code، تم تشغيل أمر `touch` وتم حظر أمر `rm -rf` برسالة السكريبت الخاصة. وعندما قمت بتغيير اسم السكريبت، تم حظر الاستدعاء بنص `failed; blocking because onFailure is "block"`.

عندما قمت بتغيير `"block"` إلى `"continue"` وغيرت اسم السكريبت مرة أخرى، تم تشغيل `touch`. وهذا هو الوضع الافتراضي للفشل المفتوح (fail-open).

## ما لا يغطيه onFailure

تقول الوثائق أن هذا الحقل ليس له تأثير على خطافات `Stop` و `SubagentStop` و `TaskCompleted` و `TeammateIdle`، لأن رمز الخروج 2 في هذه الأحداث يعيد Claude إلى العمل، ولا يمكن لـ Claude إصلاح خطاف لا يعمل. كما أنه لا يفعل شيئاً لخطافات الأوامر الخلفية التي تضبط `async` أو `asyncRewake`.[^2]

تدرج جداول الحقول هذا الخيار لخطافات `command` و `http` فقط، لذا فإن خطافات المطالبة والوكيل وأدوات MCP لا توثق مثل هذا المفتاح.[^2]

أنا لم أختبر أيًا من هذه الحالات. كما أنني لم أختبر `PostToolUse`، حيث تكون الأداة قد عملت بالفعل بحلول وقت فشل الـ hook، ولا `PermissionRequest`، حيث تذكر الوثائق أن الفشل يرفض الطلب عندما يتم تعيين `block`.[^2]

تم اختبار أخطاء الـ Schema، وJSON غير السليم، وHTTP hooks على `UserPromptSubmit` فقط، وكانت حالة HTTP عبارة عن اتصال مرفوض، وليس استجابة غير 2xx أو رد بطيء.

كود الخروج 1 ونوم لمدة 30 ثانية يمثلان حالات الانهيار (crashes) والتوقف (hangs). لم أختبر عملية تم إنهاؤها قسريًا (killed process).

كل شيء هنا تم تشغيله عبر `claude -p` مقابل النموذج الافتراضي لـ CLI على منصة واحدة، وقمت بقراءة تدفق أحداث JSON لمعظم الحالات بدلاً من الواجهة التفاعلية. النتائج هي عينة وليست ضمانًا.

## قائمة مراجعة للنشر

قراءتنا: تعامل مع هذا كقائمة مراجعة قصيرة بدلاً من إعادة كتابة السياسة.

1. قم بتعيين `"onFailure": "block"` في كل أمر `PreToolUse` أو `UserPromptSubmit` أو HTTP hook يقوم برفض شيء ما.
2. احتفظ بكود الخروج 2 للرفض المتعمد، واترك `onFailure` يغطي الحالات التي يتعطل فيها الـ hook نفسه.
3. قم بتعيين `timeout` يتناسب مع الـ hook. عمل الـ guard الخاص بي تحت مهلة 5 ثوانٍ بدون مشاكل، وهو أقل بكثير من الافتراضي البالغ 600 ثانية.
4. اترك السكريبت مفقودًا لمرة واحدة، كما تقترح الوثائق، وتأكد من رؤية نص `blocking because onFailure`.[^2]
5. ثبّت إصدارًا أدنى. يتطلب الحقل الإصدار 2.1.295 أو أحدث،[^2] وقد تجاهلته النسختان الأقدم اللتان اختبرتهما، دون أي ذكر لـ `onFailure` في مخرجات Bash الخاصة بهما.
6. خطط للجانب السلبي. الـ guard المعطل الآن يمنع المكالمات التي يطابقها، لذا قم بنشر تغييرات الـ hook مع اختبار، كما تفعل مع أي بوابة حماية.

## لماذا يهم هذا في الممارسة العملية

إليك تقرير من العالم الحقيقي. تقرير عن [GitHub issue](https://github.com/anthropics/claude-code/issues/81458) تم فتحه في 26 يوليو 2026 ضد Claude Code 2.1.220 يذكر وجود 6,865 إدخال من نوع `hook_non_blocking_error` في جلسة واحدة، جميعها بكود خروج 127.[^5]

يعزو المبلغ ذلك إلى مسار مشروع غير مقتبس يحتوي على مسافة. يقول إن أحد عشر `PreToolUse` hooks قد تأثرت، وتدرج تفاصيلهم اثني عشر سكريبت، أحدها يسمى `verify-on-stop.sh`.[^5]

لقد قاموا بعد الإدخالات في ملف نص الجلسة، ويقول عنوان المشكلة أنه لم تكن هناك إشارة مرئية.[^5] هذا تقرير مستخدم واحد، وأنا لم أتمكن من إعادة إنتاج المشكلة.

تظهر [نسخة claudeissues.com](https://claudeissues.com/issue/81458-hook-launch-failures-exit-127-are-silent-and-non-blocking-6-865-skipped-guardrai) أن المشكلة أُغلقت كـ "غير مخططة" في 8 سبتمبر 2026.[^5] لم أتحقق من سبب إغلاقها.

إذا كان التقرير دقيقًا، فهو يوضح كيف يبدو الـ fail-open في الممارسة العملية. يتم تكوين الـ guards، وتفشل الـ hooks في التشغيل، والأثر الوحيد الذي وجده المبلغ كان سجلاً في ملف النص.

بالنسبة لنمط حظر الأسرار الذي يحمي هذا، راجع [تعديل Claude Code المختبر الذي يحظر قراءة .env](https://nerdleveltech.com/claude-code-mod-block-env-secrets-tested). وللمخاطر الأوسع المتعلقة بالإضافات، راجع [Plugin4Shell وتحصين وكلاء البرمجة الذكية](https://nerdleveltech.com/plugin4shell-ai-coding-agent-plugin-security).

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

### ماذا يفعل خيار onFailure "block" في Claude Code؟

يجعل هذا الخيار أي أمر فاشل أو HTTP hook يمنع (block) الإجراء بدلاً من السماح له بالاستمرار. يُعتبر الـ hook فاشلاً إذا تعذر تشغيله، أو انتهى وقته (timeout)، أو خرج بكود غير 0 أو 2، أو أعاد خطأ HTTP، أو أنتج مخرجات لا يمكن تحليلها أو تكسر المخطط (schema).[^2]

لا تُعتبر مخرجات stdout النصية البسيطة من command hook فشلاً. القيمة الافتراضية هي `"continue"`.[^2]

### أي إصدار من Claude Code أضاف onFailure؟

الإصدار 2.1.295، الذي تم إصداره في 8 أكتوبر 2026.[^1] في اختباراتي، التزم الإصدار 2.1.295 بهذا الإعداد، بينما تجاهله الإصداران 2.1.294 و 2.1.287.

### هل كود الخروج 1 يمنع (block) الـ hook؟

ليس بشكل افتراضي. تصف الوثائق كود الخروج 1 بدون JSON صالح بأنه خطأ غير مانع (non-blocking error) وتوصي باستخدام الخروج 2 لـ policy hooks.[^2] لقد لاحظت الشيء نفسه في الإصدار 2.1.296: حيث سمح hook بخروج 1 بمرور كل من prompt وأمر Bash حتى أضفت `onFailure: "block"`.

### هل يعمل onFailure على hooks الخاصة بـ prompt أو agent أو MCP tool؟

تدرجه جداول الحقول في الوثائق لـ command و HTTP hooks فقط.[^2] لم أقم باختبار الأنواع الأخرى.

### هل يؤثر onFailure على Stop hooks؟

وفقاً للوثائق، لا. يذكرون أنه ليس له تأثير على hooks من نوع `Stop`، و `SubagentStop`، و `TaskCompleted`، و `TeammateIdle`، أو على background command hooks.[^2] لم أختبر هذه الحالات.

### كيف أتحقق من أن الـ hook الخاص بي يفشل في وضع الإغلاق (fails closed)؟

قم بنقل السكريبت أو إعادة تسميته، واطلب من Claude تشغيل أمر Bash غير ضار، وابحث عن عبارة `failed; blocking because onFailure is "block"` في الخطأ، كما تقترح الوثائق.[^2] ثم استعد السكريبت وتأكد من أن الأمر يعمل.

### هل تُعتبر مخرجات JSON غير الصالحة فشلاً؟

يعتمد ذلك على شكلها. وفقاً للوثائق، بالنسبة لـ non-async command hook، فإن المخرجات التي تبدأ بـ `{` وتنتهي بـ `}` ولكنها ليست JSON صالحاً تُعتبر فشلاً في التحليل (parse failure)، وفي اختباري قام `{not json}` بالمنع (block) تحت إعداد `block`.

أما المخرجات التي لا تبدأ بـ `{` وتنتهي بـ `}` معاً فتُعتبر نصاً عادياً، وقد مرّ كل من `{not json` و `{"a":1,` بنجاح. وتضيف الوثائق قاعدة منفصلة لعدة أسطر يتم تحليل كل منها كـ JSON، وهو ما لم أختبره.[^2]

### هل يغير الـ prompt الممنوع كود الخروج لـ claude -p؟

في فحوصاتي على الإصدار 2.1.296 ظل الكود 0، لذا يجب على وظيفة CI قراءة نص المخرجات بدلاً من حالة الخروج.

## الخلاصة

خطاف السياسة (policy hook) الذي يفشل في وضع "الفتح" (fails open) يتوقف عن فرض القيود في اللحظة التي يتعطل فيها. بدءًا من الإصدار 2.1.295، يغطي إعداد واحد خطافات الأوامر وHTTP، مع الاستثناءات المذكورة أعلاه.

في اختباراتي على الإصدار 2.1.296، استمر العمل في حالة فقدان السكريبت، أو الخروج بكود exit 1، أو انتهاء المهلة (timeout) في كلا نوعي الأحداث. وفي `UserPromptSubmit`، استمر العمل أيضًا في حالة أخطاء المخطط (schema errors)، أو JSON غير صالح محاط بأقواس، أو رفض اتصال HTTP.

أضف هذا الإعداد إلى كل أمر `PreToolUse` أو `UserPromptSubmit` أو خطاف HTTP يقوم بالرفض، وقم بتثبيت الإصدار، واختبر مسار الفشل مرة واحدة.

هناك نقطة عمياء ناتجة عن قاعدة التحليل (parsing rule) الموثقة، وقد ثبت ذلك في اختباري: يتم اعتبار جزء من JSON كنص عادي، لذا اجعل السكريبت يخرج بقيمة غير صفرية في كل مسار خطأ.

## المراجع

[^1]: [Claude Code changelog](https://code.claude.com/docs/en/changelog), Anthropic, version 2.1.295, October 8, 2026. Source for the release date and the changelog wording.

[^2]: [Hooks reference](https://code.claude.com/docs/en/hooks), Claude Code documentation, Anthropic, read October 13, 2026. Source for the field values, failure definitions, exit code 2 behavior, exclusions, JSON parsing rule and timeout defaults.

[^3]: [@anthropic-ai/claude-code on npm](https://www.npmjs.com/package/@anthropic-ai/claude-code), npm registry publish times queried with `npm view`, October 13, 2026. Source for the 2.1.287, 2.1.294, 2.1.295 and 2.1.296 publish dates.

[^4]: [Major Updates in Claude Code v2.1.294\~v2.1.295 - Addition of hooks onFailure: "block" and OSC 7501 Support](https://dev.classmethod.jp/en/articles/20261009-cc-updates-v2-1-295/), DevelopersIO by Classmethod, October 9, 2026. Source for the observation that `claude -p` exited 0 for a `UserPromptSubmit` hook that exits 1, both with and without `onFailure: "block"`.

[^5]: [Hook launch failures (exit 127) are silent and non-blocking: 6,865 skipped guardrail invocations in one session, with no visible signal](https://github.com/anthropics/claude-code/issues/81458), issue 81458 in the anthropics/claude-code repository on GitHub, with its em dash written as a colon. Source for the 6,865 count, the exit code, the unquoted path and the hook count, all the reporter's own. The opened and closing dates and the not-planned state were read on the [claudeissues.com mirror](https://claudeissues.com/issue/81458-hook-launch-failures-exit-127-are-silent-and-non-blocking-6-865-skipped-guardrai).
