از نسخه‌ی 2.1.295 کلاد کد، یک قلاب فرمان که نتواند اجرا شود، از timeout رد شود یا کد خروج غیرمنتظره بدهد، دیگر به‌طور پیش‌فرض از کنار عملیات رد نمی‌شود؛ کلید onFailure: "block" این رفتار را fail-closed می‌کند. لحظه‌ی خواندن: ۱۷ مهر ۱۴۰۵. در این نوشته هر پنج حالت خرابی یک قلاب را روی همین سرور اندازه می‌گیریم و می‌بینیم کدام‌یک واقعاً عملیات را متوقف می‌کند و کدام‌یک بی‌صدا از کنار آن می‌گذرد.

چرا یک قلاب خراب یعنی دروازه‌ی باز

مستندات رسمی کلاد کد می‌گویند تنها کد خروجی که به‌تنهایی یک عملیات را مسدود می‌کند، 2 است. حتی کد 1 که کد استاندارد خطای یونیکس است، در بیشتر رویدادها یک خطای غیرمسدودکننده است و عملیات ادامه پیدا می‌کند. هر کد خروج دیگری هم همین‌طور است: عملیات جلو می‌رود و در گفت‌وگو یک اعلان hook error می‌نشیند.

نتیجه‌ی عملی این قرارداد خطرناک است. مستندات هشدار می‌دهند که وقتی مسیر اسکریپت در settings.json غلط باشد، پوسته با کدی مثل 127 خارج می‌شود و برای بیشتر رویدادها عملیات ادامه می‌یابد. جمله‌ی صریح مستندات این است: یک مسیر غلط، دروازه را بی‌صدا غیرفعال می‌گذارد.

یعنی شما یک نگهبان نوشته‌اید، در فایل پیکربندی ثبتش کرده‌اید، و آن نگهبان هرگز اجرا نشده است. نشانه‌اش هم روی صفحه‌ی گفت‌وگو دیده نمی‌شود، چون کاری که قرار بود نگهبانی کند انجام شده و جلو رفته است.

اندازه‌گیری پنج حالت روی همین سرور

به‌جای توضیح نظری، یک اسکریپت می‌سازیم که همان کاری را می‌کند که کلاد کد با قلاب می‌کند: JSON را از stdin می‌خواند و کد خروج برمی‌گرداند. نسخه‌ی نصب‌شده روی این ماشین را با claude --version بررسی کردم و 2.1.295 را گزارش کرد.

#!/bin/bash
# نگهبان: JSON ورودی قلاب را می‌خواند و فقط دستور rm را می‌بندد
INPUT="$(cat)"
CMD="$(printf '%s' "$INPUT" | jq -r '.tool_input.command')"

if [[ "$CMD" == rm* ]]; then
  echo "نگهبان: این فرمان مجاز نیست" >&2
  exit 2   # تنها کد خروجی که به تنهایی مسدود می‌کند
fi

exit 0   # بی‌تصمیمی: جریان عادی مجوز ادامه پیدا می‌کند

این اسکریپت از مثال مرجع خود کلاد کد گرفته شده است و همان قرارداد را دارد: کد 2 یعنی مسدود، کد 0 یعنی بی‌تصمیمی. ابزار jq نسخه‌ی 1.7 روی این سرور موجود است.

حالا پنج حالت را روی دو ورودی اجرا می‌کنیم: یک فرمان بی‌خطر و یک فرمان خطرناک. خروجی واقعی اجرا این است:

$ bash probe.sh
jq موجود است؟ /usr/bin/jq

--- ۱) قلاب سالم ---
exit=0  stderr=
--- ۲) قلاب سالم، فرمان خطرناک ---
exit=2  stderr=نگهبان: این فرمان مجاز نیست
--- ۳) مسیر قلاب غلط ---
exit=127  stderr=No such file or directory
--- ۴) کد خروج غیرمنتظره ---
exit=99  stderr=explode
--- ۵) قلاب کند (۵ ثانیه) ---
exit=124  stderr=

تفسیر این پنج عدد مهم‌تر از خودشان است. فقط ردیف دوم مسدودکننده است، چون تنها او کد 2 می‌دهد. سه ردیف آخر سه شکست متفاوت‌اند: قلابی که اصلاً اجرا نمی‌شود، قلابی که کدی غیرمنتظره برمی‌گرداند، و قلابی که از مهلت عبور می‌کند. هر سه بدون آن تنظیم تازه، عملیات را متوقف نمی‌کنند.

onFailure دقیقا چه چیزی را عوض می‌کند

یادداشت انتشار 2.1.295 با تاریخ ۸ اکتبر ۲۰۲۶ این تنظیم را برای قلاب‌های فرمان و HTTP اضافه کرده است: قلابی که نتواند اجرا شود، از timeout رد شود یا کد خروج غیرمنتظره بدهد، به‌جای عبور، عملیات را مسدود می‌کند.

حالت خرابیکد خروج اندازه‌گیری‌شدهبدون onFailureبا onFailure: "block"
مسیر اشتباه یا فایل غیرقابل اجرا127ادامه می‌یابدمسدود می‌شود
کد خروج غیرمنتظره99ادامه می‌یابدمسدود می‌شود
عبور از مهلت124ادامه می‌یابدمسدود می‌شود
قصد سیاست (خروج ۲)2مسدود می‌شودمسدود می‌شود

یک نکته‌ی جداگانه که اغلب اشتباه فهمیده می‌شود: برای قلاب فرمانی که با async: true در پس‌زمینه اجرا می‌شود، این مهلت اصلاً اعمال نمی‌شود. برای بقیه‌ی قلاب‌های فرمان، HTTP و ابزار MCP، مقدار پیش‌فرض مهلت 600 ثانیه است و روی سه رویداد حساس‌تر پایین‌تر می‌آید.

مستندات همچنین می‌گویند رویدادهای قلاب استثنا دارند. رویداد PermissionRequest کد خروج 2 را رعایت نمی‌کند و جریان مجوز بدون تغییر ادامه می‌یابد؛ آنجا باید از شیء decision استفاده کنید. برای دیدن جدول کامل رفتار هر رویداد، بخش «Exit code 2 behavior per event» در مرجع قلاب‌ها را بخوانید.

پیکربندی در دو گام

گام اول، نگهبان را در ریشه‌ی پروژه بگذارید و آن را اجرایی کنید. گام دوم، دو فایل پیکربندی زیر را مقایسه کنید؛ تفاوت فقط در دو خط است.

// .claude/settings.json — پیکربندی پیش از ۲.۱.۲۹۵، رفتار fail-open
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/guard.sh" }
        ]
      }
    ]
  }
}
// .claude/settings.json — همان پیکربندی با سیاست fail-closed
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/guard.sh",
            "onFailure": "block",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

برای نصب و به‌روزرسانی خود کلاد کد، دستور زیر کافی است و نسخه‌ی نصب‌شده را بررسی می‌کند:

$ npm install -g @anthropic-ai/claude-code@2.1.295
$ claude --version
2.1.295 (Claude Code)

اگر قلاب شما مهلت کوتاهی دارد، عدد timeout را صریح بنویسید. در غیر این صورت یک نگهبان که به هر دلیل کند شود، بیش از ده دقیقه هر فرمان را معطل می‌کند، چون پیش‌فرض 600 ثانیه است. یک نگهبان که با پیش‌فرض ده دقیقه‌ای اجرا شود، عملاً همان بی‌فایده بودن fail-open را دارد.

دو چیزی که این تنظیم درست نمی‌کند

نخست آنکه این تنظیم یک تنظیم در سطح قلاب است، نه یک تضمین در سطح تیم. اگر کسی این فایل را ویرایش کند، دروازه باز می‌شود و هیچ اعلانی هم نمی‌گیرید. اگر می‌خواهید سیاست در سطح سازمانی اجباری باشد، باید از تنظیمات مدیریت‌شده استفاده کنید؛ راهنمای تنظیمات سلسله‌مراتب اولویت پیکربندی را توضیح می‌دهد.

دوم آنکه در لحظه‌ی نوشتن این نوشته، نام onFailure در هیچ‌کدام از چهار صفحه‌ی رسمی قلاب‌ها، راهنمای قلاب‌ها، تنظیمات و مجوزها نیامده است. من هر چهار صفحه را با پاسخ 200 گرفتم و در متن هر کدام این رشته را جست‌وجو کردم و نتیجه در هر چهار مورد صفر بود. تنها جایی که این کلید مستند شده، یادداشت انتشار نسخه‌ی 2.1.295 است.

پس اگر می‌خواهید مطمئن شوید، به یادداشت تکیه کنید و بعد از فعال کردن، رفتار واقعی را در پروژه‌ی خودتان بسنجید. اگر می‌خواهید بدانید یک قلاب واقعاً دارد کار می‌کند، به نگهبانی که جلوی خواندن فایل اسرار را می‌گیرد نگاه کنید؛ آنجا ورودی واقعی قلاب از خط فرمان خوانده می‌شود، نه از حافظه. برای نمونه‌ی دوم، وصل کردن کلاد کد به ترمینال با قلاب terminalSequence رویدادهایی را نشان می‌دهد که اصلاً قابل مسدود کردن نیستند.

قاعده‌ی عملی که از همه‌ی این اندازه‌گیری‌ها بیرون می‌آید ساده است: اگر یک قلاب قرار است جلوی کاری را بگیرد، آن کار باید در حالت خرابی هم متوقف بماند. پیش‌فرض یعنی باز ماندن درِ دروازه؛ onFailure: "block" یعنی بستن آن.

منابع

  1. یادداشت انتشار کلاد کد برای نسخه‌ی 2.1.295 در ۸ اکتبر ۲۰۲۶: افزودن onFailure به قلاب‌های فرمان و HTTP
  2. مرجع قلاب‌ها: معنای کدهای خروج و اینکه چرا فقط کد ۲ مسدود می‌کند
  3. مرجع قلاب‌ها: جدول رفتار کد خروج ۲ به ازای هر رویداد
  4. مرجع قلاب‌ها: فیلد timeout و مقدارهای پیش‌فرض آن
  5. راهنمای قلاب‌ها: تنظیم اولین قلاب و طراحی نگهبان فرمان
  6. نمونه‌ی مرجع اعتبارسنج فرمان‌های Bash در مخزن رسمی
  7. مرجع تنظیمات: سلسله‌مراتب اولویت پیکربندی و تنظیمات مدیریت‌شده
  8. مرجع مجوزها: جریان عادی تأیید که قلاب در آن دخالت می‌کند
  9. صفحه‌ی انتشارهای رسمی کلاد کد
  10. راهنمای رسمی jq برای خواندن JSON از ورودی قلاب
  11. نگهبانی که جلوی خواندن فایل اسرار را می‌گیرد — نوشته‌ی پیشین سایت
  12. وصل کردن کلاد کد به ترمینال با قلاب terminalSequence — نوشته‌ی پیشین سایت