خودکارسازی کارها با hooks
Hooks دستورهای شِلِ تعریفشده توسط کاربرند که در نقاط مشخصی از چرخهی عمرِ Claude Code اجرا میشوند. آنها کنترلی قطعی روی رفتار Claude Code میدهند و تضمین میکنند که برخی کارها همیشه انجام شوند، بهجای اینکه به انتخابِ LLM برای اجرای آنها متکی باشی. از hooks برای اعمالِ قواعدِ پروژه، خودکارسازیِ کارهای تکراری، و یکپارچهسازیِ Claude Code با ابزارهای موجودت استفاده کن.
برای تصمیمهایی که بهجای قواعدِ قطعی نیاز به قضاوت دارند، میتوانی از hooksِ مبتنیبر پرامپت یا hooksِ مبتنیبر ایجنت استفاده کنی که برای ارزیابیِ شرطها از یک مدلِ Claude بهره میگیرند.
برای راههای دیگرِ گسترشِ Claude Code، skills را برای دادنِ دستورها و فرمانهای اجراییِ بیشتر به Claude، subagents را برای اجرای کارها در کانتکستهای ایزوله، و plugins را برای بستهبندیِ افزونهها جهتِ همرسانی بینِ پروژهها ببین.
اولین hookِ خود را راهاندازی کن
Section titled “اولین hookِ خود را راهاندازی کن”برای ساختِ یک hook، یک بلاک hooks به یک فایل تنظیمات اضافه کن. این راهنمای گامبهگام یک hookِ اعلانِ دسکتاپ میسازد تا هر وقت Claude منتظرِ ورودیِ توست بهجای تماشای ترمینال، باخبر شوی.
hook را به تنظیماتت اضافه کن
فایل ~/.claude/settings.json را باز کن و یک hookِ Notification اضافه کن. مثالِ زیر برای macOS از osascript استفاده میکند؛ برای دستورهای Linux و Windows به وقتی Claude نیاز به ورودی دارد باخبر شو مراجعه کن.
{ "hooks": { "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" } ] } ] }}اگر فایل تنظیماتت از قبل کلیدِ hooks دارد، Notification را بهعنوان همترازِ کلیدهای رویدادِ موجود اضافه کن، نه اینکه کلِ شیء را جایگزین کنی. هر نامِ رویداد یک کلید داخلِ تکشیءِ hooks است:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }] } ], "Notification": [ { "matcher": "", "hooks": [{ "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" }] } ] }}همچنین میتوانی با توصیفِ آنچه میخواهی در CLI، از Claude بخواهی hook را برایت بنویسد.
پیکربندی را تأیید کن
/hooks را تایپ کن تا مرورگرِ hooks باز شود. فهرستی از همهی رویدادهای hookِ موجود میبینی، با شمارهای کنارِ هر رویداد که hook پیکربندیشده دارد. Notification را انتخاب کن تا تأیید کنی hookِ جدیدت در فهرست ظاهر میشود. انتخابِ hook جزئیاتش را نشان میدهد: رویداد، matcher، نوع، فایلِ منبع، و دستور.
hook را آزمایش کن
Esc را بزن تا به CLI برگردی. از Claude بخواه کاری انجام دهد که به مجوز نیاز دارد، سپس از ترمینال فاصله بگیر. باید یک اعلانِ دسکتاپ دریافت کنی.
چه چیزهایی را میتوانی خودکار کنی
Section titled “چه چیزهایی را میتوانی خودکار کنی”hookها به تو امکان میدهند کد را در نقاطِ کلیدیِ چرخهی عمرِ Claude Code اجرا کنی: قالببندیِ فایلها پس از ویرایش، مسدودکردنِ دستورها پیش از اجرا، فرستادنِ اعلان وقتی Claude نیاز به ورودی دارد، تزریقِ کانتکست در شروعِ نشست، و موارد بیشتر. برای فهرستِ کاملِ رویدادهای hook، به مرجع Hooks مراجعه کن.
هر مثال شامل یک بلاکِ پیکربندیِ آمادهی استفاده است که به یک فایل تنظیمات اضافه میکنی. رایجترین الگوها:
- وقتی Claude نیاز به ورودی دارد باخبر شو
- قالببندیِ خودکارِ کد پس از ویرایشها
- مسدودکردنِ ویرایشِ فایلهای محافظتشده
- تزریقِ دوبارهی کانتکست پس از فشردهسازی
- ممیزیِ تغییراتِ پیکربندی
- بارگذاریِ مجددِ محیط هنگام تغییرِ دایرکتوری یا فایلها
- تأییدِ خودکارِ پرامپتهای مجوزِ مشخص
برای نمونهای production از hookهایی که یک بازبینیِ جداگانه با مدل اجرا میکنند و یافتهها را به نشست بازمیگردانند، به نحوهی یکپارچهشدنِ پلاگینِ security-guidance با Claude Code مراجعه کن.
وقتی Claude نیاز به ورودی دارد باخبر شو
Section titled “وقتی Claude نیاز به ورودی دارد باخبر شو”هر وقت Claude کارش را تمام کرد و به ورودیِ تو نیاز داشت، یک اعلانِ دسکتاپ بگیر، تا بتوانی بدونِ بررسیِ ترمینال به کارهای دیگر بپردازی.
این hook از رویدادِ Notification استفاده میکند که وقتی Claude منتظرِ ورودی یا مجوز است، فعال میشود. هر تبِ زیر از دستورِ اعلانِ بومیِ آن پلتفرم استفاده میکند. این را به ~/.claude/settings.json اضافه کن:
{ "hooks": { "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" } ] } ] }}اگر هیچ اعلانی ظاهر نشد
osascript اعلانها را از طریقِ اپِ داخلیِ Script Editor مسیریابی میکند. اگر Script Editor مجوزِ اعلان نداشته باشد، دستور بیسروصدا شکست میخورد و macOS از تو نمیخواهد آن را اعطا کنی. این را یکبار در Terminal اجرا کن تا Script Editor در تنظیماتِ اعلانت ظاهر شود:
osascript -e 'display notification "test"'هنوز چیزی ظاهر نمیشود. به System Settings > Notifications برو، Script Editor را در فهرست پیدا کن، و Allow Notifications را روشن کن. دستور را دوباره اجرا کن تا تأیید کنی اعلانِ آزمایشی ظاهر میشود.
{ "hooks": { "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "notify-send 'Claude Code' 'Claude Code needs your attention'" } ] } ] }}{ "hooks": { "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\"" } ] } ] }}matcherِ خالی روی همهی انواعِ اعلان فعال میشود. برای فعالشدن فقط روی رویدادهای مشخص، آن را به یکی از این مقادیر تنظیم کن:
| Matcher | چه وقت فعال میشود |
|---|---|
permission_prompt | Claude نیاز دارد تو یک استفادهی ابزار را تأیید کنی |
idle_prompt | Claude کارش تمام شده و منتظرِ پرامپتِ بعدیِ توست |
auth_success | احراز هویت کامل میشود |
elicitation_dialog | یک سرورِ MCP یک فرمِ elicitation باز میکند |
elicitation_complete | یک فرمِ elicitationِ MCP ارسال یا رد میشود |
elicitation_response | یک پاسخِ elicitationِ MCP به سرور بازفرستاده میشود |
/hooks را تایپ کن و Notification را انتخاب کن تا تأیید کنی hook ثبت شده است. برای اسکیمای کاملِ رویداد، به مرجع Notification مراجعه کن.
قالببندیِ خودکارِ کد پس از ویرایشها
Section titled “قالببندیِ خودکارِ کد پس از ویرایشها”بهطور خودکار Prettier را روی هر فایلی که Claude ویرایش میکند اجرا کن، تا قالببندی بدونِ مداخلهی دستی یکدست بماند.
این hook از رویدادِ PostToolUse با matcherِ Edit|Write استفاده میکند، بنابراین فقط پس از ابزارهای ویرایشِ فایل اجرا میشود. دستور، مسیرِ فایلِ ویرایششده را با jq استخراج میکند و به Prettier میدهد. این را به .claude/settings.json در ریشهی پروژهات اضافه کن:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" } ] } ] }}مسدودکردنِ ویرایشِ فایلهای محافظتشده
Section titled “مسدودکردنِ ویرایشِ فایلهای محافظتشده”از تغییرِ فایلهای حساس مثل .env، package-lock.json، یا هر چیزی در .git/ توسط Claude جلوگیری کن. Claude بازخوردی دریافت میکند که توضیح میدهد چرا ویرایش مسدود شد، تا بتواند رویکردش را تنظیم کند.
این مثال از یک فایلِ اسکریپتِ جداگانه استفاده میکند که hook آن را صدا میزند. اسکریپت مسیرِ فایلِ هدف را در برابرِ فهرستی از الگوهای محافظتشده بررسی میکند و با کدِ ۲ خارج میشود تا ویرایش را مسدود کند.
اسکریپتِ hook را بساز
این را در .claude/hooks/protect-files.sh ذخیره کن:
#!/bin/bashINPUT=$(cat)FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do if [[ "$FILE_PATH" == *"$pattern"* ]]; then echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2 exit 2 fidone
exit 0اسکریپت را اجراشدنی کن (macOS/Linux)
اسکریپتهای hook باید اجراشدنی باشند تا Claude Code بتواند آنها را اجرا کند:
chmod +x .claude/hooks/protect-files.shhook را ثبت کن
یک hookِ PreToolUse به .claude/settings.json اضافه کن که اسکریپت را پیش از هر فراخوانیِ ابزارِ Edit یا Write اجرا کند:
{ "hooks": { "PreToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh" } ] } ] }}تزریقِ دوبارهی کانتکست پس از فشردهسازی
Section titled “تزریقِ دوبارهی کانتکست پس از فشردهسازی”وقتی پنجرهی کانتکستِ Claude پر میشود، فشردهسازی گفتوگو را خلاصه میکند تا فضا آزاد شود. این کار میتواند جزئیاتِ مهم را از دست بدهد. از یک hookِ SessionStart با matcherِ compact استفاده کن تا کانتکستِ حیاتی را پس از هر فشردهسازی دوباره تزریق کنی.
هر متنی که دستورت روی stdout بنویسد به کانتکستِ Claude افزوده میشود. این مثال قراردادهای پروژه و کارهای اخیر را به Claude یادآوری میکند. این را به .claude/settings.json در ریشهی پروژهات اضافه کن:
{ "hooks": { "SessionStart": [ { "matcher": "compact", "hooks": [ { "type": "command", "command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'" } ] } ] }}میتوانی echo را با هر دستوری که خروجیِ پویا تولید میکند جایگزین کنی، مثل git log --oneline -5 برای نمایشِ کامیتهای اخیر. برای تزریقِ کانتکست در هر شروعِ نشست، بهجایش CLAUDE.md را در نظر بگیر. برای متغیرهای محیطی، به CLAUDE_ENV_FILE در مرجع مراجعه کن.
ممیزیِ تغییراتِ پیکربندی
Section titled “ممیزیِ تغییراتِ پیکربندی”ردیابی کن که چه وقت فایلهای تنظیمات یا skills در طولِ یک نشست تغییر میکنند. رویدادِ ConfigChange وقتی یک پردازشِ بیرونی یا ویرایشگر یک فایلِ پیکربندی را تغییر میدهد فعال میشود، بنابراین میتوانی تغییرات را برای انطباق ثبت کنی یا تغییراتِ غیرمجاز را مسدود کنی.
این مثال هر تغییر را به یک لاگِ ممیزی پیوست میکند. این را به ~/.claude/settings.json اضافه کن:
{ "hooks": { "ConfigChange": [ { "matcher": "", "hooks": [ { "type": "command", "command": "jq -c '{timestamp: now | todate, source: .source, file: .file_path}' >> ~/claude-config-audit.log" } ] } ] }}matcher بر اساسِ نوعِ پیکربندی فیلتر میکند: user_settings، project_settings، local_settings، policy_settings، یا skills. برای جلوگیری از اعمالِ یک تغییر، با کدِ ۲ خارج شو یا {"decision": "block"} بازگردان. برای اسکیمای کاملِ ورودی، مرجع ConfigChange را ببین.
بارگذاریِ مجددِ محیط هنگام تغییرِ دایرکتوری یا فایلها
Section titled “بارگذاریِ مجددِ محیط هنگام تغییرِ دایرکتوری یا فایلها”برخی پروژهها بسته به اینکه در کدام دایرکتوری هستی، متغیرهای محیطیِ متفاوتی تنظیم میکنند. ابزارهایی مثل direnv این کار را بهطور خودکار در شِلِ تو انجام میدهند، اما ابزارِ Bashِ Claude خودش این تغییرات را برنمیدارد.
جفتکردنِ یک hookِ SessionStart با یک hookِ CwdChanged این را درست میکند. SessionStart متغیرها را برای دایرکتوریای که در آن راه میاندازی بارگذاری میکند، و CwdChanged هر بار که Claude دایرکتوری را عوض میکند آنها را دوباره بارگذاری میکند. هر دو در CLAUDE_ENV_FILE مینویسند که Claude Code آن را بهعنوان مقدمهی اسکریپت پیش از هر دستورِ Bash اجرا میکند. این را به ~/.claude/settings.json اضافه کن:
{ "hooks": { "SessionStart": [ { "hooks": [ { "type": "command", "command": "direnv export bash > \"$CLAUDE_ENV_FILE\"" } ] } ], "CwdChanged": [ { "hooks": [ { "type": "command", "command": "direnv export bash > \"$CLAUDE_ENV_FILE\"" } ] } ] }}direnv allow را یکبار در هر دایرکتوریای که .envrc دارد اجرا کن تا به direnv اجازهی بارگذاریِ آن داده شود. اگر بهجای direnv از devbox یا nix استفاده میکنی، همین الگو با devbox shellenv یا devbox global shellenv بهجای direnv export bash کار میکند.
برای واکنش به فایلهای مشخص بهجای هر تغییرِ دایرکتوری، از FileChanged با یک matcher استفاده کن که نامفایلهای موردِ پایش را فهرست میکند و با | از هم جدا شدهاند. برای ساختنِ فهرستِ پایش، این مقدار به نامفایلهای تحتاللفظی تقسیم میشود، نه اینکه بهعنوان regex ارزیابی شود. FileChanged را ببین تا بفهمی همین مقدار چطور فیلتر میکند که هنگام تغییرِ یک فایل کدام گروههای hook اجرا شوند. این مثال .envrc و .env را در دایرکتوریِ کاری پایش میکند:
{ "hooks": { "FileChanged": [ { "matcher": ".envrc|.env", "hooks": [ { "type": "command", "command": "direnv export bash > \"$CLAUDE_ENV_FILE\"" } ] } ] }}برای اسکیماهای ورودی، خروجیِ watchPaths، و جزئیاتِ CLAUDE_ENV_FILE، به مدخلهای مرجعِ CwdChanged و FileChanged مراجعه کن.
تأییدِ خودکارِ پرامپتهای مجوزِ مشخص
Section titled “تأییدِ خودکارِ پرامپتهای مجوزِ مشخص”دیالوگِ تأیید را برای فراخوانیهای ابزاری که همیشه اجازه میدهی رد کن. این مثال ExitPlanMode را بهطور خودکار تأیید میکند، ابزاری که Claude وقتی ارائهی یک برنامه را تمام میکند و درخواستِ ادامه میدهد صدا میزند، تا هر بار که یک برنامه آماده است از تو پرسیده نشود.
برخلافِ مثالهای کدِ خروجِ بالا، تأییدِ خودکار نیاز دارد که hookِ تو یک تصمیمِ JSON روی stdout بنویسد. یک hookِ PermissionRequest وقتی Claude Code قرار است یک دیالوگِ مجوز نشان دهد فعال میشود، و بازگرداندنِ "behavior": "allow" آن را بهجای تو پاسخ میدهد.
matcher دامنهی hook را فقط به ExitPlanMode محدود میکند، بنابراین هیچ پرامپتِ دیگری تحتِتأثیر قرار نمیگیرد. این را به ~/.claude/settings.json اضافه کن:
{ "hooks": { "PermissionRequest": [ { "matcher": "ExitPlanMode", "hooks": [ { "type": "command", "command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'" } ] } ] }}وقتی hook تأیید میکند، Claude Code از حالتِ برنامه خارج میشود و هر حالتِ مجوزی را که پیش از ورود به حالتِ برنامه فعال بود بازمیگرداند. رونوشت در جایی که دیالوگ ظاهر میشد «Allowed by PermissionRequest hook» را نشان میدهد. مسیرِ hook همیشه گفتوگوی فعلی را نگه میدارد: نمیتواند کانتکست را پاک کند و یک نشستِ پیادهسازیِ تازه را آنطور که دیالوگ میتواند، شروع کند.
برای تنظیمِ یک حالتِ مجوزِ مشخص بهجای آن، خروجیِ hookت میتواند شاملِ یک آرایهی updatedPermissions با یک مدخلِ setMode باشد. مقدارِ mode هر حالتِ مجوزی مثل default، acceptEdits، یا bypassPermissions است، و destination: "session" آن را فقط برای نشستِ فعلی اعمال میکند.
برای تغییرِ نشست به acceptEdits، hookت این JSON را روی stdout مینویسد:
{ "hookSpecificOutput": { "hookEventName": "PermissionRequest", "decision": { "behavior": "allow", "updatedPermissions": [ { "type": "setMode", "mode": "acceptEdits", "destination": "session" } ] } }}matcher را تا حدِ ممکن باریک نگه دار. تطبیق روی .* یا گذاشتنِ matcherِ خالی هر پرامپتِ مجوزی را بهطور خودکار تأیید میکند، از جمله نوشتنِ فایلها و دستورهای شِل. برای مجموعهی کاملِ فیلدهای تصمیم، به مرجع PermissionRequest مراجعه کن.
hookها چطور کار میکنند
Section titled “hookها چطور کار میکنند”رویدادهای hook در نقاطِ مشخصی از چرخهی عمرِ Claude Code فعال میشوند. وقتی یک رویداد فعال میشود، همهی hookهای منطبق بهصورت موازی اجرا میشوند، و دستورهای hookِ یکسان بهطور خودکار حذفِ تکرار میشوند. جدولِ زیر هر رویداد و زمانِ فعالشدنش را نشان میدهد:
| رویداد | چه وقت فعال میشود |
|---|---|
SessionStart | وقتی یک نشست شروع میشود یا از سر گرفته میشود |
Setup | وقتی Claude Code را با --init-only، یا با --init یا --maintenance در حالتِ -p شروع میکنی. برای آمادهسازیِ یکباره در CI یا اسکریپتها |
UserPromptSubmit | وقتی یک پرامپت ارسال میکنی، پیش از اینکه Claude آن را پردازش کند |
UserPromptExpansion | وقتی یک دستورِ تایپشده توسط کاربر به یک پرامپت بسط مییابد، پیش از رسیدن به Claude. میتواند بسط را مسدود کند |
PreToolUse | پیش از اجرای یک فراخوانیِ ابزار. میتواند آن را مسدود کند |
PermissionRequest | وقتی یک دیالوگِ مجوز ظاهر میشود |
PermissionDenied | وقتی یک فراخوانیِ ابزار توسط طبقهبندِ حالتِ خودکار رد میشود. {retry: true} بازگردان تا به مدل بگویی میتواند فراخوانیِ ابزارِ ردشده را دوباره امتحان کند |
PostToolUse | پس از موفقیتِ یک فراخوانیِ ابزار |
PostToolUseFailure | پس از شکستِ یک فراخوانیِ ابزار |
PostToolBatch | پس از حلشدنِ یک دستهی کاملِ فراخوانیهای ابزارِ موازی، پیش از فراخوانیِ بعدیِ مدل |
Notification | وقتی Claude Code یک اعلان میفرستد |
MessageDisplay | حین نمایشِ متنِ پیامِ دستیار |
SubagentStart | وقتی یک subagent ایجاد میشود |
SubagentStop | وقتی یک subagent تمام میکند |
TaskCreated | وقتی یک کار از طریقِ TaskCreate در حالِ ایجاد است |
TaskCompleted | وقتی یک کار در حالِ علامتخوردن بهعنوان تکمیلشده است |
Stop | وقتی Claude پاسخگویی را تمام میکند |
StopFailure | وقتی نوبت بهخاطرِ یک خطای API پایان مییابد. خروجی و کدِ خروج نادیده گرفته میشوند |
TeammateIdle | وقتی یک همتیمیِ تیمِ ایجنت در آستانهی بیکار شدن است |
InstructionsLoaded | وقتی یک فایلِ CLAUDE.md یا .claude/rules/*.md در کانتکست بارگذاری میشود. در شروعِ نشست و وقتی فایلها بهصورت تنبل در طولِ یک نشست بارگذاری میشوند فعال میشود |
ConfigChange | وقتی یک فایلِ پیکربندی در طولِ یک نشست تغییر میکند |
CwdChanged | وقتی دایرکتوریِ کاری تغییر میکند، مثلاً وقتی Claude یک دستورِ cd اجرا میکند. برای مدیریتِ واکنشیِ محیط با ابزارهایی مثل direnv مفید است |
FileChanged | وقتی یک فایلِ پایششده روی دیسک تغییر میکند. فیلدِ matcher مشخص میکند کدام نامفایلها پایش شوند |
WorktreeCreate | وقتی یک worktree از طریقِ --worktree یا isolation: "worktree" در حالِ ایجاد است. جایگزینِ رفتارِ پیشفرضِ git میشود |
WorktreeRemove | وقتی یک worktree در حالِ حذف است، چه هنگام خروج از نشست و چه وقتی یک subagent تمام میکند |
PreCompact | پیش از فشردهسازیِ کانتکست |
PostCompact | پس از تکمیلِ فشردهسازیِ کانتکست |
Elicitation | وقتی یک سرورِ MCP حین یک فراخوانیِ ابزار درخواستِ ورودیِ کاربر میکند |
ElicitationResult | پس از اینکه یک کاربر به یک elicitationِ MCP پاسخ میدهد، پیش از بازفرستادنِ پاسخ به سرور |
SessionEnd | وقتی یک نشست خاتمه مییابد |
هر hook یک type دارد که نحوهی اجرایش را تعیین میکند. بیشترِ hookها از "type": "command" استفاده میکنند که یک دستورِ شِل اجرا میکند. چهار نوعِ دیگر در دسترساند:
"type": "http": دادهی رویداد را با POST به یک URL میفرستد. HTTP hooks را ببین."type": "mcp_tool": یک ابزار را روی یک سرورِ MCPِ از قبل متصلشده صدا میزند. MCP tool hooks را ببین."type": "prompt": ارزیابیِ تکنوبتیِ LLM. hooksِ مبتنیبر پرامپت را ببین."type": "agent": راستیآزماییِ چندنوبتی با دسترسی به ابزار. hooksِ ایجنت آزمایشیاند و ممکن است تغییر کنند. hooksِ مبتنیبر ایجنت را ببین.
ترکیبِ نتایج از چند hook
Section titled “ترکیبِ نتایج از چند hook”وقتی چند hook با همان رویداد منطبق میشوند، دستورِ هر hook پیش از اینکه Claude Code نتایج را ادغام کند تا انتها اجرا میشود. بازگرداندنِ deny توسطِ یک hook اجرای hookهای همرده را متوقف نمیکند. به denyِ یک hook برای سرکوبِ اثراتِ جانبیِ یک hookِ دیگر تکیه نکن.
پس از پایانِ همهی hookهای منطبق، Claude Code خروجیهایشان را ترکیب میکند. برای تصمیمهای مجوزِ PreToolUse، محدودکنندهترین پاسخ برنده میشود، به ترتیبِ deny، defer، ask، allow. متنِ حاصل از additionalContext از هر hook نگه داشته میشود و یکجا به Claude منتقل میشود.
مثالِ زیر دو hookِ PreToolUse را روی Bash ثبت میکند. اولی هر دستور را به یک فایلِ لاگ پیوست میکند و با ۰ خارج میشود. دومی اسکریپتی را اجرا میکند که وقتی دستور شاملِ rm -rf است با ۲ خارج میشود تا رد کند:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "jq -r .tool_input.command >> ~/.claude/bash.log" }, { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm-rf.sh" } ] } ] }}وقتی Claude تلاش میکند rm -rf /tmp/build را اجرا کند، هر دو hook بهصورت موازی اجرا میشوند. hookِ لاگ دستور را در ~/.claude/bash.log مینویسد و با ۰ خارج میشود، که هیچ تصمیمی گزارش نمیکند. hookِ گاردریل با ۲ خارج میشود، که فراخوانیِ ابزار را رد میکند. deny برنده میشود، بنابراین Claude Code دستور را مسدود میکند و stderrِ گاردریل را به Claude نشان میدهد. مدخلِ لاگ همچنان نوشته میشود چون hookِ لاگ از قبل اجرا شده بود.
خواندنِ ورودی و بازگرداندنِ خروجی
Section titled “خواندنِ ورودی و بازگرداندنِ خروجی”hookها از طریقِ stdin، stdout، stderr، و کدهای خروج با Claude Code ارتباط میگیرند. وقتی یک رویداد فعال میشود، Claude Code دادهی مخصوصِ رویداد را بهصورت JSON به stdinِ اسکریپتت میدهد. اسکریپتت آن داده را میخواند، کارش را میکند، و از طریقِ کدِ خروج به Claude Code میگوید بعد چه کند.
ورودیِ hook
Section titled “ورودیِ hook”هر رویداد شاملِ فیلدهای مشترک مثل session_id و cwd است، اما هر نوعِ رویداد دادهی متفاوتی اضافه میکند. برای مثال، وقتی Claude یک دستورِ Bash اجرا میکند، یک hookِ PreToolUse چیزی شبیه این را روی stdin دریافت میکند:
{ "session_id": "abc123", // unique ID for this session "cwd": "/Users/sarah/myproject", // working directory when the event fired "hook_event_name": "PreToolUse", // which event triggered this hook "tool_name": "Bash", // the tool Claude is about to use "tool_input": { // the arguments Claude passed to the tool "command": "npm test" // for Bash, this is the shell command }}اسکریپتت میتواند آن JSON را تجزیه کند و روی هر یک از آن فیلدها عمل کند. hookهای UserPromptSubmit بهجایش متنِ prompt را میگیرند، hookهای SessionStart مقدارِ source را میگیرند (startup، resume، clear، compact)، و الی آخر. برای فیلدهای مشترک به Common input fields در مرجع، و برای اسکیماهای مخصوصِ رویداد به بخشِ هر رویداد مراجعه کن.
خروجیِ hook
Section titled “خروجیِ hook”اسکریپتت با نوشتن روی stdout یا stderr و خروج با یک کدِ مشخص به Claude Code میگوید بعد چه کند. برای مثال، یک hookِ PreToolUse که میخواهد یک دستور را مسدود کند:
#!/bin/bashINPUT=$(cat)COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q "drop table"; then echo "Blocked: dropping tables is not allowed" >&2 # stderr becomes Claude's feedback exit 2 # exit 2 = block the actionfi
exit 0 # exit 0 = no decision; the normal permission flow appliesکدِ خروج تعیین میکند بعد چه میشود:
- خروج ۰: hook هیچ اعتراضی گزارش نمیکند و کار بهطور عادی پیش میرود. برای یک hookِ
PreToolUseاین فراخوانیِ ابزار را تأیید نمیکند: جریانِ مجوزِ عادی همچنان اعمال میشود. برای hookهایUserPromptSubmit،UserPromptExpansion، وSessionStart، هر چیزی که روی stdout بنویسی به کانتکستِ Claude افزوده میشود. - خروج ۲: کار مسدود میشود. دلیلی روی stderr بنویس، و Claude آن را بهعنوان بازخورد دریافت میکند تا بتواند تنظیم کند. برخی رویدادها قابلِ مسدودشدن نیستند: برای
SessionStart،Setup،Notification، و بقیه، خروج ۲ stderr را به کاربر نشان میدهد و اجرا ادامه مییابد. برای فهرستِ کامل به رفتارِ کدِ خروجِ ۲ بهازای هر رویداد مراجعه کن. - هر کدِ خروجِ دیگر: کار پیش میرود. رونوشت یک اعلانِ
<hook name> hook errorبهدنبالِ اولین خطِ stderr نشان میدهد؛ کلِ stderr به لاگِ دیباگ میرود.
خروجیِ ساختاریافتهی JSON
Section titled “خروجیِ ساختاریافتهی JSON”کدهای خروج فقط به تو اجازه میدهند مسدود کنی یا ساکت بمانی. برای کنترلِ بیشتر، با ۰ خارج شو و بهجایش یک شیءِ JSON روی stdout چاپ کن.
برای مثال، یک hookِ PreToolUse میتواند یک فراخوانیِ ابزار را رد کند و به Claude بگوید چرا، یا آن را برای تأیید به کاربر ارجاع دهد:
{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Use rg instead of grep for better performance" }}با "deny"، Claude Code فراخوانیِ ابزار را لغو میکند و permissionDecisionReason را به Claude بازمیگرداند. این مقادیرِ permissionDecision مخصوصِ PreToolUse هستند:
"allow": از پرامپتِ تعاملیِ مجوز رد شو. قواعدِ deny و ask، از جمله فهرستهای denyِ مدیریتشدهی سازمانی، همچنان اعمال میشوند"deny": فراخوانیِ ابزار را لغو کن و دلیل را به Claude بفرست"ask": پرامپتِ مجوز را بهطور عادی به کاربر نشان بده
یک مقدارِ چهارم، "defer"، در حالتِ غیرتعاملی با پرچمِ -p در دسترس است. پردازش را با حفظِ فراخوانیِ ابزار خارج میکند تا یک رَپِر Agent SDK بتواند ورودی جمع کند و از سر بگیرد. به یک فراخوانیِ ابزار را برای بعد بهتعویق بینداز در مرجع مراجعه کن.
بازگرداندنِ "allow" از پرامپتِ تعاملی رد میشود اما قواعدِ مجوز را نادیده نمیگیرد. اگر یک قاعدهی deny با فراخوانیِ ابزار منطبق شود، فراخوانی مسدود میشود حتی وقتی hookت "allow" بازمیگرداند. اگر یک قاعدهی ask منطبق شود، همچنان از کاربر پرسیده میشود. این یعنی قواعدِ deny از هر دامنهی تنظیمات، از جمله تنظیماتِ مدیریتشده، همیشه بر تأییدهای hook اولویت دارند.
رویدادهای دیگر از الگوهای تصمیمِ متفاوتی استفاده میکنند. برای مثال، hookهای PostToolUse و Stop از یک فیلدِ سطحبالای decision: "block" استفاده میکنند، در حالی که PermissionRequest از hookSpecificOutput.decision.behavior استفاده میکند. برای تفکیکِ کامل بهازای هر رویداد، به جدولِ خلاصه در مرجع مراجعه کن.
برای hookهای UserPromptSubmit، بهجایش از additionalContext برای تزریقِ متن به کانتکستِ Claude استفاده کن. hookهای مبتنیبر پرامپت (type: "prompt") خروجی را متفاوت مدیریت میکنند: hooksِ مبتنیبر پرامپت را ببین.
فیلتر کردنِ hookها با matcherها
Section titled “فیلتر کردنِ hookها با matcherها”بدونِ matcher، یک hook روی هر رخدادِ رویدادش فعال میشود. matcherها به تو امکان میدهند این را محدودتر کنی. برای مثال، اگر میخواهی یک قالببند را فقط پس از ویرایشِ فایل (نه پس از هر فراخوانیِ ابزار) اجرا کنی، یک matcher به hookِ PostToolUseات اضافه کن:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "prettier --write ..." } ] } ] }}matcherِ "Edit|Write" فقط وقتی Claude از ابزارِ Edit یا Write استفاده میکند فعال میشود، نه وقتی از Bash، Read، یا هر ابزارِ دیگر استفاده میکند. برای نحوهی ارزیابیِ نامهای ساده و عباراتِ منظم، Matcher patterns را ببین.
هر نوعِ رویداد روی یک فیلدِ مشخص تطبیق میدهد:
| رویداد | matcher چه چیزی را فیلتر میکند | مقادیرِ نمونهی matcher |
|---|---|---|
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied | نامِ ابزار | Bash, Edit|Write, mcp__.* |
SessionStart | چطور نشست شروع شد | startup, resume, clear, compact |
Setup | کدام پرچمِ CLI راهاندازی را فعال کرد | init, maintenance |
SessionEnd | چرا نشست پایان یافت | clear, resume, logout, prompt_input_exit, bypass_permissions_disabled, other |
Notification | نوعِ اعلان | permission_prompt, idle_prompt, auth_success, elicitation_dialog, elicitation_complete, elicitation_response |
SubagentStart | نوعِ ایجنت | general-purpose, Explore, Plan, یا نامهای ایجنتِ سفارشی |
PreCompact, PostCompact | چه چیزی فشردهسازی را فعال کرد | manual, auto |
SubagentStop | نوعِ ایجنت | همان مقادیرِ SubagentStart |
ConfigChange | منبعِ پیکربندی | user_settings, project_settings, local_settings, policy_settings, skills |
StopFailure | نوعِ خطا | rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, unknown |
InstructionsLoaded | دلیلِ بارگذاری | session_start, nested_traversal, path_glob_match, include, compact |
Elicitation | نامِ سرورِ MCP | نامهای سرورِ MCPِ پیکربندیشدهات |
ElicitationResult | نامِ سرورِ MCP | همان مقادیرِ Elicitation |
FileChanged | نامفایلهای تحتاللفظی برای پایش (نگاه کن به FileChanged) | .envrc|.env |
UserPromptExpansion | نامِ دستور | نامهای skill یا دستورِ تو |
UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, CwdChanged, MessageDisplay | پشتیبانی از matcher ندارد | همیشه روی هر رخداد فعال میشود |
چند مثالِ بیشتر که matcherها را روی انواعِ مختلفِ رویداد نشان میدهند:
فقط فراخوانیهای ابزارِ Bash را تطبیق بده و هر دستور را در یک فایل لاگ کن. رویدادِ PostToolUse پس از تکمیلِ دستور فعال میشود، بنابراین tool_input.command شاملِ آن چیزی است که اجرا شد. hook دادهی رویداد را بهصورت JSON روی stdin دریافت میکند، و jq -r '.tool_input.command' فقط رشتهی دستور را استخراج میکند، که >> آن را به فایلِ لاگ پیوست میکند:
{ "hooks": { "PostToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt" } ] } ] }}ابزارهای MCP از قراردادِ نامگذاریِ متفاوتی نسبت به ابزارهای داخلی استفاده میکنند: mcp__<server>__<tool>، که در آن <server> نامِ سرورِ MCP و <tool> ابزاری است که ارائه میدهد. برای مثال، mcp__github__search_repositories یا mcp__filesystem__read_file. از یک matcherِ regex برای هدفگیریِ همهی ابزارهای یک سرورِ مشخص استفاده کن، یا با الگویی مثل mcp__.*__write.* در میانِ سرورها تطبیق بده. برای فهرستِ کاملِ مثالها، Match MCP tools را در مرجع ببین.
دستورِ زیر نامِ ابزار را از ورودیِ JSONِ hook با jq استخراج میکند و آن را روی stderr مینویسد. نوشتن روی stderr، stdout را برای خروجیِ JSON تمیز نگه میدارد و پیام را به لاگِ دیباگ میفرستد:
{ "hooks": { "PreToolUse": [ { "matcher": "mcp__github__.*", "hooks": [ { "type": "command", "command": "echo \"GitHub tool called: $(jq -r '.tool_name')\" >&2" } ] } ] }}رویدادِ SessionEnd از matcherها روی دلیلِ پایانِ نشست پشتیبانی میکند. این hook فقط روی clear فعال میشود (وقتی /clear را اجرا میکنی)، نه روی خروجهای عادی:
{ "hooks": { "SessionEnd": [ { "matcher": "clear", "hooks": [ { "type": "command", "command": "rm -f /tmp/claude-scratch-*.txt" } ] } ] }}برای نحوِ کاملِ matcher، به مرجع Hooks مراجعه کن.
فیلتر کردن بر اساسِ نامِ ابزار و آرگومانها با فیلدِ if
Section titled “فیلتر کردن بر اساسِ نامِ ابزار و آرگومانها با فیلدِ if”فیلدِ if از نحوِ قاعدهی مجوز برای فیلترکردنِ hookها بر اساسِ نامِ ابزار و آرگومانها با هم استفاده میکند، بنابراین پردازشِ hook فقط وقتی فراخوانیِ ابزار منطبق شود ایجاد میشود. این فراتر از matcher میرود، که در سطحِ گروه فقط بر اساسِ نامِ ابزار فیلتر میکند.
برای مثال، برای اجرای یک hook فقط وقتی Claude از دستورهای git استفاده میکند نه همهی دستورهای Bash:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "if": "Bash(git *)", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh" } ] } ] }}اینکه دستورِ hookت اجرا میشود یا نه به شکلِ الگوی ifات و دستورِ Bashی که Claude صدا میزند بستگی دارد:
الگوی if | دستورِ Bash | hook اجرا میشود؟ | چرا |
|---|---|---|---|
Bash(git *) | git push | بله | نامِ دستور منطبق میشود |
Bash(git *) | npm test && git push | بله | هر زیردستور بررسی میشود؛ git push منطبق میشود |
Bash(git *) | echo $(git log) | بله | دستورهای داخلِ $() و بکتیکها بررسی میشوند؛ git log منطبق میشود |
Bash(git *) | echo $(date) | نه | هیچ زیردستوری با git * منطبق نمیشود |
Bash(git push *) | echo $(date) | بله | الگوهایی که بیش از نامِ دستور را مشخص میکنند، در هر صورت hook را روی $()، بکتیکها، یا $VAR اجرا میکنند |
این فیلتر هنگامی که دستورِ Bash قابلِ تجزیه نباشد نیز fail open میشود، یعنی hookت را صرفنظر از الگو اجرا میکند. چون فیلتر best-effort است، برای اعمالِ یک allow یا denyِ قطعی بهجای hook از سیستمِ مجوز استفاده کن.
فیلدِ if همان الگوهای قواعدِ مجوز را میپذیرد: "Bash(git *)"، "Edit(*.ts)"، و الی آخر. برای تطبیق با چند نامِ ابزار، از هندلرهای جداگانه هرکدام با مقدارِ if خودشان استفاده کن، یا در سطحِ matcher تطبیق بده که در آنجا تناوبِ pipe پشتیبانی میشود.
if فقط روی رویدادهای ابزار کار میکند: PreToolUse، PostToolUse، PostToolUseFailure، PermissionRequest، و PermissionDenied. افزودنِ آن به هر رویدادِ دیگر مانعِ اجرای hook میشود.
پیکربندیِ محلِ hook
Section titled “پیکربندیِ محلِ hook”جایی که hook را اضافه میکنی دامنهاش را تعیین میکند:
| محل | دامنه | قابلِ همرسانی |
|---|---|---|
~/.claude/settings.json | همهی پروژههایت | نه، محلیِ ماشینِ توست |
.claude/settings.json | یک پروژهی واحد | بله، میتواند به مخزن کامیت شود |
.claude/settings.local.json | یک پروژهی واحد | نه، وقتی Claude Code میسازدش gitignore میشود |
| تنظیماتِ خطمشیِ مدیریتشده | سراسرِ سازمان | بله، تحتِ کنترلِ ادمین |
پلاگین hooks/hooks.json | وقتی پلاگین فعال است | بله، همراهِ پلاگین بستهبندی میشود |
| frontmatterِ Skill یا agent | تا وقتی skill یا agent فعال است | بله، در فایلِ کامپوننت تعریف میشود |
/hooks را در Claude Code اجرا کن تا همهی hookهای پیکربندیشده را گروهبندیشده بر اساسِ رویداد مرور کنی. برای غیرفعالکردنِ hookها، "disableAllHooks": true را در فایلِ تنظیماتت تنظیم کن. hookهای پیکربندیشده در تنظیماتِ مدیریتشده همچنان اجرا میشوند مگر اینکه disableAllHooks آنجا هم تنظیم شده باشد.
اگر فایلهای تنظیمات را مستقیماً حین اجرای Claude Code ویرایش کنی، پایشگرِ فایل معمولاً تغییراتِ hook را بهطور خودکار برمیدارد.
hooksِ مبتنیبر پرامپت
Section titled “hooksِ مبتنیبر پرامپت”برای تصمیمهایی که بهجای قواعدِ قطعی نیاز به قضاوت دارند، از hookهای type: "prompt" استفاده کن. بهجای اجرای یک دستورِ شِل، Claude Code پرامپتت و دادهی ورودیِ hook را به یک مدلِ Claude (بهطور پیشفرض Haiku) میفرستد تا تصمیم بگیرد. اگر به توانمندیِ بیشتری نیاز داری میتوانی با فیلدِ model مدلِ دیگری را مشخص کنی.
تنها وظیفهی مدل بازگرداندنِ یک تصمیمِ بله/خیر بهصورت JSON است:
"ok": true: کار پیش میرود"ok": false: آنچه اتفاق میافتد به رویداد بستگی دارد:StopوSubagentStop: مقدارِreasonبه Claude بازگردانده میشود تا به کار ادامه دهدPreToolUse: فراخوانیِ ابزار رد میشود و مقدارِreasonبهعنوان خطای ابزار به Claude بازگردانده میشود، تا بتواند تنظیم کند و ادامه دهدPostToolUse،PostToolBatch،UserPromptSubmit، وUserPromptExpansion: نوبت پایان مییابد و مقدارِreasonبهعنوان یک خطِ هشدار در چت ظاهر میشود
این مثال از یک hookِ Stop استفاده میکند تا از مدل بپرسد آیا همهی کارهای درخواستشده کاملاند. اگر مدل "ok": false بازگرداند، Claude به کار ادامه میدهد و مقدارِ reason را بهعنوان دستورِ بعدیاش به کار میگیرد:
{ "hooks": { "Stop": [ { "hooks": [ { "type": "prompt", "prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}." } ] } ] }}برای گزینههای کاملِ پیکربندی، به hooksِ مبتنیبر پرامپت در مرجع مراجعه کن.
hooksِ مبتنیبر ایجنت
Section titled “hooksِ مبتنیبر ایجنت”وقتی راستیآزمایی نیازمندِ بازرسیِ فایلها یا اجرای دستورهاست، از hookهای type: "agent" استفاده کن. برخلافِ hookهای پرامپت که یک فراخوانیِ تکیِ LLM انجام میدهند، hookهای ایجنت یک subagent ایجاد میکنند که میتواند فایلها را بخواند، کد را جستجو کند، و از ابزارهای دیگر برای راستیآزماییِ شرطها پیش از بازگرداندنِ یک تصمیم استفاده کند.
hookهای ایجنت از همان قالبِ پاسخِ "ok" / "reason"ِ hookهای پرامپت استفاده میکنند، اما با یک تایماوتِ پیشفرضِ طولانیترِ ۶۰ ثانیه و تا ۵۰ نوبتِ استفاده از ابزار.
این مثال راستیآزمایی میکند که تستها پیش از اجازهی توقفِ Claude قبول میشوند:
{ "hooks": { "Stop": [ { "hooks": [ { "type": "agent", "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS", "timeout": 120 } ] } ] }}از hookهای پرامپت وقتی استفاده کن که دادهی ورودیِ hook بهتنهایی برای تصمیمگیری کافی است. از hookهای ایجنت وقتی استفاده کن که نیاز داری چیزی را در برابرِ وضعیتِ واقعیِ کدبیس راستیآزمایی کنی.
برای گزینههای کاملِ پیکربندی، به hooksِ مبتنیبر ایجنت در مرجع مراجعه کن.
HTTP hooks
Section titled “HTTP hooks”از hookهای type: "http" برای فرستادنِ دادهی رویداد با POST به یک نقطهپایانِ HTTP بهجای اجرای یک دستورِ شِل استفاده کن. نقطهپایان همان JSONی را که یک hookِ دستور روی stdin دریافت میکرد دریافت میکند، و نتایج را از طریقِ بدنهی پاسخِ HTTP با همان قالبِ JSON بازمیگرداند.
HTTP hooks وقتی مفیدند که بخواهی یک وبسرور، تابعِ ابری، یا سرویسِ بیرونی منطقِ hook را مدیریت کند: برای مثال، یک سرویسِ ممیزیِ مشترک که رویدادهای استفاده از ابزار را در سراسرِ یک تیم لاگ میکند.
این مثال هر استفاده از ابزار را به یک سرویسِ لاگِ محلی POST میکند:
{ "hooks": { "PostToolUse": [ { "hooks": [ { "type": "http", "url": "http://localhost:8080/hooks/tool-use", "headers": { "Authorization": "Bearer $MY_TOKEN" }, "allowedEnvVars": ["MY_TOKEN"] } ] } ] }}نقطهپایان باید یک بدنهی پاسخِ JSON با همان قالبِ خروجیِ hookهای دستور بازگرداند. برای مسدودکردنِ یک فراخوانیِ ابزار، یک پاسخِ 2xx با فیلدهای مناسبِ hookSpecificOutput بازگردان. کدهای وضعیتِ HTTP بهتنهایی نمیتوانند کارها را مسدود کنند.
مقادیرِ هدر از درونیابیِ متغیرهای محیطی با نحوِ $VAR_NAME یا ${VAR_NAME} پشتیبانی میکنند. فقط متغیرهای فهرستشده در آرایهی allowedEnvVars resolve میشوند؛ همهی ارجاعهای $VARِ دیگر خالی میمانند.
برای گزینههای کاملِ پیکربندی و مدیریتِ پاسخ، به HTTP hooks در مرجع مراجعه کن.
محدودیتها و عیبیابی
Section titled “محدودیتها و عیبیابی”محدودیتها
Section titled “محدودیتها”- hookهای دستور فقط از طریقِ stdout، stderr، و کدهای خروج ارتباط میگیرند. آنها نمیتوانند دستورهای
/یا فراخوانیهای ابزار را فعال کنند. متنِ بازگرداندهشده از طریقِadditionalContextبهعنوان یک یادآورِ سیستمی تزریق میشود که Claude آن را بهعنوان متنِ ساده میخواند. HTTP hooks بهجای آن از طریقِ بدنهی پاسخ ارتباط میگیرند. - تایماوتِ hookها بر اساسِ نوع متفاوت است. با فیلدِ
timeoutبر حسبِ ثانیه بهازای هر hook بازنویسی کن.command،http،mcp_tool: ۱۰ دقیقه.UserPromptSubmitاینها را به ۳۰ ثانیه وMessageDisplayآنها را به ۱۰ ثانیه کاهش میدهد.prompt: ۳۰ ثانیه.agent: ۶۰ ثانیه.
- hookهای
PostToolUseنمیتوانند کارها را برگردانند چون ابزار از قبل اجرا شده است. - hookهای
PermissionRequestدر حالتِ غیرتعاملی (-p) فعال نمیشوند. برای تصمیمهای مجوزِ خودکار از hookهایPreToolUseاستفاده کن. - hookهای
Stopهر وقت Claude پاسخگویی را تمام میکند فعال میشوند، نه فقط در تکمیلِ کار. آنها روی وقفههای کاربر فعال نمیشوند. خطاهای API بهجای آن StopFailure را فعال میکنند. - وقتی چند hookِ PreToolUse مقدارِ
updatedInputرا برای بازنویسیِ آرگومانهای یک ابزار بازمیگردانند، آخرین hookی که تمام شود برنده میشود. چون hookها بهصورت موازی اجرا میشوند، ترتیب غیرقطعی است. از داشتنِ بیش از یک hook که ورودیِ همان ابزار را تغییر دهد بپرهیز.
hookها و حالتهای مجوز
Section titled “hookها و حالتهای مجوز”hookهای PreToolUse پیش از هر بررسیِ حالتِ مجوز فعال میشوند. یک hook که permissionDecision: "deny" بازمیگرداند ابزار را حتی در حالتِ bypassPermissions یا با --dangerously-skip-permissions مسدود میکند. این به تو امکان میدهد خطمشیای را اعمال کنی که کاربران نمیتوانند با تغییرِ حالتِ مجوزشان دورش بزنند.
عکسش درست نیست: یک hook که "allow" بازمیگرداند قواعدِ deny از تنظیمات را دور نمیزند. hookها میتوانند محدودیتها را سفتتر کنند اما نمیتوانند فراتر از آنچه قواعدِ مجوز اجازه میدهند شلشان کنند.
hook فعال نمیشود
Section titled “hook فعال نمیشود”hook پیکربندی شده اما هرگز اجرا نمیشود.
/hooksرا اجرا کن و تأیید کن hook زیرِ رویدادِ درست ظاهر میشود- بررسی کن که الگوی matcher دقیقاً با نامِ ابزار منطبق است (matcherها به بزرگی و کوچکیِ حروف حساساند)
- راستیآزمایی کن که نوعِ رویدادِ درست را فعال میکنی (مثلاً
PreToolUseپیش از اجرای ابزار وPostToolUseپس از آن فعال میشود) - اگر از hookهای
PermissionRequestدر حالتِ غیرتعاملی (-p) استفاده میکنی، بهجایش بهPreToolUseتغییر بده
خطای hook در خروجی
Section titled “خطای hook در خروجی”پیامی مثل «PreToolUse hook error: …» در رونوشت میبینی.
- اسکریپتت بهطور غیرمنتظره با یک کدِ غیرصفر خارج شده است. آن را دستی با لولهکردنِ JSONِ نمونه آزمایش کن:
Terminal window echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.shecho $? # Check the exit code - اگر «command not found» میبینی، از مسیرهای مطلق یا
${CLAUDE_PROJECT_DIR}برای ارجاع به اسکریپتها استفاده کن. برای پرهیز کامل از quotingِ شِل،"args": []را اضافه کن تا به فرمِ exec تغییر دهی، که اسکریپت را مستقیماً بدونِ شِل ایجاد میکند - اگر «jq: command not found» میبینی،
jqرا نصب کن یا برای تجزیهی JSON از Python/Node.js استفاده کن - اگر اسکریپت اصلاً اجرا نمیشود، آن را اجراشدنی کن:
chmod +x ./my-hook.sh
/hooks هیچ hookِ پیکربندیشدهای نشان نمیدهد
Section titled “/hooks هیچ hookِ پیکربندیشدهای نشان نمیدهد”یک فایلِ تنظیمات را ویرایش کردی اما hookها در منو ظاهر نمیشوند.
- ویرایشهای فایل معمولاً بهطور خودکار برداشته میشوند. اگر پس از چند ثانیه ظاهر نشدند، ممکن است پایشگرِ فایل تغییر را از دست داده باشد: نشستت را برای واداشتن به بارگذاریِ مجدد ریاستارت کن.
- راستیآزمایی کن که JSONت معتبر است (کاما و کامنتِ انتهایی مجاز نیستند)
- تأیید کن که فایلِ تنظیمات در محلِ درست است:
.claude/settings.jsonبرای hookهای پروژه،~/.claude/settings.jsonبرای hookهای سراسری
hookِ Stop به سقفِ مسدودسازی میخورد
Section titled “hookِ Stop به سقفِ مسدودسازی میخورد”Claude بهجای توقف به کار ادامه میدهد، سپس نوبت را با هشداری پایان میدهد که hookِ Stop بیش از حد پشتِ سرِ هم مسدود کرده است.
Claude Code یک hookِ Stop را پس از ۸ بار مسدودسازیِ متوالی بدونِ پیشرفت کنار میگذارد. اسکریپتِ hookت باید بررسی کند که آیا از قبل یک ادامه را فعال کرده است. فیلدِ stop_hook_active را از ورودیِ JSON تجزیه کن و اگر true بود زود خارج شو:
#!/bin/bashINPUT=$(cat)if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then exit 0 # Allow Claude to stopfi# ... rest of your hook logicاگر hookت بهطور مشروع برای همگرایی به بیش از هشت تکرار نیاز دارد، سقف را با CLAUDE_CODE_STOP_HOOK_BLOCK_CAP بالا ببر.
اعتبارسنجیِ JSON شکست خورد
Section titled “اعتبارسنجیِ JSON شکست خورد”Claude Code یک خطای تجزیهی JSON نشان میدهد، حتی با وجودِ اینکه اسکریپتِ hookت JSONِ معتبر خروجی میدهد.
وقتی Claude Code یک hookِ دستورِ فرمِ شِل (بدونِ args) را اجرا میکند، روی macOS و Linux بهطور پیشفرض sh -c و روی Windows Git Bash را ایجاد میکند. این شِل غیرتعاملی است، اما Git Bash و برخی پیکربندیها (مثلِ BASH_ENV که به ~/.bashrc اشاره میکند) همچنان پروفایلت را source میکنند. اگر آن پروفایل شاملِ دستورهای echoِ بیقیدوشرط باشد، خروجی به ابتدای JSONِ hookت اضافه میشود:
Shell ready on arm64{"decision": "block", "reason": "Not allowed"}Claude Code تلاش میکند این را بهعنوان JSON تجزیه کند و شکست میخورد. برای رفعِ این، دستورهای echo را در پروفایلِ شِلت طوری بپیچان که فقط در شِلهای تعاملی اجرا شوند:
# In ~/.zshrc or ~/.bashrcif [[ $- == *i* ]]; then echo "Shell ready"fiمتغیرِ $- شاملِ پرچمهای شِل است، و i یعنی تعاملی. hookها در شِلهای غیرتعاملی اجرا میشوند، بنابراین echo رد میشود.
تکنیکهای دیباگ
Section titled “تکنیکهای دیباگ”نمای رونوشت، که با Ctrl+O تغییرِ وضعیت میدهد، یک خلاصهی یکخطی برای هر hookی که فعال شد نشان میدهد: موفقیت ساکت است، خطاهای مسدودکننده stderr را نشان میدهند، و خطاهای غیرمسدودکننده یک اعلانِ <hook name> hook error بهدنبالِ اولین خطِ stderr نشان میدهند.
برای جزئیاتِ کاملِ اجرا شاملِ اینکه کدام hookها منطبق شدند، کدهای خروجشان، stdout، و stderr، لاگِ دیباگ را بخوان. Claude Code را با claude --debug-file /tmp/claude.log شروع کن تا در یک مسیرِ مشخص بنویسد، سپس tail -f /tmp/claude.log را در ترمینالِ دیگری اجرا کن. اگر بدونِ آن پرچم شروع کردی، /debug را وسطِ نشست اجرا کن تا لاگگیری را فعال کنی و مسیرِ لاگ را بیابی.
بیشتر بدان
Section titled “بیشتر بدان”- مرجع Hooks: اسکیماهای کاملِ رویداد، قالبِ خروجیِ JSON، hooksِ async، و hooksِ ابزارِ MCP
- ملاحظاتِ امنیتی: پیش از استقرارِ hookها در محیطهای مشترک یا production مرور کن
- مثالِ اعتبارسنجِ دستورِ Bash: پیادهسازیِ مرجعِ کامل