SSD Nodes Learn Hosting plans →
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-27

راهنمای کامل هوک‌های Claude Code و نحوه عملکرد آن‌ها

با نحوه اجرای خودکار دستورات در Claude Code آشنا شوید. یاد بگیرید چگونه با خروجی کد 2 و ارسال داده به stdin، اجرای tool call را کنترل کرده و امنیت پروژه را تضمین کنید.

هوک Claude Code چیست

هوک‌های Claude Code دستورات shell هستند که Claude Code آن‌ها را به‌طور خودکار در نقاط مشخصی از چرخهٔ حیات خود اجرا می‌کند. این تمام تفاوت بین یک هوک و یک فایل قوانین (rules file) است. دستورالعمل موجود در CLAUDE.md یک توصیه است و مدل آن را در کنار سایر موارد موجود در context خود می‌سنجد. اما هوک، کد است و فارغ از موافقت یا مخالفت مدل، اجرا می‌شود. اگر agent شما همچنان از formatterای که دو بار به آن گفته‌اید صرف‌نظر می‌کند، نیازی به دستورالعمل قاطع‌تر ندارید؛ شما به یک هوک نیاز دارید.

این مکانیزم ساده است. شما یک دستور را در فایل تنظیمات تحت یک نام رویداد (event name) ثبت می‌کنید. هنگامی که آن رویداد رخ می‌دهد، Claude Code دستور شما را اجرا کرده و داده‌های رویداد را به صورت JSON (مخفف JavaScript object notation) به ورودی استاندارد (stdin) آن می‌فرستد. دستور شما داده‌ها را می‌خواند، کار خود را انجام می‌دهد و با یک وضعیت خروج (exit status) پاسخ می‌دهد. خروج با کد 2 از یک هوک PreToolUse باعث لغو اجرای tool call پیش از شروع آن می‌شود و هر آنچه اسکریپت شما در خروجی خطای استاندارد (stderr) نوشته باشد، به عنوان دلیل به مدل بازگردانده می‌شود.

نام رویدادها و نام فیلدها در اینجا از مرجع هوک‌های Claude Code گرفته شده‌اند که در اوت 2026 و بر اساس نسخه 2.1.232 بررسی شده‌اند. این رابط کاربری به‌سرعت تغییر می‌کند، بنابراین پیش از کپی کردن JSON از هر پست وبلاگی، از جمله همین پست، مرجع مربوط به نسخه خود را بررسی کنید. شما می‌توانید تنظیمات خود را با claude --version چاپ کنید.

محل قرارگیری پیکربندی hook

هر hook یک بلوک JSON در یک فایل تنظیمات است. شش مکان مختلف می‌توانند میزبان یک hook باشند و دامنهٔ (scope) فایل، همان دامنهٔ hook خواهد بود.

  • ~/.claude/settings.json: تمام پروژه‌های موجود در سیستم شما، و نه سیستم‌های دیگران.
  • .claude/settings.json: یک پروژهٔ خاص، که در مخزن (repository) commit می‌شود تا هر کسی که آن را clone می‌کند، hook را دریافت کند.
  • .claude/settings.local.json: یک پروژهٔ خاص، فقط روی سیستم شما.
  • تنظیمات خط‌مشی مدیریت‌شده (Managed policy settings): در سطح کل سازمان، که توسط مدیر سیستم تعیین می‌شود.
  • hooks/hooks.json درون یک افزونه (plugin)، که تا زمانی که آن افزونه فعال است، اجرا می‌شود.
  • بخش frontmatter در یک Skill یا subagent، که تا زمانی که آن مؤلفه فعال است، اجرا می‌شود.

ورودی‌های hook از این فایل‌ها با هم ادغام می‌شوند و جایگزین یکدیگر نمی‌شوند. یک فایل تنظیمات پروژه، hookهای خود را به hookهای موجود در تنظیمات کاربر اضافه می‌کند و آن‌ها را حذف نمی‌کند؛ بنابراین یک رویداد می‌تواند چندین hook از چندین فایل مختلف داشته باشد. تنظیم "disableAllHooks": true آن‌ها را غیرفعال می‌کند، با یک استثنا: hookهای ناشی از تنظیمات خط‌مشی مدیریت‌شده همچنان اجرا می‌شوند، مگر اینکه آن تنظیم نیز در تنظیمات مدیریت‌شده اعمال شده باشد.

دستور /hooks را در یک session اجرا کنید تا لیست تمام hookهای ثبت‌شده را مشاهده کنید. این لیست بر اساس رویداد گروه‌بندی شده و منبع فایل و matcher هر کدام را نشان می‌دهد. این منو فقط‌خواندنی (read-only) است، بنابراین برای تغییر یک hook باید فایل تنظیمات مربوطه را ویرایش کنید. ابزار نظارت بر فایل (file watcher) معمولاً تغییرات را بدون نیاز به restart شناسایی می‌کند.

چه رویدادهای هوکی در Claude Code وجود دارند

نسخه 2.1.232 شامل سی و یک رویداد است، از SessionStart تا SessionEnd، که فشرده‌سازی (compaction)، زیرعامل‌ها (subagents)، درخت‌های کاری (worktrees) و فایل‌های پیکربندی را پوشش می‌دهند. کارهای سرور از تعداد انگشت‌شماری از آن‌ها استفاده می‌کنند.

  • PreToolUse: پیش از اجرای یک فراخوانی ابزار (tool call). این همان هوکی است که می‌تواند عملیات را مسدود کند.
  • PostToolUse: پس از موفقیت‌آمیز بودن یک فراخوانی ابزار. PostToolUseFailure در صورت شکست اجرا می‌شود، بنابراین هوکی که باید تمام نتایج را مشاهده کند، به هر دو نیاز دارد.
  • PermissionRequest: زمانی که یک فراخوانی ابزار نیاز به تصمیم‌گیری برای مجوز دارد؛ این همان لحظه‌ای است که اعلان تأیید (approval prompt) ظاهر می‌شود.
  • UserPromptSubmit: هنگام ارسال یک پرامپت، پیش از آنکه Claude آن را پردازش کند. هر چیزی که این هوک در stdout چاپ کند، به کانتکست مدل اضافه می‌شود.
  • SessionStart و SessionEnd: در هر دو انتهای یک نشست (session). SessionStart همچنین پس از فشرده‌سازی، تحت مقدار تطبیق‌دهنده (matcher) compact اجرا می‌شود.
  • Stop: زمانی که Claude پاسخ‌دهی را به پایان می‌رساند. این رویداد یک بار در هر نوبت (turn) رخ می‌دهد، نه یک بار در هر وظیفه کامل‌شده.

هر گروه دارای یک matcher است که تعیین می‌کند کدام رخدادها هوک را اجرا کنند. در رویدادهای ابزار، این فیلتر بر اساس نام ابزار عمل می‌کند، بنابراین "Edit|Write" فقط روی ویرایش فایل‌ها اجرا می‌شود و نه هیچ چیز دیگر. تطبیق‌دهنده‌ها به حروف بزرگ و کوچک حساس هستند. یک تطبیق‌دهنده خالی روی تمام رخدادها اجرا می‌شود. ابزارهای یک سرور MCP (پروتکل کانتکست مدل) با نام mcp__<server>__<tool> شناخته می‌شوند، بنابراین یک تطبیق‌دهنده به صورت "mcp__github__.*" ابزارهای یک سرور خاص را می‌گیرد و بقیه را نادیده می‌گیرد.

هوک‌های Stop دارای تله‌ای هستند که پیش از نوشتن هوک باید از آن آگاه باشید. یک هوک Stop که مسدودکننده است، مدل را به کار بازمی‌گرداند و Claude Code پس از هشت بار مسدودسازی متوالی، هوک را نادیده می‌گیرد (override می‌کند). فیلد stop_hook_active را از ورودی هوک بخوانید و زمانی که مقدار آن true است با کد 0 خارج شوید، در غیر این صورت هوک شما تا رسیدن به آن سقف، در حلقه تکرار می‌ماند.

آنچه یک هوک از طریق stdin دریافت می‌کند

هنگامی که Claude قصد اجرای npm test را دارد، یک هوک 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 events) موارد tool_name، tool_input و tool_use_id را نیز اضافه می‌کنند. سایر رویدادها فیلدهای خاص خود را دارند: UserPromptSubmit متن prompt را دریافت می‌کند و SessionStart شامل یک source از startup، resume، clear، compact یا fork است.

jq روش معمول برای خواندن این داده‌ها در یک اسکریپت shell است و در imageهای سرور مینیمال موجود نیست. ابتدا آن را با استفاده از sudo apt install -y jq در Ubuntu و Debian نصب کنید.

تأثیر وضعیت خروج (exit status) بر فراخوانی ابزار در حال اجرا

سه نتیجه ممکن وجود دارد:

  • خروج با 0 به این معنی است که هوک شما هیچ مخالفتی ندارد. در PreToolUse این به معنای تأیید نیست و جریان عادی مجوزها همچنان اجرا می‌شود. در UserPromptSubmit و SessionStart، خروجی استاندارد (stdout) به کانتکست مدل اضافه می‌شود.
  • خروج با 2 عملیات را در رویدادهایی که قابل مسدود شدن هستند، از جمله PreToolUse، متوقف می‌کند و خروجی خطای استاندارد (stderr) به عنوان دلیلی که به مدل نمایش داده می‌شود، استفاده می‌گردد. در رویدادهایی که قابل مسدود شدن نیستند، مانند PostToolUse، مسدودسازی نادیده گرفته می‌شود، اگرچه stderr همچنان به عنوان بازخورد به مدل می‌رسد.
  • هر کد خروج دیگری یک خطای غیرمسدودکننده است. عملیات ادامه می‌یابد. در متن ثبت‌شده (transcript)، یک اعلان خطای هوک نمایش داده می‌شود که شامل خط اول stderr پس از متن Failed with non-blocking status code: است.

برای هر چیزی فراتر از مسدود کردن یا سکوت، با کد 0 خارج شوید و یک شیء JSON را در stdout چاپ کنید. یک هوک PreToolUse با استفاده از permissionDecision تصمیم‌گیری می‌کند:

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

"allow" اعلان تعاملی را رد می‌کند، "deny" فراخوانی را لغو کرده و دلیل آن را برای مدل ارسال می‌کند، و "ask" اعلان را به صورت عادی نمایش می‌دهد. برای هر هوک یک سبک انتخاب کنید. ترکیب خروج با 2 و تصمیم JSON در stdout، نتیجه‌ای ایجاد می‌کند که باید آن را بررسی کنید.

هنگامی که چندین هوک با یک رویداد مطابقت دارند، آن‌ها به صورت موازی اجرا می‌شوند و هر کدام تا پایان ادامه می‌یابند. یک deny از یک هوک، اجرای هوک‌های هم‌رده را متوقف نمی‌کند؛ بنابراین یک هوک ثبت لاگ (logging hook) همچنان خط خود را می‌نویسد، حتی اگر یک هوک محافظ (guardrail 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"
          }
        ]
      }
    ]
  }
}

پیش از اعتماد به اسکریپت، آن را به‌صورت دستی تست کنید؛ زیرا هوکی که به دلیل ورودی خودش کرش کند، در حالت fail-open قرار می‌گیرد:

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 باشد. در یک نشست، دستور مسدودشده در رونوشت (transcript) به همراه پیام شما به عنوان دلیل ظاهر می‌شود و مدل آن پیام را می‌خواند و خود را تطبیق می‌دهد.

یک ویژگی باعث می‌شود این کار ارزشمند باشد: هوک‌های PreToolUse پیش از بررسی حالت مجوز (permission-mode) و در تمامی حالت‌های مجوز اجرا می‌شوند، بنابراین مسدودسازی حتی تحت bypassPermissions نیز برقرار می‌ماند. این همان چیزی است که یک هوک را در کنار حالت خودکار Claude Code و تنظیمات مجوز آن مفید می‌سازد؛ جایی که پرامپت‌ها محدود شده‌اند اما هوک همچنان اجرا می‌شود.

در مورد ماهیت این ابزار صادق باشید. تطبیق الگو (Pattern matching) روی رشتهٔ دستور، یک حفاظ در برابر بی‌دقتی عامل (agent) است و مرزی در برابر هوشمندی عامل محسوب نمی‌شود، زیرا همان دستور می‌تواند به شکلی نوشته شود که grep شما هرگز آن را نبیند. قوانین سخت‌گیرانه باید در سیستم مجوزها و در حسابی که پردازش تحت آن اجرا می‌شود، اعمال گردند.

مثال 2: فرمت و lint کردن پس از هر ویرایش

PostToolUse با یک matcher از نوع 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 بخواهید یک تابع با تورفتگی (indentation) نادرست به یک فایل Python اضافه کند، سپس فایل را باز کنید. فایل به‌صورت خودکار فرمت‌شده بازمی‌گردد. این نشانهٔ اجرای موفقیت‌آمیز hook است، زیرا یک hook موفق هیچ خروجی اضافه‌ای در گفتگو نمایش نمی‌دهد.

خروج با کد 2 در اینجا هیچ تغییری را لغو نمی‌کند. PostToolUse پس از اجرای ابزار فراخوانی می‌شود، بنابراین ویرایش در هر صورت روی دیسک ذخیره شده است. مزیت استفاده از کد خروج 2 این است که خروجی ruff check به‌عنوان بازخورد به مدل ارسال می‌شود، بنابراین مدل خطایی را که به‌تازگی ایجاد کرده است اصلاح می‌کند و به سراغ کار بعدی نمی‌رود. این تفاوت بین خطای lint که در زمان commit متوجه آن می‌شوید و خطایی است که عامل (agent) در همان نوبت آن را برطرف می‌کند.

در اینجا دو محدودیت برای matcher اهمیت دارد. Edit|Write فایل‌هایی را که توسط دستورات shell تغییر یافته‌اند نمی‌بیند و Claude به‌اندازه‌ای از Bash برای نوشتن فایل‌ها استفاده می‌کند که این شکاف واقعی باشد. برای پوشش در هر فراخوانی، Bash را نیز match کنید و اسکریپت را طوری تنظیم کنید که فایل‌های تغییریافته را با git status --porcelain لیست کند. برای پوشش در هر نوبت (turn)، اسکن را در یک hook از نوع Stop قرار دهید.

مثال 3: ثبت لاگ تمام فراخوانی‌های ابزار برای بازرسی

یک matcher خالی در PostToolUse روی تمام ابزارها اجرا می‌شود. ارسال رکورد به system journal به‌جای یک فایل در دایرکتوری home، باعث می‌شود که این رکوردها از دسترس shell خودِ agent خارج بمانند:

{
  "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 به‌جای اضافه کردن (append) به یک فایل در دایرکتوری home، مالکیت فایل است: یک hook با همان کاربری اجرا می‌شود که shell مربوط به agent با آن اجرا شده است؛ بنابراین هر فایلی که آن کاربر بتواند به آن چیزی اضافه کند، همان کاربر می‌تواند آن را truncate (خالی) کند. journal توسط systemd-journald و با حساب کاربری خودش نوشته می‌شود.

مدت زمان اجرای یک هوک

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
  }
]

یک هوک دستوری به‌صورت پیش‌فرض 600 ثانیه زمان دارد که معادل 10 دقیقه است. برخی رویدادها این زمان را به‌شدت کاهش می‌دهند. هوک‌های SessionEnd در مجموع از یک بودجه زمانی 1.5 ثانیه‌ای استفاده می‌کنند؛ بنابراین پاک‌سازی پایان نشست (end-of-session cleanup) باید سریع انجام شود، هرچند تنظیم یک timeout طولانی‌تر برای هوک، این بودجه مشترک را تا سقف 60 ثانیه افزایش می‌دهد.

هوک‌هایی که به محدودیت زمانی خود برسند، لغو شده و هیچ تصمیمی صادر نمی‌کنند. برای یک گاردریل PreToolUse، این به معنای عدم مسدودسازی است: فراخوانی ابزار در جریان عادی مجوزها ادامه می‌یابد. به همین دلیل، اسکریپت‌های گاردریل را کوچک نگه دارید. برای کارهای زمان‌بر که کسی منتظر نتیجه آن‌ها نیست، مانند ارسال لاگ به یک مقصد دیگر، از "async": true استفاده کنید تا هوک در پس‌زمینه اجرا شود و مانع از توقف فراخوانی ابزار نشود.

هوک‌ها، فایل‌های قوانین، مهارت‌ها و سرورهای MCP

چهار مورد وجود دارند که اغلب با یکدیگر اشتباه گرفته می‌شوند، زیرا همگی عملکرد عامل (agent) را تغییر می‌دهند. تنها یکی از آن‌ها از حالت پیشنهاد خارج می‌شود.

یک فایل قوانین (CLAUDE.md، یا فایلی در مسیر .claude/rules/) متنی است که در کانتکست مدل بارگذاری می‌شود. این فایل رفتار را شکل می‌دهد اما هیچ‌چیز را اجبار نمی‌کند. در مواجهه با یک گفتگوی طولانی، یک diff بزرگ و یک درخواست جدید از کاربر، ممکن است یک خط از آن نادیده گرفته شود. این همان مکانیسم معمولی است که باعث نادیده گرفتن دستورالعمل‌های نوشته‌شده توسط عامل‌ها می‌شود.

یک مهارت (skill)، پوشه‌ای از دستورالعمل‌ها و اسکریپت‌هاست که مدل در صورت تشخیص مرتبط بودن، آن را بارگذاری می‌کند. این قضاوت، هدف اصلی یک مهارت است و در عین حال محدودیت آن نیز محسوب می‌شود: تصمیم نهایی همچنان با مدل است. شما می‌توانید هر دو جنبه را در مهارتی مانند Ponytail، که عامل را به سمت کوچک‌ترین تغییرِ کارآمد سوق می‌دهد مشاهده کنید؛ زیرا این مهارت نحوهٔ رویکرد به کل یک وظیفه را به شکلی شکل می‌دهد که هیچ هوکی قادر به آن نیست، و تنها زمانی عمل می‌کند که مدل تصمیم به بارگذاری آن بگیرد.

یک سرور MCP (پروتکل کانتکست مدل) ابزارهای جدیدی برای فراخوانی در اختیار مدل قرار می‌دهد. این کار دامنهٔ دسترسی عامل را گسترش می‌دهد. این پروتکل عامل را مجبور به استفاده از هیچ‌چیز نمی‌کند و یک پردازش جداگانه است که باید آن را مدیریت کنید، که خود وظیفه‌ای مستقل است: به اجرای سرورهای MCP روی یک VPS مراجعه کنید.

هوک (hook) تنها مورد از این چهار مورد است که بدون انتخاب مدل اجرا می‌شود. از فایل قوانین برای اولویت‌ها و از مهارت برای رویه‌ای که مدل باید هنگام اعمال آن دنبال کند، استفاده کنید. از هوک برای مرحله‌ای استفاده کنید که باید هر بار اتفاق بیفتد، یا برای کاری که هرگز نباید رخ دهد. مقایسهٔ عمیق‌تر، از جمله اینکه چه زمانی یک مهارت بر فایل قوانین برتری دارد، در مقایسه مهارت‌ها، MCP و فایل‌های قوانین آمده است.

پلاگین (plugin) بیش از آنکه مکانیسم پنجم باشد، یک بسته‌بندی است. پلاگین‌ها هوک‌ها را همراه با مهارت‌ها در یک واحد قابل نصب ترکیب می‌کنند؛ این همان روشی است که یک تیم، یک چارچوب حفاظتی (guardrail) یکسان را روی تمام ماشین‌ها توزیع می‌کند: به نحوه عملکرد پلاگین‌های Claude Code مراجعه کنید.

تصمیم امنیتی در یک VPS اشتراکی

هوک (hook) کدی است که توسط ایجنت فراخوانی می‌شود و با دسترسی کاربری که Claude Code را اجرا کرده است، اجرا می‌گردد. این کد، محیط و مجوزهای فایل همان کاربر را به ارث می‌برد. در یک لپ‌تاپ، این موضوع صرفاً یک مسئله در گردش‌کار است؛ اما در یک VPS که ایجنت به‌صورت خودکار و بدون نظارت اجرا می‌شود، این یک مسئله امنیتی با چهار بخش عملیاتی است.

هوک موجود در یک مخزن، کدی است که شما ننوشته‌اید. .claude/settings.json در مخزن commit می‌شود، بنابراین کلون کردن یک مخزن و شروع یک نشست در داخل آن می‌تواند هوک‌هایی را که همراه مخزن بوده‌اند، ثبت کند. Claude Code هوک‌های پروژه را پشت دیالوگ اعتماد به فضای کاری (workspace trust) برای آن پوشه قرار می‌دهد؛ این یعنی پذیرش اعتماد، لحظه‌ای است که شما تصمیم می‌گیرید آن‌ها را اجرا کنید. ابتدا بلوک hooks را مطالعه کنید.

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

هوک می‌تواند در کانتکست مدل بنویسد. هر چیزی که یک هوک SessionStart یا UserPromptSubmit به stdout چاپ کند، به گفتگو اضافه می‌شود. هوکی که متن را از خارج، از یک سیستم ردیابی خطا (issue tracker) یا یک فایل لاگ به داخل هدایت می‌کند، در حال ارائه متن غیرقابل‌اعتماد به مدل است، گویی خودتان آن را تایپ کرده‌اید. هوکی که یادداشتی را از نشست دیگری از Claude Code روی همان VPS فوروارد می‌کند نیز همین کار را انجام می‌دهد و خروجی یک ایجنت، اعتبار بیشتری نسبت به خروجی سیستم ردیابی خطا ندارد. با آن stdout به‌عنوان ورودی رفتار کنید، نه به‌عنوان خروجی.

امتیاز دسترسی، کنترل واقعی است. ایجنت را به‌عنوان یک کاربر اختصاصی و بدون امتیاز (unprivileged) اجرا کنید که فقط قوانین sudo مورد نیاز خود را دارد. داشتن یک deny در PreToolUse ارزشمند است، اما این قابلیت ذاتاً یک تلاش حداکثری (best effort) است: مستندات مرجع همین موضوع را درباره فیلتر if بیان می‌کنند و توصیه می‌کنند زمانی که به یک منع قطعی (hard deny) نیاز دارید، از سیستم مجوزهای سیستم‌عامل استفاده کنید. قوانین مجوز و حسابی که پروسه تحت آن اجرا می‌شود، بخش‌هایی هستند که در شرایط فشار و نفوذ، مقاومت می‌کنند.

یک ویژگی در تمامی پیکربندی‌ها صادق است. هوک‌های PreToolUse پیش از بررسی حالت مجوز در هر permission mode اجرا می‌شوند، بنابراین هوکی که deny را برمی‌گرداند، ابزار را حتی در حالت bypassPermissions مسدود می‌کند. هوک‌ها می‌توانند محدودیت‌های قوانین مجوز را سخت‌تر کنند، اما نمی‌توانند آن‌ها را کاهش دهند.

چرا هوک (hook) من اجرا نمی‌شود؟

این مراحل را به ترتیب دنبال کنید. هر مرحله نشان‌دهنده علامتی است که در عمل مشاهده خواهید کرد.

  • دستور /hooks را اجرا کنید و بررسی کنید که آیا هوک در زیر رویداد مورد انتظار شما ظاهر می‌شود یا خیر. اگر هوک در منو وجود ندارد، معمولاً به این معنی است که فایل تنظیمات دارای خطای نحوی JSON است (زیرا کاما در انتها و کامنت‌ها مجاز نیستند) یا اینکه فایل در یکی از 6 مکان ذکر شده در بالا قرار ندارد.
  • تطبیق‌دهنده (matcher) را دقیقاً با نام ابزار مقایسه کنید. تطبیق‌دهنده‌ها به حروف بزرگ و کوچک حساس هستند، بنابراین "bash" هرگز با ابزار Bash مطابقت نخواهد داشت.
  • اسکریپت را به‌صورت دستی با ورودی نمونه، همان‌طور که در مثال 1 در بالا آمده است، اجرا کنید. کد خروجی غیرمنتظره، یک باگ در اسکریپت شماست و Claude Code آن را به عنوان خطای هوک گزارش می‌کند، نه به عنوان یک تصمیم.
  • مشاهده پیام jq: command not found به این معنی است که jq روی آن ماشین موجود نیست. خطای command not found برای اسکریپت شخصی شما به این معنی است که مسیر پیدا نشده است، بنابراین از ${CLAUDE_PROJECT_DIR} یا یک مسیر مطلق (absolute path) استفاده کنید. اگر اسکریپت اصلاً اجرا نمی‌شود، احتمالاً مجوز اجرای آن تنظیم نشده است.
  • هوک JSON معتبر چاپ می‌کند اما اتفاقی نمی‌افتد. هوک‌های با فرمت shell از طریق sh -c اجرا می‌شوند؛ اگر پروفایل shell شما یک بنر چاپ کند، آن بنر به ابتدای JSON شما اضافه می‌شود. در نتیجه، خروجی استاندارد (stdout) دیگر با { شروع نمی‌شود و Claude Code کل محتوا را به عنوان متن ساده می‌خواند و تصمیم را نادیده می‌گیرد. در صورت خروج با کد 0، هیچ چیزی جز لاگ دیباگ گزارش نمی‌شود. هرگونه echo را در پروفایل خود محدود کنید تا فقط در shellهای تعاملی اجرا شود.
  • اگر همچنان مشکل دارید: نشست را با claude --debug-file /tmp/claude.log شروع کنید و در ترمینال دوم tail -f /tmp/claude.log را اجرا کنید. لاگ دیباگ ثبت می‌کند که کدام هوک‌ها مطابقت داشته‌اند، هر کدام چه کد خروجی‌ای برگردانده‌اند و هر آنچه را که در stdout و stderr نوشته‌اند، ضبط می‌کند.

FAQ

تفاوت بین hook در Claude Code و دستورالعمل در CLAUDE.md چیست؟

یک دستورالعمل CLAUDE.md متنی در context مدل است، بنابراین برای جلب توجه مدل با گفتگو و درخواست فعلی رقابت می‌کند و مدل می‌تواند آن را در برابر آن‌ها بسنجد. یک hook یک دستور shell است که Claude Code در نقطهٔ مشخصی از چرخهٔ حیات خود اجرا می‌کند، بنابراین فارغ از تصمیم مدل، در هر بار وقوع رویداد مربوطه اجرا می‌شود. برای ترجیحات از دستورالعمل استفاده کنید. برای گامی که باید همیشه انجام شود یا عملی که هرگز نباید رخ دهد، از hook استفاده کنید.

چگونه از اجرای یک دستور shell خاص توسط Claude Code جلوگیری کنم؟

یک hook از نوع PreToolUse با یک matcher از نوع Bash ثبت کنید که دستور را از .tool_input.command می‌خواند، دلیلی را در stderr می‌نویسد و با کد خروج 2 پایان می‌یابد. Claude Code فراخوانی را لغو کرده و دلیل شما را به مدل نشان می‌دهد؛ این اتفاق پیش از بررسی permission-mode رخ می‌دهد، بنابراین این منع حتی در حالت bypassPermissions نیز معتبر باقی می‌ماند. تطبیق الگو (pattern matching) روی رشتهٔ دستور، بیشتر یک حفاظ (guardrail) است تا یک مرز امنیتی، زیرا همان دستور می‌تواند به شکلی نوشته شود که از دید الگو پنهان بماند؛ بنابراین آن را با قوانین دسترسی (permission rules) و یک حساب کاربری بدون امتیاز (unprivileged) پشتیبانی کنید.

hook من JSON معتبر چاپ می‌کند اما اتفاقی نمی‌افتد. چرا؟

شایع‌ترین علت، profile مربوط به shell شماست. یک hook بدون فیلد args از طریق sh -c اجرا می‌شود و برخی profileها در هر بار اجرای shell یک بنر چاپ می‌کنند که پیش از JSON شما در stdout قرار می‌گیرد. از آنجا که خروجی دیگر با { شروع نمی‌شود، Claude Code تمام آن را به عنوان متن ساده در نظر گرفته و تصمیم را نادیده می‌گیرد و در صورت خروج با کد 0، هیچ چیزی در transcript گزارش نمی‌شود. هرگونه echo در profile خود را با یک تست interactive-shell محدود کنید و سپس با خواندن لاگ دیباگ از claude --debug-file /tmp/claude.log، اصلاح آن را تأیید کنید.

آیا اجرای hookهای Claude Code روی یک سرور اشتراکی امن است؟

hookها با کاربری که Claude Code را اجرا کرده و با مجوزهای فایل همان کاربر اجرا می‌شوند، بنابراین یک hook می‌تواند هر کاری که آن حساب کاربری قادر به انجامش است را انجام دهد. دو عادت، بیشتر ریسک‌ها را پوشش می‌دهد: عامل (agent) را به عنوان یک حساب کاربری اختصاصی و بدون امتیاز با سیاست sudo محدود اجرا کنید، و پیش از پذیرش گفتگوی اعتماد به workspace، بلوک hooks هر مخزن را بخوانید، زیرا hookهای پروژه در داخل .claude/settings.json عرضه می‌شوند. زمانی که نمی‌خواهید هیچ‌کدام از آن‌ها اجرا شوند، "disableAllHooks": true را در فایل تنظیمات خود قرار دهید.