SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-15

آموزش کامل کار با هوک‌های Claude Code

هوک‌های Claude Code فارغ از تصمیم مدل اجرا می‌شوند. در این راهنما می‌آموزید که چگونه با کد خروج 2 و ارسال داده به stdin، اجرای ابزارها را کنترل و مدیریت کنید.

هوک Claude Code چیست

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

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

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

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

هر hook یک بلوک JSON در یک فایل تنظیمات است. شش مکان برای قرارگیری آن وجود دارد و دامنه (scope) فایل، همان دامنه hook است.

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

ورودی‌های 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: پیش از اجرای یک فراخوانی ابزار. این تنها موردی است که می‌تواند عملیات را مسدود کند.
  • PostToolUse: پس از موفقیت‌آمیز بودن یک فراخوانی ابزار. PostToolUseFailure زمانی که فراخوانی با شکست مواجه شود اجرا می‌شود، بنابراین هوکی که باید تمام نتایج را مشاهده کند به هر دو نیاز دارد.
  • PermissionRequest: زمانی که یک فراخوانی ابزار نیاز به تصمیم‌گیری برای مجوز دارد، یعنی لحظه‌ای که اعلان تأیید ظاهر می‌شود.
  • UserPromptSubmit: زمانی که شما یک پرامپت را ارسال می‌کنید، پیش از آنکه Claude آن را پردازش کند. هر چیزی که این هوک در stdout چاپ کند به کانتکست مدل اضافه می‌شود.
  • SessionStart و SessionEnd: در هر دو انتهای یک نشست. SessionStart همچنین پس از فشرده‌سازی، تحت مقدار تطبیق‌دهنده (matcher) compact اجرا می‌شود.
  • Stop: زمانی که Claude پاسخ‌دهی را به پایان می‌رساند. این اتفاق یک بار در هر نوبت رخ می‌دهد، نه یک بار در هر وظیفه کامل‌شده.

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

هوک‌های Stop دارای یک تله هستند که پیش از نوشتن هوک باید از آن آگاه باشید. یک هوک Stop که مسدودکننده است، مدل را به کار بازمی‌گرداند و Claude Code پس از هشت بار مسدودسازی متوالی، هوک را نادیده می‌گیرد. فیلد 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_name، tool_input و tool_use_id را نیز اضافه می‌کنند. سایر رویدادها فیلدهای خاص خود را دارند: UserPromptSubmit متن prompt را دریافت می‌کند و SessionStart شامل یک source از startup، resume، clear، compact یا fork است.

استفاده از jq روش معمول برای خواندن این داده‌ها در یک اسکریپت شل است، اما ایمیج‌های حداقلی سرور آن را ندارند. ابتدا آن را با دستور 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 از یک هوک، اجرای هوک‌های هم‌رده خود را متوقف نمی‌کند؛ بنابراین یک هوک لاگ‌گیری همچنان خط خود را می‌نویسد، حتی اگر یک هوک محافظ (guardrail) همان فراخوانی را رد کند. سپس Claude Code پاسخ‌ها را ادغام کرده و محدودکننده‌ترین آن‌ها را بر اساس ترتیب اولویت: رد کردن (deny)، تعویق (defer)، پرسش (ask) و اجازه (allow) حفظ می‌کند.

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

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

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

دو محدودیت در matcherها در اینجا اهمیت دارند. Edit|Write فایل‌هایی را که توسط دستورات shell تغییر یافته‌اند نمی‌بیند و Claude به‌اندازهٔ کافی از طریق Bash فایل‌ها را می‌نویسد که این شکاف واقعی باشد. برای پوشش در هر فراخوانی، Bash را نیز match کنید و اسکریپت را طوری تنظیم کنید که فایل‌های تغییریافته را با git status --porcelain لیست کند. برای پوشش یک‌بار در هر نوبت، اسکن را در یک 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 و تحت حساب کاربری خودش نوشته می‌شود.

مدت زمان اجرای یک 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
  }
]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

چرا hook من اجرا نمی‌شود؟

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

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