رفتن به محتوا

مرجع Hooks

Hookها فرمان‌های شِلِ تعریف‌شده توسطِ کاربر، endpointهای HTTP یا پرامپت‌های LLM هستند که به‌صورت خودکار در نقاطِ مشخصی از چرخه‌ی حیاتِ Claude Code اجرا می‌شوند. از این مرجع برای پیداکردنِ اسکیمای رویدادها، گزینه‌های پیکربندی، فرمت‌های ورودی/خروجی JSON و قابلیت‌های پیشرفته مثل hookهای async، hookهای HTTP و hookهای ابزارِ MCP استفاده کن. اگر برای اولین بار hook راه می‌اندازی، به‌جای این مرجع از راهنما شروع کن.

Hookها در نقاطِ مشخصی در طولِ یک نشستِ Claude Code فعال می‌شوند. وقتی یک رویداد فعال می‌شود و یک matcher مطابقت پیدا می‌کند، Claude Code کانتکستِ JSON مربوط به آن رویداد را به handlerِ hookِ تو پاس می‌دهد. برای hookهای فرمان، ورودی روی stdin می‌رسد. برای hookهای HTTP، ورودی به‌صورتِ بدنه‌ی درخواستِ POST می‌رسد. handlerِ تو می‌تواند سپس ورودی را بررسی کند، اکشنی انجام دهد و اختیاراً یک تصمیم برگرداند. رویدادها در سه ریتم قرار می‌گیرند: یک‌بار در هر نشست (SessionStart, SessionEnd)، یک‌بار در هر نوبت (UserPromptSubmit, Stop, StopFailure)، و در هر فراخوانیِ ابزار داخلِ حلقه‌ی ایجنتیک (PreToolUse, PostToolUse):

Hook lifecycle diagram showing optional Setup feeding into SessionStart, then a per-turn loop containing UserPromptSubmit, UserPromptExpansion for slash commands, the nested agentic loop (PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart/Stop, TaskCreated, TaskCompleted), and Stop or StopFailure, followed by TeammateIdle, PreCompact, PostCompact, and SessionEnd, with Elicitation and ElicitationResult nested inside MCP tool execution, PermissionDenied as a side branch from PermissionRequest for auto-mode denials, WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, and FileChanged as standalone async events, and MessageDisplay as a display-only event that runs while assistant message text streams

جدولِ زیر خلاصه می‌کند که هر رویداد چه زمانی فعال می‌شود. بخشِ رویدادهای hook اسکیمای کاملِ ورودی و گزینه‌های کنترلِ تصمیمِ هرکدام را مستند می‌کند.

رویدادچه زمانی فعال می‌شود
SessionStartوقتی یک نشست آغاز یا از سر گرفته می‌شود
Setupوقتی Claude Code را با --init-only، یا با --init یا --maintenance در حالتِ -p اجرا می‌کنی. برای آماده‌سازیِ یک‌باره در CI یا اسکریپت‌ها
UserPromptSubmitوقتی یک پرامپت ثبت می‌کنی، پیش از آن‌که Claude پردازشش کند
UserPromptExpansionوقتی یک فرمانِ تایپ‌شده توسطِ کاربر به یک پرامپت بسط پیدا می‌کند، پیش از رسیدن به Claude. می‌تواند بسط را مسدود کند
PreToolUseپیش از اجرای یک فراخوانیِ ابزار. می‌تواند مسدودش کند
PermissionRequestوقتی یک دیالوگِ مجوز ظاهر می‌شود
PermissionDeniedوقتی یک فراخوانیِ ابزار توسطِ طبقه‌بندِ حالتِ auto رد می‌شود. {retry: true} برگردان تا به مدل بگویی می‌تواند فراخوانیِ ردشده را دوباره امتحان کند
PostToolUseپس از موفقیتِ یک فراخوانیِ ابزار
PostToolUseFailureپس از شکستِ یک فراخوانیِ ابزار
PostToolBatchپس از resolve‌شدنِ یک دسته‌ی کاملِ فراخوانی‌های موازیِ ابزار، پیش از فراخوانیِ بعدیِ مدل
Notificationوقتی Claude Code یک اعلان می‌فرستد
MessageDisplayحین نمایشِ متنِ پیامِ دستیار
SubagentStartوقتی یک ساب‌ایجنت spawn می‌شود
SubagentStopوقتی یک ساب‌ایجنت تمام می‌شود
TaskCreatedوقتی یک task از طریقِ TaskCreate در حالِ ساخته‌شدن است
TaskCompletedوقتی یک task در حالِ علامت‌خوردن به‌عنوانِ تکمیل‌شده است
Stopوقتی Claude پاسخ‌دادن را تمام می‌کند
StopFailureوقتی نوبت به‌خاطرِ یک خطای API پایان می‌یابد. خروجی و کدِ خروج نادیده گرفته می‌شوند
TeammateIdleوقتی یک هم‌تیمیِ تیمِ ایجنت قرار است idle شود
InstructionsLoadedوقتی یک فایلِ CLAUDE.md یا .claude/rules/*.md در کانتکست بارگذاری می‌شود. در آغازِ نشست و وقتی فایل‌ها در طولِ نشست به‌صورتِ lazy بارگذاری می‌شوند فعال می‌شود
ConfigChangeوقتی یک فایلِ پیکربندی در طولِ یک نشست تغییر می‌کند
CwdChangedوقتی دایرکتوریِ کاری تغییر می‌کند، مثلاً وقتی Claude یک فرمانِ cd اجرا می‌کند. برای مدیریتِ واکنشیِ محیط با ابزارهایی مثل direnv مفید است
FileChangedوقتی یک فایلِ تحتِ نظر روی دیسک تغییر می‌کند. فیلدِ matcher مشخص می‌کند کدام نام‌فایل‌ها تحتِ نظر باشند
WorktreeCreateوقتی یک worktree از طریقِ --worktree یا isolation: "worktree" در حالِ ساخته‌شدن است. رفتارِ پیش‌فرضِ git را جایگزین می‌کند
WorktreeRemoveوقتی یک worktree در حالِ حذف است، چه هنگامِ خروج از نشست چه وقتی یک ساب‌ایجنت تمام می‌شود
PreCompactپیش از فشرده‌سازیِ کانتکست
PostCompactپس از تکمیلِ فشرده‌سازیِ کانتکست
Elicitationوقتی یک سرورِ MCP در حینِ یک فراخوانیِ ابزار درخواستِ ورودیِ کاربر می‌کند
ElicitationResultپس از آن‌که کاربر به یک elicitationِ MCP پاسخ می‌دهد، پیش از آن‌که پاسخ به سرور بازگردانده شود
SessionEndوقتی یک نشست خاتمه می‌یابد

برای دیدنِ این‌که این قطعه‌ها چطور کنار هم می‌نشینند، این hookِ PreToolUse را در نظر بگیر که فرمان‌های شِلِ مخرب را مسدود می‌کند. matcher دامنه را به فراخوانی‌های ابزارِ Bash محدود می‌کند و شرطِ if آن را بیشتر محدود می‌کند به زیرفرمان‌های Bash که با rm * مطابقت دارند، پس block-rm.sh فقط وقتی هر دو فیلتر مطابقت کنند spawn می‌شود:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}

اسکریپت ورودیِ JSON را از stdin می‌خواند، فرمان را استخراج می‌کند و اگر شاملِ rm -rf باشد یک permissionDecision با مقدارِ "deny" برمی‌گرداند:

.claude/hooks/block-rm.sh
#!/bin/bash
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0 # no decision; normal permission flow applies
fi

حالا فرض کن Claude Code تصمیم می‌گیرد Bash "rm -rf /tmp/build" را اجرا کند. این اتفاقی است که می‌افتد:

Diagram of hook resolution: PreToolUse fires, the matcher checks for a Bash match, then the if condition checks for a Bash(rm *) match. If both match, the hook command runs and returns permissionDecision deny, so the tool call is blocked and Claude Code continues. If either check fails to match, the hook is skipped and the tool call is allowed to proceed.

رویداد فعال می‌شود

رویدادِ PreToolUse فعال می‌شود. Claude Code ورودیِ ابزار را به‌صورتِ JSON روی stdin به hook می‌فرستد:

{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }

بررسیِ matcher

matcherِ "Bash" با نامِ ابزار مطابقت دارد، پس این گروهِ hook فعال می‌شود. اگر matcher را حذف کنی یا از "*" استفاده کنی، این گروه در هر رخدادِ این رویداد فعال می‌شود.

بررسیِ شرطِ if

شرطِ if با مقدارِ "Bash(rm *)" مطابقت دارد، چون rm -rf /tmp/build یک زیرفرمانِ مطابق با rm * است، پس این handler spawn می‌شود. اگر فرمان npm test بود، بررسیِ if شکست می‌خورد و block-rm.sh هرگز اجرا نمی‌شد و از سربارِ spawnِ پروسه جلوگیری می‌شد. فیلدِ if اختیاری است؛ بدونِ آن، هر handler در گروهِ مطابقت‌یافته اجرا می‌شود.

handlerِ hook اجرا می‌شود

اسکریپت فرمانِ کامل را بررسی می‌کند و rm -rf را پیدا می‌کند، پس یک تصمیم روی stdout چاپ می‌کند:

{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}

اگر فرمان یک نسخه‌ی امن‌ترِ rm مثل rm file.txt بود، اسکریپت به‌جای آن به exit 0 می‌رسید. کدِ خروجِ 0 بدونِ هیچ خروجی یعنی hook تصمیمی برای گزارش ندارد، پس فراخوانیِ ابزار از مسیرِ عادیِ جریانِ مجوز ادامه می‌یابد. hook می‌تواند فراخوانی را رد کند، اما سکوت‌کردن آن را تأیید نمی‌کند.

Claude Code بر اساسِ نتیجه عمل می‌کند

Claude Code تصمیمِ JSON را می‌خواند، فراخوانیِ ابزار را مسدود می‌کند و دلیل را به Claude نشان می‌دهد.

بخشِ پیکربندی در ادامه اسکیمای کامل را مستند می‌کند، و هر بخشِ رویدادِ hook مستند می‌کند که فرمانت چه ورودی‌ای دریافت می‌کند و چه خروجی‌ای می‌تواند برگرداند.

Hookها در فایل‌های تنظیماتِ JSON تعریف می‌شوند. پیکربندی سه سطحِ تودرتو دارد:

  1. یک رویدادِ hook برای پاسخ‌دادن انتخاب کن، مثل PreToolUse یا Stop
  2. یک گروهِ matcher اضافه کن تا فیلتر کنی کِی فعال شود، مثل «فقط برای ابزارِ Bash»
  3. یک یا چند handlerِ hook تعریف کن که هنگامِ مطابقت اجرا شوند

برای راهنماییِ کامل با مثالِ شرح‌داده‌شده، چطور یک hook resolve می‌شود در بالا را ببین.

جایی که یک hook را تعریف می‌کنی دامنه‌اش را تعیین می‌کند:

محلدامنهقابلِ اشتراک
~/.claude/settings.jsonهمه‌ی پروژه‌هایتخیر، محلی روی دستگاهت
.claude/settings.jsonیک پروژه‌ی واحدبله، می‌تواند در مخزن کامیت شود
.claude/settings.local.jsonیک پروژه‌ی واحدخیر، وقتی Claude Code می‌سازدش gitignore می‌شود
تنظیماتِ سیاستِ مدیریت‌شدهسطحِ کلِ سازمانبله، تحتِ کنترلِ ادمین
Plugin hooks/hooks.jsonوقتی پلاگین فعال استبله، همراه با پلاگین بسته‌بندی می‌شود
frontmatterِ Skill یا agentتا وقتی کامپوننت فعال استبله، در فایلِ کامپوننت تعریف می‌شود

برای جزئیاتِ resolveِ فایلِ تنظیمات، settings را ببین. ادمین‌های سازمانی می‌توانند از allowManagedHooksOnly برای مسدودکردنِ hookهای کاربر، پروژه و پلاگین استفاده کنند. hookهای پلاگین‌هایی که در تنظیماتِ مدیریت‌شده‌ی enabledPlugins به‌اجبار فعال شده‌اند مستثنا هستند، پس ادمین‌ها می‌توانند hookهای بررسی‌شده را از طریقِ یک marketplaceِ سازمانی توزیع کنند. پیکربندیِ Hook را ببین.

فیلدِ matcher فیلتر می‌کند کِی hookها فعال شوند. این‌که یک matcher چطور ارزیابی می‌شود به کاراکترهایی که در آن هست بستگی دارد:

مقدارِ matcherارزیابی به‌صورتِمثال
"*"، ""، یا حذف‌شدهمطابقت با همهدر هر رخدادِ این رویداد فعال می‌شود
فقط حروف، ارقام، _ و |رشته‌ی دقیق، یا فهرستِ رشته‌های دقیقِ جداشده با |Bash فقط با ابزارِ Bash مطابقت دارد؛ Edit|Write دقیقاً با هر کدام از این دو ابزار مطابقت دارد
شاملِ هر کاراکترِ دیگریعبارتِ منظمِ JavaScript^Notebook با هر ابزاری که با Notebook شروع شود مطابقت دارد؛ mcp__memory__.* با هر ابزاری از سرورِ memory مطابقت دارد

رویدادِ FileChanged هنگامِ ساختنِ فهرستِ watchِ خود از این قواعد پیروی نمی‌کند. FileChanged را ببین.

هر نوعِ رویداد روی فیلدِ متفاوتی مطابقت می‌کند:

رویدادmatcher چه چیزی را فیلتر می‌کندنمونه مقادیرِ matcher
PreToolUse، PostToolUse، PostToolUseFailure، PermissionRequest، PermissionDeniedنامِ ابزارBash، Edit|Write، mcp__.*
SessionStartچطور نشست آغاز شدstartup، resume، clear، compact
Setupکدام پرچمِ CLI، setup را فعال کرد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
CwdChangedبدونِ پشتیبانی از matcherهمیشه در هر تغییرِ دایرکتوری فعال می‌شود
FileChangedنام‌فایل‌های واقعی برای watch (نگاه کن به FileChanged).envrc|.env
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
UserPromptExpansionنامِ فرماننام‌های skill یا فرمانِ تو
Elicitationنامِ سرورِ MCPنام‌های سرورِ MCPِ پیکربندی‌شده‌ات
ElicitationResultنامِ سرورِ MCPهمان مقادیرِ Elicitation
UserPromptSubmit، PostToolBatch، Stop، TeammateIdle، TaskCreated، TaskCompleted، WorktreeCreate، WorktreeRemove، MessageDisplayبدونِ پشتیبانی از matcherهمیشه در هر رخداد فعال می‌شود

matcher روی فیلدی از ورودیِ JSON اجرا می‌شود که Claude Code روی stdin به hookِ تو می‌فرستد. برای رویدادهای ابزار، آن فیلد tool_name است. هر بخشِ رویدادِ hook مجموعه‌ی کاملِ مقادیرِ matcher و اسکیمای ورودیِ آن رویداد را فهرست می‌کند.

این مثال یک اسکریپتِ lint را فقط وقتی Claude فایلی می‌نویسد یا ویرایش می‌کند اجرا می‌کند:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "/path/to/lint-check.sh"
}
]
}
]
}
}

UserPromptSubmit، PostToolBatch، Stop، TeammateIdle، TaskCreated، TaskCompleted، WorktreeCreate، WorktreeRemove و CwdChanged از matcher پشتیبانی نمی‌کنند و همیشه در هر رخداد فعال می‌شوند. اگر فیلدِ matcher به این رویدادها اضافه کنی، بی‌صدا نادیده گرفته می‌شود.

برای رویدادهای ابزار، می‌توانی با تنظیمِ فیلدِ if روی handlerهای منفردِ hook دقیق‌تر فیلتر کنی. if از نحوِ قاعده‌ی مجوز استفاده می‌کند تا روی نامِ ابزار و آرگومان‌ها با هم مطابقت کند، پس "Bash(git *)" وقتی اجرا می‌شود که هر زیرفرمانی از ورودیِ Bash با git * مطابقت کند و "Edit(*.ts)" فقط برای فایل‌های TypeScript اجرا می‌شود.

ابزارهای سرورِ MCP در رویدادهای ابزار (PreToolUse، PostToolUse، PostToolUseFailure، PermissionRequest، PermissionDenied) مثل ابزارهای عادی ظاهر می‌شوند، پس می‌توانی به همان شیوه‌ای که با هر نامِ ابزارِ دیگری مطابقت می‌کنی با آن‌ها مطابقت کنی.

ابزارهای MCP از الگوی نام‌گذاریِ mcp__<server>__<tool> پیروی می‌کنند، برای مثال:

  • mcp__memory__create_entities: ابزارِ create entitiesِ سرورِ Memory
  • mcp__filesystem__read_file: ابزارِ read fileِ سرورِ Filesystem
  • mcp__github__search_repositories: ابزارِ searchِ سرورِ GitHub

برای مطابقت با هر ابزاری از یک سرور، .* را به پیشوندِ سرور اضافه کن. .* الزامی است: matcherی مثل mcp__memory فقط شاملِ حروف و آندرلاین است، پس به‌عنوانِ رشته‌ی دقیق مقایسه می‌شود و با هیچ ابزاری مطابقت ندارد.

  • mcp__memory__.* با همه‌ی ابزارهای سرورِ memory مطابقت دارد
  • mcp__.*__write.* با هر ابزاری که نامش با write شروع شود از هر سروری مطابقت دارد

این مثال همه‌ی عملیاتِ سرورِ memory را لاگ می‌کند و عملیاتِ writeِ هر سرورِ MCP را اعتبارسنجی می‌کند:

{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__memory__.*",
"hooks": [
{
"type": "command",
"command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
}
]
},
{
"matcher": "mcp__.*__write.*",
"hooks": [
{
"type": "command",
"command": "/home/user/scripts/validate-mcp-write.py"
}
]
}
]
}
}

هر شیء در آرایه‌ی داخلیِ hooks یک handlerِ hook است: فرمانِ شِل، endpointِ HTTP، ابزارِ MCP، پرامپتِ LLM یا ایجنتی که هنگامِ مطابقتِ matcher اجرا می‌شود. پنج نوع وجود دارد:

  • hookهای فرمان (type: "command"): یک فرمانِ شِل اجرا می‌کنند. اسکریپتت ورودیِ JSONِ رویداد را روی stdin دریافت می‌کند و نتایج را از طریقِ کدهای خروج و stdout بازمی‌گرداند.
  • hookهای HTTP (type: "http"): ورودیِ JSONِ رویداد را به‌صورتِ یک درخواستِ HTTP POST به یک URL می‌فرستند. endpoint نتایج را از طریقِ بدنه‌ی پاسخ و با همان فرمتِ خروجیِ JSONِ hookهای فرمان بازمی‌گرداند.
  • hookهای ابزارِ MCP (type: "mcp_tool"): ابزاری را روی یک سرورِ MCPِ از پیش‌متصل فراخوانی می‌کنند. خروجیِ متنیِ ابزار مثلِ stdoutِ hookِ فرمان رفتار می‌شود.
  • hookهای پرامپت (type: "prompt"): یک پرامپت برای ارزیابیِ تک‌نوبتی به یک مدلِ Claude می‌فرستند. مدل یک تصمیمِ بله/خیر به‌صورتِ JSON برمی‌گرداند. hookهای مبتنی بر پرامپت را ببین.
  • hookهای ایجنت (type: "agent"): یک ساب‌ایجنت spawn می‌کنند که می‌تواند از ابزارهایی مثل Read، Grep و Glob استفاده کند تا پیش از برگرداندنِ یک تصمیم شرایط را تأیید کند. hookهای ایجنت آزمایشی هستند و ممکن است تغییر کنند. hookهای مبتنی بر ایجنت را ببین.

این فیلدها برای همه‌ی انواعِ hook اعمال می‌شوند:

فیلدالزامیتوضیح
typeبله"command"، "http"، "mcp_tool"، "prompt"، یا "agent"
ifخیرنحوِ قاعده‌ی مجوز برای فیلترکردنِ این‌که این hook کِی اجرا شود، مثل "Bash(git *)" یا "Edit(*.ts)". فرمانِ hook فقط اگر فراخوانیِ ابزار با الگو مطابقت کند اجرا می‌شود. برای این‌که الگوهای Bash چطور روی زیرفرمان‌ها، $() و backtickها ارزیابی می‌شوند، جدولِ مطابقتِ Bash را در پایین ببین. فقط روی رویدادهای ابزار ارزیابی می‌شود: PreToolUse، PostToolUse، PostToolUseFailure، PermissionRequest و PermissionDenied. روی رویدادهای دیگر، hookی که if تنظیم‌شده دارد هرگز اجرا نمی‌شود. از همان نحوِ قواعدِ مجوز استفاده می‌کند
timeoutخیرثانیه‌ها پیش از لغو. پیش‌فرض‌ها: 600 برای command، http و mcp_tool؛ 30 برای prompt؛ 60 برای agent. UserPromptSubmit پیش‌فرضِ command، http و mcp_tool را به 30 کاهش می‌دهد، و MessageDisplay آن را به 10 کاهش می‌دهد
statusMessageخیرپیامِ سفارشیِ spinner که حین اجرای hook نمایش داده می‌شود
onceخیراگر true باشد، یک‌بار در هر نشست اجرا می‌شود و سپس حذف می‌شود. فقط برای hookهای اعلام‌شده در frontmatterِ skill رعایت می‌شود؛ در فایل‌های تنظیمات و frontmatterِ ایجنت نادیده گرفته می‌شود

فیلدِ if دقیقاً یک قاعده‌ی مجوز نگه می‌دارد. هیچ نحوِ &&، || یا فهرستی برای ترکیبِ قواعد وجود ندارد؛ برای اعمالِ چند شرط، برای هرکدام یک handlerِ hookِ جداگانه تعریف کن.

برای الگوهای Bash، این‌که فرمانِ hookت اجرا شود یا نه به شکلِ الگو و فرمانِ Bashی که Claude فراخوانی می‌کند بستگی دارد. انتسابات VAR=valueِ ابتدایی پیش از مطابقت حذف می‌شوند.

الگوی ifفرمانِ Bashhook اجرا می‌شود؟چرا
Bash(git *)FOO=bar git pushبلهانتسابات ابتدایی حذف می‌شوند؛ git push مطابقت دارد
Bash(git *)npm test && git pushبلههر زیرفرمان بررسی می‌شود؛ git push مطابقت دارد
Bash(rm *)echo $(rm -rf /)بلهفرمان‌های داخلِ $() و backtickها بررسی می‌شوند؛ rm -rf / مطابقت دارد
Bash(rm *)echo $(date)خیرهیچ زیرفرمانی با rm * مطابقت ندارد
Bash(git push *)echo $(date)بلهالگوهایی که بیش از نامِ فرمان را مشخص می‌کنند، روی $()، backtickها یا $VAR به‌هرحال hook را اجرا می‌کنند

این فیلتر هنگامی که فرمانِ Bash قابلِ پارس نباشد نیز fail open می‌کند و hookت را صرفِ‌نظر از الگو اجرا می‌کند. چون فیلترِ if بهترین‌تلاش (best-effort) است، برای اعمالِ یک allow یا deny قطعی به‌جای hook از سیستمِ مجوز استفاده کن.

علاوه بر فیلدهای مشترک، hookهای فرمان این فیلدها را می‌پذیرند:

فیلدالزامیتوضیح
commandبلهفرمانِ شِل برای اجرا. به همراه args، فایلِ اجرایی‌ای که مستقیماً spawn می‌شود. فرمِ exec و فرمِ shell را ببین
argsخیرفهرستِ آرگومان‌ها. وقتی حاضر باشد، command به‌عنوانِ یک فایلِ اجرایی resolve می‌شود و مستقیماً با args به‌عنوانِ بردارِ آرگومان، بدونِ دخالتِ هیچ شِلی spawn می‌شود. فرمِ exec و فرمِ shell را ببین
asyncخیراگر true باشد، در پس‌زمینه و بدونِ مسدودکردن اجرا می‌شود. اجرای hookها در پس‌زمینه را ببین
asyncRewakeخیراگر true باشد، در پس‌زمینه اجرا می‌شود و در کدِ خروجِ 2 Claude را بیدار می‌کند. به‌طورِ ضمنی async را در پی دارد. stderrِ hook، یا stdout اگر stderr خالی باشد، به‌صورتِ یک system reminder به Claude نشان داده می‌شود تا بتواند به یک شکستِ پس‌زمینه‌ی طولانی واکنش نشان دهد
shellخیرشِلی که برای این hook استفاده می‌شود. "bash" (پیش‌فرض) یا "powershell" را می‌پذیرد. تنظیمِ "powershell" فرمان را روی Windows از طریقِ PowerShell اجرا می‌کند. چون hookها مستقیماً PowerShell را spawn می‌کنند، به CLAUDE_CODE_USE_POWERSHELL_TOOL نیاز ندارد. وقتی args تنظیم شده باشد نادیده گرفته می‌شود

یک hookِ فرمان وقتی args تنظیم شده باشد به‌صورتِ فرمِ exec اجرا می‌شود، و وقتی args حذف شده باشد به‌صورتِ فرمِ shell. هر وقت hook به یک جانگهدارِ مسیر اشاره می‌کند args را تنظیم کن، چون هر عنصر به‌عنوانِ یک آرگومان بدونِ هیچ نقل‌قولی پاس داده می‌شود. وقتی به قابلیت‌های شِل مثل pipe یا && نیاز داری، یا وقتی هیچ‌کدام از این دغدغه‌ها مطرح نیست، args را حذف کن.

فرمِ exec وقتی args حاضر باشد اجرا می‌شود. Claude Code‏ command را به‌عنوانِ یک فایلِ اجرایی روی PATH resolve می‌کند و مستقیماً با args به‌عنوانِ بردارِ آرگومان spawnش می‌کند. هیچ شِلی در کار نیست، پس هر عنصرِ args دقیقاً همان‌طور که نوشته شده یک آرگومان است، و جانگهدارهای مسیر مثل ${CLAUDE_PLUGIN_ROOT} به‌صورتِ رشته‌های ساده در command و در هر عنصرِ args جایگزین می‌شوند. کاراکترهای خاص مثل apostrophe، $ و backtick بی‌کم‌وکاست عبور می‌کنند چون هیچ شِلی برای تفسیرشان نیست. هیچ tokenization شِلی روی هیچ پلتفرمی رخ نمی‌دهد.

فرمِ shell وقتی args غایب باشد اجرا می‌شود. رشته‌ی command به یک شِل پاس داده می‌شود: sh -c روی macOS و Linux، Git Bash روی Windows، یا PowerShell وقتی Git Bash نصب نباشد. فیلدِ shell را تنظیم کن تا صریحاً انتخاب کنی. شِل رشته را tokenize می‌کند، متغیرها را بسط می‌دهد و pipeها، &&، redirectها و globها را تفسیر می‌کند.

این مثال یک اسکریپتِ Node را که همراه با یک پلاگین بسته‌بندی شده اجرا می‌کند. فرمِ exec مسیرِ resolveشده‌ی اسکریپت را به‌عنوانِ یک آرگومانِ بدونِ نقل‌قول پاس می‌دهد:

{
"type": "command",
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/format.js", "--fix"]
}

فرمِ shellِ معادل برای مدیریتِ مسیرهای دارای فاصله یا کاراکترهای خاص به نقل‌قول نیاز دارد:

{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/format.js --fix"
}

هر دو فرم همان جانگهدارهای مسیر را پشتیبانی می‌کنند، و هر دو آن‌ها را به‌عنوانِ متغیرهای محیطیِ CLAUDE_PROJECT_DIR، CLAUDE_PLUGIN_ROOT و CLAUDE_PLUGIN_DATA روی پروسه‌ی spawnشده export می‌کنند، پس یک اسکریپت می‌تواند process.env.CLAUDE_PLUGIN_ROOT را بخواند صرفِ‌نظر از این‌که چطور راه‌اندازی شده. hookهای پلاگین علاوه بر این مقادیرِ ${user_config.*} را جایگزین می‌کنند؛ پیکربندیِ کاربر را ببین.

علاوه بر فیلدهای مشترک، hookهای HTTP این فیلدها را می‌پذیرند:

فیلدالزامیتوضیح
urlبلهURLی که درخواستِ POST به آن فرستاده می‌شود
headersخیرهدرهای اضافیِ HTTP به‌صورتِ جفت‌های کلید-مقدار. مقادیر از درون‌یابیِ متغیرِ محیطی با نحوِ $VAR_NAME یا ${VAR_NAME} پشتیبانی می‌کنند. فقط متغیرهای فهرست‌شده در allowedEnvVars resolve می‌شوند
allowedEnvVarsخیرفهرستِ نام‌های متغیرِ محیطی که می‌توانند در مقادیرِ هدر درون‌یابی شوند. ارجاع به متغیرهای فهرست‌نشده با رشته‌ی خالی جایگزین می‌شوند. برای کارکردنِ هرگونه درون‌یابیِ متغیرِ محیطی الزامی است

Claude Code‏ ورودیِ JSONِ hook را به‌عنوانِ بدنه‌ی درخواستِ POST با Content-Type: application/json می‌فرستد. بدنه‌ی پاسخ از همان فرمتِ خروجیِ JSONِ hookهای فرمان استفاده می‌کند.

مدیریتِ خطا با hookهای فرمان فرق دارد: پاسخ‌های غیرِ-2xx، شکستِ اتصال و timeoutها همگی خطاهای غیرمسدودکننده تولید می‌کنند که اجازه می‌دهند اجرا ادامه یابد. برای مسدودکردنِ یک فراخوانیِ ابزار یا ردِ یک مجوز، یک پاسخِ 2xx با بدنه‌ی JSONِ شاملِ decision: "block" یا یک hookSpecificOutput با permissionDecision: "deny" برگردان.

این مثال رویدادهای PreToolUse را به یک سرویسِ اعتبارسنجیِ محلی می‌فرستد و با یک توکن از متغیرِ محیطیِ MY_TOKEN احراز هویت می‌کند:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"timeout": 30,
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}

علاوه بر فیلدهای مشترک، hookهای ابزارِ MCP این فیلدها را می‌پذیرند:

فیلدالزامیتوضیح
serverبلهنامِ یک سرورِ MCPِ پیکربندی‌شده. سرور باید از پیش متصل باشد؛ hook هرگز یک جریانِ OAuth یا اتصال را راه نمی‌اندازد
toolبلهنامِ ابزاری که روی آن سرور فراخوانی می‌شود
inputخیرآرگومان‌های پاس‌داده‌شده به ابزار. مقادیرِ رشته‌ای از جایگزینیِ ${path} از ورودیِ JSONِ hook پشتیبانی می‌کنند، مثلِ "${tool_input.file_path}"

محتوای متنیِ ابزار مثلِ stdoutِ hookِ فرمان رفتار می‌شود: اگر به‌عنوانِ خروجیِ JSONِ معتبر پارس شود به‌عنوانِ یک تصمیم پردازش می‌شود، در غیرِ این‌صورت به‌صورتِ متنِ ساده نشان داده می‌شود. اگر سرورِ نام‌برده‌شده متصل نباشد، یا ابزار isError: true برگرداند، hook یک خطای غیرمسدودکننده تولید می‌کند و اجرا ادامه می‌یابد.

hookهای ابزارِ MCP روی هر رویدادِ hook در دسترس‌اند به‌محض این‌که Claude Code به سرورهای MCPت متصل شده باشد. SessionStart و Setup معمولاً پیش از اتمامِ اتصالِ سرورها فعال می‌شوند، پس hookهای آن رویدادها باید انتظارِ خطای «متصل نیست» را در اولین اجرا داشته باشند.

این مثال ابزارِ security_scan را روی سرورِ MCPِ my_server پس از هر Write یا Edit فراخوانی می‌کند و مسیرِ فایلِ ویرایش‌شده را پاس می‌دهد:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "my_server",
"tool": "security_scan",
"input": { "file_path": "${tool_input.file_path}" }
}
]
}
]
}
}

فیلدهای hookِ پرامپت و ایجنت

Section titled “فیلدهای hookِ پرامپت و ایجنت”

علاوه بر فیلدهای مشترک، hookهای پرامپت و ایجنت این فیلدها را می‌پذیرند:

فیلدالزامیتوضیح
promptبلهمتنِ پرامپتی که به مدل فرستاده می‌شود. از $ARGUMENTS به‌عنوانِ جانگهدارِ JSONِ ورودیِ hook استفاده کن. برای گنجاندنِ متنِ تحت‌اللفظی با backslash escape کن: \$1.00 به‌صورتِ $1.00 رندر می‌شود
modelخیرمدلی که برای ارزیابی استفاده می‌شود. پیش‌فرض یک مدلِ سریع

همه‌ی hookهای مطابقت‌یافته به‌صورتِ موازی اجرا می‌شوند، و handlerهای یکسان به‌صورتِ خودکار حذفِ تکراری می‌شوند. hookهای فرمان بر اساسِ رشته‌ی فرمان و args حذفِ تکراری می‌شوند، و hookهای HTTP بر اساسِ URL. handlerها در دایرکتوریِ جاری و با محیطِ Claude Code اجرا می‌شوند. متغیرِ محیطیِ $CLAUDE_CODE_REMOTE در محیط‌های وبِ ریموت روی "true" تنظیم می‌شود و در CLIِ محلی تنظیم نمی‌شود.

ارجاع به اسکریپت‌ها از طریقِ مسیر

Section titled “ارجاع به اسکریپت‌ها از طریقِ مسیر”

از این جانگهدارها برای ارجاع به اسکریپت‌های hook نسبت به ریشه‌ی پروژه یا پلاگین استفاده کن، صرفِ‌نظر از دایرکتوریِ کاری هنگامِ اجرای hook:

  • ${CLAUDE_PROJECT_DIR}: ریشه‌ی پروژه. Claude Code این متغیر را در محیطِ سرورهای MCPِ stdio و سرورهای LSPِ پلاگین نیز تنظیم می‌کند.
  • ${CLAUDE_PLUGIN_ROOT}: دایرکتوریِ نصبِ پلاگین، برای اسکریپت‌هایی که همراه با یک پلاگین بسته‌بندی شده‌اند. در هر به‌روزرسانیِ پلاگین تغییر می‌کند.
  • ${CLAUDE_PLUGIN_DATA}: دایرکتوریِ داده‌ی پایدارِ پلاگین، برای وابستگی‌ها و حالتی که باید از به‌روزرسانی‌های پلاگین جان به‌در ببرند.

برای هر hookی که به یک جانگهدارِ مسیر اشاره می‌کند، فرمِ exec را ترجیح بده. فرمِ exec هر عنصرِ args را به‌عنوانِ یک آرگومان بدونِ tokenization شِل پاس می‌دهد، پس مسیرهای دارای فاصله یا کاراکترهای خاص به هیچ نقل‌قولی نیاز ندارند. در فرمِ shell، هر جانگهدار را در نقل‌قولِ دوگانه بپیچ.

این مثال از ${CLAUDE_PROJECT_DIR} استفاده می‌کند تا یک بررسی‌کننده‌ی استایل را از دایرکتوریِ .claude/hooks/ِ پروژه پس از هر فراخوانیِ ابزارِ Write یا Edit اجرا کند:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh",
"args": []
}
]
}
]
}
}

hookها در skillها و ایجنت‌ها

Section titled “hookها در skillها و ایجنت‌ها”

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

همه‌ی رویدادهای hook پشتیبانی می‌شوند. برای ساب‌ایجنت‌ها، hookهای Stop به‌صورتِ خودکار به SubagentStop تبدیل می‌شوند چون آن رویدادی است که هنگامِ تکمیلِ یک ساب‌ایجنت فعال می‌شود.

hookها از همان فرمتِ پیکربندیِ hookهای مبتنی بر تنظیمات استفاده می‌کنند اما به طولِ عمرِ کامپوننت محدود می‌شوند و وقتی کامپوننت تمام می‌شود پاک‌سازی می‌شوند.

این skill یک hookِ PreToolUse تعریف می‌کند که پیش از هر فرمانِ Bash یک اسکریپتِ اعتبارسنجیِ امنیتی اجرا می‌کند:

---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
---

ایجنت‌ها از همان فرمت در frontmatterِ YAMLِ خود استفاده می‌کنند.

در Claude Code‏ /hooks را تایپ کن تا یک مرورگرِ فقط‌خواندنی برای hookهای پیکربندی‌شده‌ات باز شود. منو هر رویدادِ hook را با شمارشِ hookهای پیکربندی‌شده نشان می‌دهد، اجازه می‌دهد به matcherها وارد شوی، و جزئیاتِ کاملِ هر handlerِ hook را نشان می‌دهد. از آن برای تأییدِ پیکربندی، بررسیِ این‌که یک hook از کدام فایلِ تنظیمات آمده، یا بازرسیِ فرمان، پرامپت یا URLِ یک hook استفاده کن.

منو هر پنج نوعِ hook را نمایش می‌دهد: command، prompt، agent، http و mcp_tool. هر hook با یک پیشوندِ [type] و یک منبع که نشان می‌دهد کجا تعریف شده برچسب می‌خورد:

  • User: از ~/.claude/settings.json
  • Project: از .claude/settings.json
  • Local: از .claude/settings.local.json
  • Plugin: از hooks/hooks.jsonِ یک پلاگین
  • Session: ثبت‌شده در حافظه برای نشستِ جاری
  • Built-in: ثبت‌شده به‌صورتِ داخلی توسطِ Claude Code

انتخابِ یک hook یک نمای جزئیات باز می‌کند که رویداد، matcher، نوع، فایلِ منبع و فرمان، پرامپت یا URLِ کاملش را نشان می‌دهد. منو فقط‌خواندنی است: برای افزودن، تغییر یا حذفِ hookها، تنظیماتِ JSON را مستقیماً ویرایش کن یا از Claude بخواه تغییر را اعمال کند.

غیرفعال‌کردن یا حذفِ hookها

Section titled “غیرفعال‌کردن یا حذفِ hookها”

برای حذفِ یک hook، ورودی‌اش را از فایلِ تنظیماتِ JSON پاک کن.

برای غیرفعال‌کردنِ موقتِ همه‌ی hookها بدونِ حذفشان، "disableAllHooks": true را در فایلِ تنظیماتت تنظیم کن. هیچ راهی برای غیرفعال‌کردنِ یک hookِ منفرد ضمنِ نگه‌داشتنش در پیکربندی وجود ندارد.

تنظیمِ disableAllHooks سلسله‌مراتبِ تنظیماتِ مدیریت‌شده را رعایت می‌کند. اگر یک ادمین hookهایی را از طریقِ تنظیماتِ سیاستِ مدیریت‌شده پیکربندی کرده باشد، disableAllHooksِ تنظیم‌شده در تنظیماتِ کاربر، پروژه یا محلی نمی‌تواند آن hookهای مدیریت‌شده را غیرفعال کند. فقط disableAllHooksِ تنظیم‌شده در سطحِ تنظیماتِ مدیریت‌شده می‌تواند hookهای مدیریت‌شده را غیرفعال کند.

ویرایش‌های مستقیمِ hookها در فایل‌های تنظیمات معمولاً به‌صورتِ خودکار توسطِ ناظرِ فایل (file watcher) برداشته می‌شوند.

hookهای فرمان داده‌ی JSON را از طریقِ stdin دریافت می‌کنند و نتایج را از طریقِ کدهای خروج، stdout و stderr بازمی‌گردانند. hookهای HTTP همان JSON را به‌عنوانِ بدنه‌ی درخواستِ POST دریافت می‌کنند و نتایج را از طریقِ بدنه‌ی پاسخِ HTTP بازمی‌گردانند. این بخش فیلدها و رفتارِ مشترکِ همه‌ی رویدادها را پوشش می‌دهد. هر بخشِ رویداد زیرِ رویدادهای hook اسکیمای ورودیِ خاصِ خود و گزینه‌های کنترلِ تصمیمش را شامل می‌شود.

روی macOS و Linux، از نسخه‌ی v2.1.139 به بعد hookهای فرمان در نشستِ خودشان و بدونِ ترمینالِ کنترل‌کننده اجرا می‌شوند. پروسه‌ی hook و هر پروسه‌ی فرزندی نمی‌توانند /dev/tty را باز کنند یا توالی‌های escape را مستقیماً به رابطِ Claude Code بفرستند. Windows هیچ /dev/ttyای ندارد. برای آشکارکردنِ یک پیام به کاربر روی هر پلتفرمی، systemMessage را در خروجیِ JSON برگردان. برای راه‌اندازیِ یک اعلانِ دسکتاپ، تنظیمِ عنوانِ پنجره یا زدنِ زنگ، به‌جای آن terminalSequence را برگردان.

رویدادهای hook این فیلدها را به‌صورتِ JSON دریافت می‌کنند، علاوه بر فیلدهای خاصِ رویداد که در هر بخشِ رویدادِ hook مستند شده‌اند. برای hookهای فرمان، این JSON از طریقِ stdin می‌رسد. برای hookهای HTTP، به‌صورتِ بدنه‌ی درخواستِ POST می‌رسد.

فیلدتوضیح
session_idشناسه‌ی نشستِ جاری
transcript_pathمسیرِ JSONِ گفت‌وگو
cwdدایرکتوریِ کاریِ جاری هنگامِ فراخوانیِ hook
permission_modeحالتِ مجوزِ جاری: "default"، "plan"، "acceptEdits"، "auto"، "dontAsk"، یا "bypassPermissions". همه‌ی رویدادها این فیلد را دریافت نمی‌کنند: مثالِ JSONِ هر رویداد در پایین را برای بررسی ببین
effortشیئی با فیلدِ level که سطحِ effortِ فعالِ این نوبت را نگه می‌دارد: "low"، "medium"، "high"، "xhigh"، یا "max". اگر effortِ درخواستیِ مدل از آنچه مدلِ جاری پشتیبانی می‌کند فراتر برود، این همان سطحِ کاهش‌یافته‌ای است که مدل واقعاً استفاده کرد. Ultracode یک سطحِ مجزا نیست و به‌صورتِ "xhigh" گزارش می‌شود. این شیء با فیلدِ effortِ status line مطابقت دارد. برای رویدادهایی که در یک کانتکستِ استفاده‌ی ابزار فعال می‌شوند، مثلِ PreToolUse، PostToolUse، Stop و SubagentStop، وقتی مدلِ جاری از پارامترِ effort پشتیبانی می‌کند حاضر است. این سطح برای فرمان‌های hook و ابزارِ Bash نیز به‌صورتِ متغیرِ محیطیِ $CLAUDE_EFFORT در دسترس است.
hook_event_nameنامِ رویدادی که فعال شد

وقتی با --agent یا داخلِ یک ساب‌ایجنت اجرا می‌شوی، دو فیلدِ اضافی گنجانده می‌شوند:

فیلدتوضیح
agent_idشناسه‌ی یکتای ساب‌ایجنت. فقط وقتی hook داخلِ یک فراخوانیِ ساب‌ایجنت فعال می‌شود حاضر است. از این برای تمایزِ فراخوانی‌های hookِ ساب‌ایجنت از فراخوانی‌های نخِ اصلی استفاده کن.
agent_typeنامِ ایجنت (برای مثال، "Explore" یا "security-reviewer"). وقتی نشست از --agent استفاده می‌کند یا hook داخلِ یک ساب‌ایجنت فعال می‌شود حاضر است. برای ساب‌ایجنت‌ها، نوعِ ساب‌ایجنت بر مقدارِ --agentِ نشست اولویت دارد. برای ساب‌ایجنت‌های سفارشی، این فیلدِ name از frontmatterِ ایجنت است، نه نام‌فایل.

فقط hookهای SessionStart می‌توانند یک فیلدِ model دریافت کنند، و حضورش تضمین نشده است. هیچ متغیرِ محیطیِ $CLAUDE_MODEL وجود ندارد. یک پروسه‌ی hook محیطِ والد را به ارث می‌برد، پس می‌تواند $ANTHROPIC_MODEL را بخواند اگر آن را در شِلت تنظیم کنی، اما آن مقدار وقتی مدل را با /model در طولِ یک نشست عوض می‌کنی تغییر نمی‌کند.

برای مثال، یک hookِ PreToolUse برای یک فرمانِ Bash این را روی stdin دریافت می‌کند:

{
"session_id": "abc123",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}

فیلدهای tool_name و tool_input خاصِ رویداد هستند. هر بخشِ رویدادِ hook فیلدهای اضافیِ آن رویداد را مستند می‌کند.

کدِ خروج از فرمانِ hookت به Claude Code می‌گوید که اکشن باید ادامه یابد، مسدود شود، یا نادیده گرفته شود.

خروجِ 0 یعنی موفقیت. Claude Code‏ stdout را برای فیلدهای خروجیِ JSON پارس می‌کند. خروجیِ JSON فقط در خروجِ 0 پردازش می‌شود. برای بیشترِ رویدادها، stdout در لاگِ دیباگ نوشته می‌شود اما در transcript نشان داده نمی‌شود. استثناها UserPromptSubmit، UserPromptExpansion و SessionStart هستند، که در آن‌ها stdout به‌عنوانِ کانتکستی که Claude می‌تواند ببیند و بر اساسش عمل کند اضافه می‌شود.

خروجِ 2 یعنی یک خطای مسدودکننده. Claude Code‏ stdout و هر JSONی در آن را نادیده می‌گیرد. در عوض، متنِ stderr به‌عنوانِ یک پیامِ خطا به Claude بازخورانده می‌شود. اثرش به رویداد بستگی دارد: PreToolUse فراخوانیِ ابزار را مسدود می‌کند، UserPromptSubmit پرامپت را رد می‌کند، و الی آخر. برای فهرستِ کامل، رفتارِ کدِ خروجِ 2 را ببین.

هر کدِ خروجِ دیگری برای بیشترِ رویدادهای hook یک خطای غیرمسدودکننده است. transcript یک اعلانِ <hook name> hook error و سپس خطِ اولِ stderr را نشان می‌دهد، تا بتوانی علت را بدونِ --debug شناسایی کنی. اجرا ادامه می‌یابد و stderrِ کامل در لاگِ دیباگ نوشته می‌شود.

برای مثال، یک اسکریپتِ فرمانِ hook که فرمان‌های خطرناکِ Bash را مسدود می‌کند:

#!/bin/bash
# Reads JSON input from stdin, checks the command
command=$(jq -r '.tool_input.command' < /dev/stdin)
if [[ "$command" == rm* ]]; then
echo "Blocked: rm commands are not allowed" >&2
exit 2 # Blocking error: tool call is prevented
fi
exit 0 # No decision: the normal permission flow applies

رفتارِ کدِ خروجِ 2 به‌ازای هر رویداد

Section titled “رفتارِ کدِ خروجِ 2 به‌ازای هر رویداد”

کدِ خروجِ 2 راهی است که یک hook با آن سیگنالِ «بایست، این کار را نکن» می‌دهد. اثرش به رویداد بستگی دارد، چون برخی رویدادها اکشن‌هایی را نمایندگی می‌کنند که می‌توان مسدودشان کرد (مثلِ یک فراخوانیِ ابزار که هنوز اتفاق نیفتاده) و برخی دیگر چیزهایی را نمایندگی می‌کنند که قبلاً اتفاق افتاده‌اند یا قابلِ جلوگیری نیستند.

رویدادِ hookقابلِ مسدودکردن؟در خروجِ 2 چه می‌شود
PreToolUseبلهفراخوانیِ ابزار را مسدود می‌کند
PermissionRequestبلهمجوز را رد می‌کند
UserPromptSubmitبلهپردازشِ پرامپت را مسدود می‌کند و پرامپت را پاک می‌کند
UserPromptExpansionبلهبسط را مسدود می‌کند
Stopبلهاز توقفِ Claude جلوگیری می‌کند، گفت‌وگو را ادامه می‌دهد
SubagentStopبلهاز توقفِ ساب‌ایجنت جلوگیری می‌کند
TeammateIdleبلهاز idle‌شدنِ هم‌تیمی جلوگیری می‌کند (هم‌تیمی به کارکردن ادامه می‌دهد)
TaskCreatedبلهساختِ task را برمی‌گرداند
TaskCompletedبلهاز علامت‌خوردنِ task به‌عنوانِ تکمیل‌شده جلوگیری می‌کند
ConfigChangeبلهاز اعمال‌شدنِ تغییرِ پیکربندی جلوگیری می‌کند (به‌جز policy_settings)
StopFailureخیرخروجی و کدِ خروج نادیده گرفته می‌شوند
PostToolUseخیرstderr را به Claude نشان می‌دهد (ابزار قبلاً اجرا شده)
PostToolUseFailureخیرstderr را به Claude نشان می‌دهد (ابزار قبلاً شکست خورده)
PostToolBatchبلهحلقه‌ی ایجنتیک را پیش از فراخوانیِ بعدیِ مدل متوقف می‌کند
PermissionDeniedخیرکدِ خروج و stderr نادیده گرفته می‌شوند (رد قبلاً رخ داده). از JSONِ hookSpecificOutput.retry: true برای گفتن به مدل که می‌تواند دوباره امتحان کند استفاده کن
Notificationخیرstderr را فقط به کاربر نشان می‌دهد
SubagentStartخیرstderr را فقط به کاربر نشان می‌دهد
SessionStartخیرstderr را فقط به کاربر نشان می‌دهد
Setupخیرstderr را فقط به کاربر نشان می‌دهد
SessionEndخیرstderr را فقط به کاربر نشان می‌دهد
CwdChangedخیرstderr را فقط به کاربر نشان می‌دهد
FileChangedخیرstderr را فقط به کاربر نشان می‌دهد
PreCompactبلهفشرده‌سازی را مسدود می‌کند
PostCompactخیرstderr را فقط به کاربر نشان می‌دهد
Elicitationبلهelicitation را رد می‌کند
ElicitationResultبلهپاسخ را مسدود می‌کند (اکشن تبدیل به decline می‌شود)
WorktreeCreateبلههر کدِ خروجِ غیرصفر باعثِ شکستِ ساختِ worktree می‌شود
WorktreeRemoveخیرشکست‌ها فقط در حالتِ دیباگ لاگ می‌شوند
InstructionsLoadedخیرکدِ خروج نادیده گرفته می‌شود
MessageDisplayخیرمتنِ اصلی نمایش داده می‌شود

hookهای HTTP به‌جای کدهای خروج و stdout از کدهای وضعیتِ HTTP و بدنه‌های پاسخ استفاده می‌کنند:

  • 2xx با بدنه‌ی خالی: موفقیت، معادلِ کدِ خروجِ 0 بدونِ خروجی
  • 2xx با بدنه‌ی متنِ ساده: موفقیت، متن به‌عنوانِ کانتکست اضافه می‌شود
  • 2xx با بدنه‌ی JSON: موفقیت، با همان اسکیمای خروجیِ JSONِ hookهای فرمان پارس می‌شود
  • وضعیتِ غیرِ-2xx: خطای غیرمسدودکننده، اجرا ادامه می‌یابد
  • شکستِ اتصال یا timeout: خطای غیرمسدودکننده، اجرا ادامه می‌یابد

برخلافِ hookهای فرمان، hookهای HTTP نمی‌توانند تنها از طریقِ کدهای وضعیت یک خطای مسدودکننده سیگنال بدهند. برای مسدودکردنِ یک فراخوانیِ ابزار یا ردِ یک مجوز، یک پاسخِ 2xx با بدنه‌ی JSONِ شاملِ فیلدهای تصمیمِ مناسب برگردان.

کدهای خروج فقط اجازه می‌دهند مسدود کنی یا ساکت بمانی، اما خروجیِ JSON کنترلِ ریزدانه‌تری می‌دهد. به‌جای خروج با کدِ 2 برای مسدودکردن، با کدِ 0 خارج شو و یک شیء JSON روی stdout چاپ کن. Claude Code فیلدهای مشخصی از آن JSON را برای کنترلِ رفتار می‌خواند، از جمله کنترلِ تصمیم برای مسدودکردن، مجازشمردن یا ارجاع به کاربر.

stdoutِ hookت باید فقط شاملِ شیء JSON باشد. اگر پروفایلِ شِلت هنگامِ راه‌اندازی متنی چاپ کند، می‌تواند در پارسِ JSON اختلال ایجاد کند. JSON validation failed در راهنمای عیب‌یابی را ببین.

رشته‌های خروجیِ hook، از جمله additionalContext، systemMessage و stdoutِ ساده، به ۱۰٬۰۰۰ کاراکتر محدود می‌شوند. خروجی‌ای که از این سقف فراتر رود در یک فایل ذخیره و با یک پیش‌نمایش و مسیرِ فایل جایگزین می‌شود، درست همان‌طور که نتایجِ بزرگِ ابزار مدیریت می‌شوند.

شیء JSON سه دسته فیلد را پشتیبانی می‌کند:

  • فیلدهای جهانی مثلِ continue در همه‌ی رویدادها کار می‌کنند. این‌ها در جدولِ زیر فهرست شده‌اند.
  • decision و reasonِ سطحِ بالا توسطِ برخی رویدادها برای مسدودکردن یا ارائه‌ی بازخورد استفاده می‌شوند.
  • hookSpecificOutput یک شیء تودرتو برای رویدادهایی است که به کنترلِ غنی‌تری نیاز دارند. به یک فیلدِ hookEventName تنظیم‌شده به نامِ رویداد نیاز دارد.
فیلدپیش‌فرضتوضیح
continuetrueاگر false باشد، Claude پس از اجرای hook پردازش را کاملاً متوقف می‌کند. بر هر فیلدِ تصمیمِ خاصِ رویداد اولویت دارد
stopReasonهیچپیامی که وقتی continue برابرِ false است به کاربر نشان داده می‌شود. به Claude نشان داده نمی‌شود
suppressOutputfalseاگر true باشد، stdoutِ hook را از transcript پنهان می‌کند. stdout همچنان در لاگِ دیباگ ظاهر می‌شود
systemMessageهیچپیامِ هشداری که به کاربر نشان داده می‌شود
terminalSequenceهیچیک توالیِ escapeِ ترمینال که Claude Code از طرفِ تو منتشر می‌کند، مثلِ یک اعلانِ دسکتاپ، عنوانِ پنجره یا زنگ. به OSCِ 0/1/2/9/99/777 و BEL محدود است. اگر مقدار شاملِ چیزی خارج از فهرستِ مجاز باشد، فیلد نادیده گرفته می‌شود. از این به‌جای نوشتن در /dev/tty که برای hookها در دسترس نیست استفاده کن

برای متوقف‌کردنِ کاملِ Claude صرفِ‌نظر از نوعِ رویداد:

{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }

انتشارِ اعلان‌های ترمینال

Section titled “انتشارِ اعلان‌های ترمینال”

فیلدِ terminalSequence به Claude Code نسخه‌ی v2.1.141 یا بالاتر نیاز دارد.

hookها بدونِ ترمینالِ کنترل‌کننده اجرا می‌شوند، پس نوشتنِ توالی‌های escape مستقیماً در /dev/tty شکست می‌خورد. در عوض، توالیِ escape را در فیلدِ terminalSequence برگردان و Claude Code آن را از طریقِ مسیرِ نوشتنِ ترمینالِ خودش از طرفِ تو منتشر می‌کند. این بدونِ race است، داخلِ tmux و GNU screen کار می‌کند، و روی Windows که /dev/ttyای ندارد کار می‌کند.

این فیلد یک رشته از یک یا چند توالیِ escapeِ مجاز را می‌پذیرد:

  • OSCِ 0، 1، 2: عنوان‌های پنجره و آیکون
  • OSCِ 9: اعلان‌های iTerm2، ConEmu، Windows Terminal و WezTerm، از جمله پیشرفتِ نوارِ وظیفه‌ی 9;4
  • OSCِ 99: اعلان‌های Kitty
  • OSCِ 777: اعلان‌های urxvt، Ghostty و Warp
  • BELِ خالی

توالی‌ها می‌توانند با BEL یا با ST خاتمه یابند. هر چیزی خارج از فهرستِ مجاز، از جمله توالی‌های مکان‌نما و رنگِ CSI، توالی‌های palette‌ی OSC، hyperlinkهای OSC 8، نوشتن‌های clipboardِ OSC 52 و OSC 1337، رد می‌شود و فیلد نادیده گرفته می‌شود.

مثالِ زیر یک اعلانِ دسکتاپ را از یک hookِ Notification فعال می‌کند. توالیِ escape با escapeهای اوکتالِ printf ساخته می‌شود تا بایت‌های کنترلی هرگز روی خطِ فرمانِ شِل ظاهر نشوند، و jq -n --arg خروجیِ JSON را می‌سازد تا نقل‌قول‌ها، backslashها و newlineها در پیامِ اعلان درست escape شوند:

#!/bin/bash
# Notification hook: ping the desktop when Claude Code needs attention.
input=$(cat)
title="Claude Code"
body=$(jq -r '.message // "Needs your attention"' <<<"$input")
seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'

شکلِ { "terminalSequence": "..." } از هر شِل یا زبانی یکسان است. روی Windows، رشته‌ی escape را در PowerShell یا یک اسکریپت بساز و همان شیء JSON را منتشر کن.

افزودنِ کانتکست برای Claude

Section titled “افزودنِ کانتکست برای Claude”

فیلدِ additionalContext یک رشته را از hookت به پنجره‌ی کانتکستِ Claude پاس می‌دهد. Claude Code رشته را در یک system reminder می‌پیچد و آن را در گفت‌وگو در نقطه‌ای که hook فعال شد درج می‌کند. Claude reminder را در درخواستِ بعدیِ مدل می‌خواند، اما به‌صورتِ یک پیامِ چت در رابط ظاهر نمی‌شود.

additionalContext را داخلِ hookSpecificOutput در کنارِ نامِ رویداد برگردان:

{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
}
}

این‌که reminder کجا ظاهر می‌شود به رویداد بستگی دارد:

وقتی چند hook برای یک رویداد additionalContext برمی‌گردانند، Claude همه‌ی مقادیر را دریافت می‌کند. اگر یک مقدار از ۱۰٬۰۰۰ کاراکتر فراتر رود، Claude Code متنِ کامل را در فایلی در دایرکتوریِ نشست می‌نویسد و به‌جایش مسیرِ فایل را با یک پیش‌نمایشِ کوتاه به Claude پاس می‌دهد.

از additionalContext برای اطلاعاتی استفاده کن که Claude باید درباره‌ی وضعیتِ جاریِ محیطت یا عملیاتی که همین‌الان اجرا شد بداند:

  • وضعیتِ محیط: شاخه‌ی جاری، مقصدِ استقرار، یا feature flagهای فعال
  • قواعدِ مشروطِ پروژه: کدام فرمانِ تست برای فایلی که همین‌الان ویرایش شد اعمال می‌شود، کدام دایرکتوری‌ها در این worktree فقط‌خواندنی‌اند
  • داده‌ی بیرونی: issueهای بازِ منتسب به تو، نتایجِ اخیرِ CI، محتوای واکشی‌شده از یک سرویسِ داخلی

برای دستورالعمل‌هایی که هرگز تغییر نمی‌کنند، CLAUDE.md را ترجیح بده. بدونِ اجرای یک اسکریپت بارگذاری می‌شود و جای استانداردِ قراردادهای ایستای پروژه است.

متن را به‌صورتِ گزاره‌های واقعی بنویس نه دستورالعمل‌های سیستمیِ امری. عباراتی مثلِ «مقصدِ استقرار production است» یا «این مخزن از bun test استفاده می‌کند» به‌عنوانِ اطلاعاتِ پروژه خوانده می‌شوند. متنی که به‌صورتِ فرمان‌های سیستمیِ خارج از باند چارچوب‌بندی شود می‌تواند دفاع‌های ضدِ تزریقِ پرامپتِ Claude را فعال کند، که باعث می‌شود Claude به‌جای رفتارِ کانتکستی، متن را به تو آشکار کند.

به‌محضِ تزریق، متن در transcriptِ نشست ذخیره می‌شود. برای رویدادهای میان‌نشستی مثلِ PostToolUse یا UserPromptSubmit، از سرگیری با --continue یا --resume به‌جای اجرای دوباره‌ی hook برای نوبت‌های گذشته، متنِ ذخیره‌شده را بازپخش می‌کند، پس مقادیری مثلِ timestamp یا SHAهای کامیت هنگامِ از سرگیری کهنه می‌شوند. hookهای SessionStart هنگامِ از سرگیری دوباره اجرا می‌شوند با source تنظیم‌شده به "resume"، پس می‌توانند کانتکستشان را تازه کنند.

هر رویداد مسدودکردن یا کنترلِ رفتار از طریقِ JSON را پشتیبانی نمی‌کند. رویدادهایی که می‌کنند هرکدام مجموعه‌ی متفاوتی از فیلدها را برای بیانِ آن تصمیم به کار می‌برند. از این جدول به‌عنوانِ مرجعِ سریع پیش از نوشتنِ یک hook استفاده کن:

رویدادهاالگوی تصمیمفیلدهای کلیدی
UserPromptSubmit، UserPromptExpansion، PostToolUse، PostToolUseFailure، PostToolBatch، Stop، SubagentStop، ConfigChange، PreCompactdecisionِ سطحِ بالاdecision: "block"، reason. Stop و SubagentStop همچنین hookSpecificOutput.additionalContext را برای بازخوردِ غیرخطایی که گفت‌وگو را ادامه می‌دهد می‌پذیرند
TeammateIdle، TaskCreated، TaskCompletedکدِ خروج یا continue: falseکدِ خروجِ 2 اکشن را با بازخوردِ stderr مسدود می‌کند. JSONِ {"continue": false, "stopReason": "..."} نیز هم‌تیمی را کاملاً متوقف می‌کند، مطابق با رفتارِ hookِ Stop
PreToolUsehookSpecificOutputpermissionDecision (allow/deny/ask/defer)، permissionDecisionReason
PermissionRequesthookSpecificOutputdecision.behavior (allow/deny)
PermissionDeniedhookSpecificOutputretry: true به مدل می‌گوید می‌تواند فراخوانیِ ردشده‌ی ابزار را دوباره امتحان کند
WorktreeCreateبازگشتِ مسیرhookِ فرمان مسیر را روی stdout چاپ می‌کند؛ hookِ HTTP‏ hookSpecificOutput.worktreePath را برمی‌گرداند. شکستِ hook یا نبودِ مسیر باعثِ شکستِ ساخت می‌شود
ElicitationhookSpecificOutputaction (accept/decline/cancel)، content (مقادیرِ فیلدهای فرم برای accept)
ElicitationResulthookSpecificOutputaction (accept/decline/cancel)، content (بازنویسیِ مقادیرِ فیلدهای فرم)
MessageDisplayhookSpecificOutputdisplayContent متنِ نمایش‌داده‌شده روی صفحه را جایگزین می‌کند. فقط-نمایش: transcript و آنچه Claude می‌بیند اصل را نگه می‌دارند
SessionStart، Setup، SubagentStartفقط کانتکستhookSpecificOutput.additionalContext کانتکست برای Claude اضافه می‌کند. SessionStart همچنین initialUserMessage، watchPaths، sessionTitle و reloadSkills را می‌پذیرد. بدونِ مسدودکردن یا کنترلِ تصمیم
WorktreeRemove، Notification، SessionEnd، PostCompact، InstructionsLoaded، StopFailure، CwdChanged، FileChangedهیچبدونِ کنترلِ تصمیم. برای اثرهای جانبی مثلِ لاگ‌کردن یا پاک‌سازی استفاده می‌شود

چند رویداد می‌توانند محتوا را بازنویسی نیز بکنند به‌جای آن‌که فقط مجاز یا مسدودش کنند:

  • PreToolUseupdatedInput مستقیماً زیرِ hookSpecificOutput آرگومان‌های یک ابزار را پیش از اجرا جایگزین می‌کند (جزئیات)
  • PermissionRequestupdatedInput داخلِ شیء decision (جزئیات)
  • PostToolUseupdatedToolOutput نتیجه‌ی ابزار را جایگزین می‌کند (جزئیات)
  • UserPromptSubmit — نمی‌تواند پرامپت را جایگزین کند؛ فقط additionalContext را در کنارش تزریق می‌کند

برای مواردِ استفاده‌ی ویرایش (redaction) یا تبدیل، در PreToolUse برای ورودی‌های خروجیِ ابزار و در PostToolUse برای نتایجِ ورودیِ ابزار میانجی‌گری کن.

این‌ها مثال‌هایی از هر الگو در عمل هستند:

توسطِ UserPromptSubmit، UserPromptExpansion، PostToolUse، PostToolUseFailure، PostToolBatch، Stop، SubagentStop، ConfigChange و PreCompact استفاده می‌شود. تنها مقدار "block" است. برای اجازه‌دادن به ادامه‌ی اکشن، decision را از JSONت حذف کن، یا با کدِ 0 بدونِ هیچ JSONی خارج شو:

{
"decision": "block",
"reason": "Test suite must pass before proceeding"
}

برای مثال‌های گسترده‌تر شاملِ اعتبارسنجیِ فرمانِ Bash، فیلترِ پرامپت و اسکریپت‌های تأییدِ خودکار، چه چیزهایی را می‌توانی خودکار کنی در راهنما و پیاده‌سازیِ مرجعِ اعتبارسنجِ فرمانِ Bash را ببین.

هر رویداد متناظر با نقطه‌ای در چرخه‌ی حیاتِ Claude Code است که hookها می‌توانند در آن اجرا شوند. بخش‌های زیر مرتب شده‌اند تا با چرخه‌ی حیات مطابقت کنند: از راه‌اندازیِ نشست تا حلقه‌ی ایجنتیک تا پایانِ نشست. هر بخش توصیف می‌کند که رویداد کِی فعال می‌شود، چه matcherهایی را پشتیبانی می‌کند، چه ورودیِ JSONی دریافت می‌کند، و چطور رفتار را از طریقِ خروجی کنترل کند.

وقتی Claude Code یک نشستِ تازه آغاز می‌کند یا یک نشستِ موجود را از سر می‌گیرد اجرا می‌شود. برای بارگذاریِ کانتکستِ توسعه مثلِ issueهای موجود یا تغییراتِ اخیرِ کدبیست، یا تنظیمِ متغیرهای محیطی مفید است. برای کانتکستِ ایستا که به اسکریپت نیاز ندارد، به‌جایش از CLAUDE.md استفاده کن.

SessionStart در هر نشست اجرا می‌شود، پس این hookها را سریع نگه دار. فقط hookهای type: "command" و type: "mcp_tool" پشتیبانی می‌شوند.

مقدارِ matcher متناظر با این است که نشست چطور آغاز شده:

Matcherچه زمانی فعال می‌شود
startupنشستِ تازه
resume--resume، --continue، یا /resume
clear/clear
compactفشرده‌سازیِ خودکار یا دستی

علاوه بر فیلدهای ورودیِ مشترک، hookهای SessionStart فیلدِ source و اختیاراً model، agent_type و session_title را دریافت می‌کنند. فیلدِ source نشان می‌دهد نشست چطور آغاز شده: "startup" برای نشست‌های تازه، "resume" برای نشست‌های از سر گرفته‌شده، "clear" پس از /clear، یا "compact" پس از فشرده‌سازی. فیلدِ model شناسه‌ی مدلِ فعال را شامل می‌شود. می‌تواند حذف شود، برای مثال پس از /clear یا وقتی یک نشست از طریقِ بازیابیِ گفت‌وگو احیا می‌شود، پس پیش از خواندن فیلد را بررسی کن. اگر Claude Code را با claude --agent <name> اجرا کنی، یک فیلدِ agent_type نامِ ایجنت را شامل می‌شود. فیلدِ session_title عنوانِ نشستِ جاری را حمل می‌کند اگر یکی از قبل تنظیم شده باشد، برای مثال از طریقِ --name یا /rename. hookی که sessionTitle منتشر می‌کند می‌تواند ابتدا session_title را بررسی کند تا عنوانی را که کاربر صریحاً تنظیم کرده بازنویسی نکند.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SessionStart",
"source": "startup",
"model": "claude-sonnet-4-6"
}

هر متنی که اسکریپتِ hookت روی stdout چاپ کند به‌عنوانِ کانتکست برای Claude اضافه می‌شود. علاوه بر فیلدهای خروجیِ JSONِ در دسترسِ همه‌ی hookها، می‌توانی این فیلدهای خاصِ رویداد را برگردانی:

فیلدتوضیح
additionalContextرشته‌ای که در آغازِ گفت‌وگو، پیش از اولین پرامپت، به کانتکستِ Claude اضافه می‌شود. برای این‌که متن چطور تحویل داده می‌شود و چه چیزی در آن بگذاری، افزودنِ کانتکست برای Claude را ببین
initialUserMessageرشته‌ای که به‌عنوانِ اولین پیامِ کاربرِ نشست استفاده می‌شود. در حالتِ غیرتعاملی (-p) اعمال می‌شود، جایی که حتی اگر پرامپتی ارائه نشود اولین نوبت می‌شود. اگر پرامپتی ارائه شود، به‌عنوانِ نوبتِ بعدی دنبالش می‌آید. برخلافِ additionalContext که به یک نوبتِ موجود می‌چسبد، این یکی نوبت را می‌سازد
sessionTitleعنوانِ نشست را تنظیم می‌کند، با همان اثرِ /rename. برای نام‌گذاریِ خودکارِ نشست‌ها از پوشه‌ی راه‌اندازی، شاخه‌ی git یا نامِ worktree استفاده کن. فقط وقتی source برابرِ "startup" یا "resume" است اعمال می‌شود؛ روی "clear" و "compact" نادیده گرفته می‌شود
watchPathsآرایه‌ای از مسیرهای مطلق برای watch جهتِ رویدادهای FileChanged در طولِ این نشست
reloadSkillsبولین. وقتی true باشد، Claude Code پس از تکمیلِ hookهای SessionStart دایرکتوری‌های skill و فرمان را دوباره اسکن می‌کند، پس skillهایی که hook نصب کرده در همان نشست در دسترس‌اند، از اولین پرامپت
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2",
"sessionTitle": "auth-refactor"
}
}

چون stdoutِ ساده برای این رویداد همین‌حالا به Claude می‌رسد، hookی که فقط کانتکست بارگذاری می‌کند می‌تواند مستقیماً روی stdout چاپ کند بدونِ ساختنِ JSON. وقتی به ترکیبِ کانتکست با فیلدهای دیگری مثلِ suppressOutput یا sessionTitle نیاز داری از فرمِ JSON استفاده کن.

از reloadSkills وقتی استفاده کن که یک hookِ SessionStart‏ skillها را نصب یا به‌روز می‌کند. کشفِ skill معمولاً پیش از اتمامِ hookهای SessionStart اجرا می‌شود، پس فایل‌هایی که hook در ~/.claude/skills/ یا .claude/skills/ می‌نویسد در غیرِ این‌صورت فقط در نشستِ بعدی ظاهر می‌شوند. این مثال یک مخزنِ skillِ مشترک را همگام و درخواستِ اسکنِ دوباره می‌کند:

#!/bin/bash
git -C ~/.claude/skills/team-skills pull --quiet 2>/dev/null || \
git clone --quiet https://git.example.com/your-org/team-skills.git ~/.claude/skills/team-skills
echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "reloadSkills": true}}'

پایداری‌بخشیدن به متغیرهای محیطی

Section titled “پایداری‌بخشیدن به متغیرهای محیطی”

hookهای SessionStart به متغیرِ محیطیِ CLAUDE_ENV_FILE دسترسی دارند، که مسیرِ فایلی را فراهم می‌کند که در آن می‌توانی متغیرهای محیطی را برای فرمان‌های Bashِ بعدی پایدار کنی.

برای تنظیمِ متغیرهای محیطیِ منفرد، گزاره‌های export را در CLAUDE_ENV_FILE بنویس. از append (>>) استفاده کن تا متغیرهای تنظیم‌شده توسطِ hookهای دیگر حفظ شوند:

#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi
exit 0

برای ضبطِ همه‌ی تغییراتِ محیطی از فرمان‌های راه‌اندازی، متغیرهای export‌شده را پیش و پس مقایسه کن:

#!/bin/bash
ENV_BEFORE=$(export -p | sort)
# Run your setup commands that modify the environment
source ~/.nvm/nvm.sh
nvm use 20
if [ -n "$CLAUDE_ENV_FILE" ]; then
ENV_AFTER=$(export -p | sort)
comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi
exit 0

هر متغیری که در این فایل نوشته شود در همه‌ی فرمان‌های Bashِ بعدی که Claude Code در طولِ نشست اجرا می‌کند در دسترس خواهد بود.

فقط وقتی فعال می‌شود که Claude Code را با --init-only، یا با --init یا --maintenance در حالتِ print (-p) راه بیندازی. روی راه‌اندازیِ عادی فعال نمی‌شود. از آن برای نصبِ یک‌باره‌ی وابستگی یا پاک‌سازیِ زمان‌بندی‌شده‌ای استفاده کن که صریحاً از CI یا اسکریپت‌ها فعال می‌کنی، جدا از راه‌اندازیِ عادیِ نشست. برای راه‌اندازیِ هر-نشست، به‌جایش از SessionStart استفاده کن.

مقدارِ matcher متناظر با پرچمِ CLIی است که hook را فعال کرد:

Matcherچه زمانی فعال می‌شود
initclaude --init-only یا claude -p --init
maintenanceclaude -p --maintenance

--init-only hookهای Setup و hookهای SessionStart را با matcherِ startup اجرا می‌کند، سپس بدونِ شروعِ یک گفت‌وگو خارج می‌شود. --init و --maintenance فقط وقتی با -p (حالتِ print) ترکیب شوند hookهای Setup را فعال می‌کنند؛ در یک نشستِ تعاملی این دو پرچم در حالِ حاضر hookهای Setup را فعال نمی‌کنند.

چون Setup در هر راه‌اندازی فعال نمی‌شود، پلاگینی که به نصبِ یک وابستگی نیاز دارد نمی‌تواند فقط به Setup تکیه کند. الگوی عملی این است که در اولین استفاده وابستگی را بررسی و در صورتِ نبود نصب کنی، برای مثال یک hook یا skill که ${CLAUDE_PLUGIN_DATA}/node_modules را آزمایش می‌کند و در صورتِ نبود npm install را اجرا می‌کند. برای این‌که وابستگی‌های نصب‌شده را کجا ذخیره کنی، دایرکتوریِ داده‌ی پایدار را ببین.

علاوه بر فیلدهای ورودیِ مشترک، hookهای Setup یک فیلدِ trigger تنظیم‌شده به یا "init" یا "maintenance" دریافت می‌کنند:

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Setup",
"trigger": "init"
}

hookهای Setup نمی‌توانند مسدود کنند. در کدِ خروجِ 2، stderr به کاربر نشان داده می‌شود؛ در هر کدِ خروجِ غیرصفرِ دیگری، stderr فقط وقتی با --verbose راه بیندازی ظاهر می‌شود. در هر دو حالت اجرا ادامه می‌یابد. برای پاس‌دادنِ اطلاعات به کانتکستِ Claude، additionalContext را در خروجیِ JSON برگردان؛ stdoutِ ساده فقط در لاگِ دیباگ نوشته می‌شود. علاوه بر فیلدهای خروجیِ JSONِ در دسترسِ همه‌ی hookها، می‌توانی این فیلدهای خاصِ رویداد را برگردانی:

فیلدتوضیح
additionalContextرشته‌ای که به کانتکستِ Claude اضافه می‌شود. مقادیرِ چند hook به هم متصل می‌شوند
{
"hookSpecificOutput": {
"hookEventName": "Setup",
"additionalContext": "Dependencies installed: node_modules, .venv"
}
}

hookهای Setup به CLAUDE_ENV_FILE دسترسی دارند. متغیرهای نوشته‌شده در آن فایل به فرمان‌های Bashِ بعدیِ نشست پایدار می‌مانند، درست مثلِ hookهای SessionStart. فقط hookهای type: "command" و type: "mcp_tool" پشتیبانی می‌شوند.

وقتی یک فایلِ CLAUDE.md یا .claude/rules/*.md در کانتکست بارگذاری می‌شود فعال می‌شود. این رویداد در آغازِ نشست برای فایل‌های eager-loaded و دوباره بعداً وقتی فایل‌ها به‌صورتِ lazy بارگذاری می‌شوند فعال می‌شود، برای مثال وقتی Claude به زیردایرکتوری‌ای دسترسی پیدا می‌کند که شاملِ یک CLAUDE.mdِ تودرتو است یا وقتی قواعدِ مشروط با frontmatterِ paths: مطابقت می‌کنند. hook از مسدودکردن یا کنترلِ تصمیم پشتیبانی نمی‌کند. برای اهدافِ مشاهده‌پذیری به‌صورتِ async اجرا می‌شود.

matcher روی load_reason اجرا می‌شود. برای مثال، از "matcher": "session_start" استفاده کن تا فقط برای فایل‌های بارگذاری‌شده در آغازِ نشست فعال شود، یا از "matcher": "path_glob_match|nested_traversal" تا فقط برای بارگذاری‌های lazy فعال شود.

علاوه بر فیلدهای ورودیِ مشترک، hookهای InstructionsLoaded این فیلدها را دریافت می‌کنند:

فیلدتوضیح
file_pathمسیرِ مطلق به فایلِ دستورالعملی که بارگذاری شد
memory_typeدامنه‌ی فایل: "User"، "Project"، "Local"، یا "Managed"
load_reasonچرا فایل بارگذاری شد: "session_start"، "nested_traversal"، "path_glob_match"، "include"، یا "compact". مقدارِ "compact" وقتی فعال می‌شود که فایل‌های دستورالعمل پس از یک رویدادِ فشرده‌سازی دوباره بارگذاری می‌شوند
globsالگوهای globِ مسیر از frontmatterِ paths:ِ فایل، در صورتِ وجود. فقط برای بارگذاری‌های path_glob_match حاضر است
trigger_file_pathمسیر به فایلی که دسترسی به آن این بارگذاری را فعال کرد، برای بارگذاری‌های lazy
parent_file_pathمسیر به فایلِ دستورالعملِ والدی که این یکی را include کرد، برای بارگذاری‌های include
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "InstructionsLoaded",
"file_path": "/Users/my-project/CLAUDE.md",
"memory_type": "Project",
"load_reason": "session_start"
}

کنترلِ تصمیمِ InstructionsLoaded

Section titled “کنترلِ تصمیمِ InstructionsLoaded”

hookهای InstructionsLoaded کنترلِ تصمیم ندارند. نمی‌توانند بارگذاریِ دستورالعمل را مسدود یا تغییر دهند. از این رویداد برای لاگِ ممیزی، ردیابیِ انطباق یا مشاهده‌پذیری استفاده کن.

وقتی کاربر یک پرامپت ثبت می‌کند، پیش از آن‌که Claude پردازشش کند، اجرا می‌شود. این به تو اجازه می‌دهد کانتکستِ اضافی بر اساسِ پرامپت/گفت‌وگو اضافه کنی، پرامپت‌ها را اعتبارسنجی کنی، یا انواعِ خاصی از پرامپت‌ها را مسدود کنی.

hookهای UserPromptSubmit برای نوع‌های command، http و mcp_tool یک timeoutِ پیش‌فرضِ ۳۰ ثانیه دارند، کوتاه‌تر از پیش‌فرضِ ۶۰۰ ثانیه‌ای برای آن نوع‌ها در بیشترِ رویدادهای دیگر. چون این hook پیش از هر پرامپت اجرا می‌شود و پردازشِ مدل را تا تکمیلش مسدود می‌کند، یک hookِ گیرکرده نشست را متوقف می‌کند. اگر hookت به زمانِ بیشتری نیاز دارد، فیلدِ timeout را در ورودیِ hook تنظیم کن.

علاوه بر فیلدهای ورودیِ مشترک، hookهای UserPromptSubmit فیلدِ prompt را که شاملِ متنِ ثبت‌شده توسطِ کاربر است دریافت می‌کنند.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "UserPromptSubmit",
"prompt": "Write a function to calculate the factorial of a number"
}

کنترلِ تصمیمِ UserPromptSubmit

Section titled “کنترلِ تصمیمِ UserPromptSubmit”

hookهای UserPromptSubmit می‌توانند کنترل کنند که یک پرامپتِ کاربر پردازش شود و کانتکست اضافه کنند. همه‌ی فیلدهای خروجیِ JSON در دسترس‌اند.

دو راه برای افزودنِ کانتکست به گفت‌وگو در کدِ خروجِ 0 وجود دارد:

  • stdoutِ متنِ ساده: هر متنِ غیر-JSONی که روی stdout نوشته شود به‌عنوانِ کانتکست اضافه می‌شود
  • JSON با additionalContext: از فرمتِ JSONِ زیر برای کنترلِ بیشتر استفاده کن. فیلدِ additionalContext به‌عنوانِ کانتکست اضافه می‌شود

stdoutِ ساده به‌عنوانِ خروجیِ hook در transcript نشان داده می‌شود. فیلدِ additionalContext با احتیاطِ بیشتری اضافه می‌شود.

برای مسدودکردنِ یک پرامپت، یک شیء JSON با decision تنظیم‌شده به "block" برگردان:

فیلدتوضیح
decision"block" از پردازشِ پرامپت جلوگیری می‌کند و آن را از کانتکست پاک می‌کند. برای اجازه‌ی ادامه‌ی پرامپت حذفش کن
reasonوقتی decision برابرِ "block" است به کاربر نشان داده می‌شود. به کانتکست اضافه نمی‌شود
additionalContextرشته‌ای که در کنارِ پرامپتِ ثبت‌شده به کانتکستِ Claude اضافه می‌شود. افزودنِ کانتکست برای Claude را ببین
sessionTitleعنوانِ نشست را تنظیم می‌کند. برای نام‌گذاریِ خودکارِ نشست‌ها بر اساسِ محتوای پرامپت استفاده کن
suppressOriginalPromptاگر true باشد وقتی decision برابرِ "block" است، متنِ پرامپتِ اصلی را از پیامِ مسدودسازیِ نشان‌داده‌شده به کاربر حذف می‌کند
{
"decision": "block",
"reason": "Explanation for decision",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "My additional context here",
"sessionTitle": "My session title"
}
}

وقتی یک slash command‌ی تایپ‌شده توسطِ کاربر پیش از رسیدن به Claude به یک پرامپت بسط پیدا می‌کند اجرا می‌شود. از این برای مسدودکردنِ فراخوانیِ مستقیمِ فرمان‌های مشخص، تزریقِ کانتکست برای یک skillِ خاص، یا لاگ‌کردنِ این‌که کاربران چه فرمان‌هایی را فراخوانی می‌کنند استفاده کن. برای مثال، یک hookِ مطابق با deploy می‌تواند /deploy را مسدود کند مگر آن‌که یک فایلِ تأیید حاضر باشد، یا یک hookِ مطابق با یک skillِ بازبینی می‌تواند چک‌لیستِ بازبینیِ تیم را به‌عنوانِ additionalContext اضافه کند.

این رویداد مسیری را پوشش می‌دهد که PreToolUse پوشش نمی‌دهد: یک hookِ PreToolUseِ مطابق با ابزارِ Skill فقط وقتی Claude ابزار را فراخوانی می‌کند فعال می‌شود، اما تایپ‌کردنِ مستقیمِ /skillname از PreToolUse عبور می‌کند. UserPromptExpansion روی آن مسیرِ مستقیم فعال می‌شود.

روی command_name مطابقت می‌کند. matcher را خالی بگذار تا روی هر slash command‌ی از نوعِ پرامپت فعال شود.

علاوه بر فیلدهای ورودیِ مشترک، hookهای UserPromptExpansion فیلدهای expansion_type، command_name، command_args، command_source و رشته‌ی promptِ اصلی را دریافت می‌کنند. فیلدِ expansion_type برای فرمان‌های skill و سفارشی slash_command است، یا mcp_prompt برای پرامپت‌های سرورِ MCP.

{
"session_id": "abc123",
"transcript_path": "/Users/.../00893aaf.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "UserPromptExpansion",
"expansion_type": "slash_command",
"command_name": "example-skill",
"command_args": "arg1 arg2",
"command_source": "plugin",
"prompt": "/example-skill arg1 arg2"
}

کنترلِ تصمیمِ UserPromptExpansion

Section titled “کنترلِ تصمیمِ UserPromptExpansion”

hookهای UserPromptExpansion می‌توانند بسط را مسدود کنند یا کانتکست اضافه کنند. همه‌ی فیلدهای خروجیِ JSON در دسترس‌اند.

فیلدتوضیح
decision"block" از بسطِ slash command جلوگیری می‌کند. برای اجازه‌ی ادامه حذفش کن
reasonوقتی decision برابرِ "block" است به کاربر نشان داده می‌شود
additionalContextرشته‌ای که در کنارِ پرامپتِ بسط‌یافته به کانتکستِ Claude اضافه می‌شود. افزودنِ کانتکست برای Claude را ببین
{
"decision": "block",
"reason": "This slash command is not available",
"hookSpecificOutput": {
"hookEventName": "UserPromptExpansion",
"additionalContext": "Additional context for this expansion"
}
}

حین جریان‌یافتنِ یک پیامِ دستیار روی صفحه اجرا می‌شود. Claude Code پیام را در گام‌هایی نمایش می‌دهد: هر بار که دسته‌ای از خطوطِ تازه‌تکمیل‌شده آماده‌ی رندر است، hook یک‌بار با آن خطوط اجرا می‌شود و Claude Code متنِ جایگزینِ hook را به‌جای آن‌ها رندر می‌کند. یک پیامِ طولانی چند فراخوانی تولید می‌کند؛ یک پیامِ کوتاه ممکن است فقط یکی تولید کند.

از MessageDisplay برای این‌ها استفاده کن:

  • حذفِ markdown برای یک نمایشِ کمینه
  • تبدیلِ متنی که یک اپلیکیشنِ Agent SDK به کاربرانش نشان می‌دهد
  • ویرایشِ کلیدهای API یا hostnameهای داخلی از پاسخ‌های Claude

Claude Code هر دسته را تا بازگشتِ hookت نگه می‌دارد، پس hook را سریع نگه دار. اگر hook شکست بخورد یا timeout شود، Claude Code متنِ اصلی را نمایش می‌دهد. timeoutِ پیش‌فرض برای این رویداد ۱۰ ثانیه است؛ اگر hookت به زمانِ بیشتری نیاز دارد، فیلدِ timeout را در ورودیِ hook تنظیم کن.

MessageDisplay فقط-نمایش است: متنِ جایگزین فقط آنچه را روی صفحه رندر می‌شود تغییر می‌دهد. transcript و آنچه Claude می‌بیند متنِ اصلی را نگه می‌دارند، پس Claude هرگز جایگزین را نمی‌بیند، و حالتِ verbose اصل را نشان می‌دهد. hook فقط متنِ پیامِ دستیار را دریافت می‌کند، پس نتایجِ ابزار و متنی که تو تایپ می‌کنی بدونِ تغییر رندر می‌شوند.

MessageDisplay از matcher پشتیبانی نمی‌کند و برای هر پیامِ دستیاری که متن جریان می‌دهد فعال می‌شود؛ پیام‌های بدونِ متن، مثلِ پاسخ‌های صرفاً فراخوانیِ ابزار، آن را فعال نمی‌کنند.

در اجراهای غیرتعاملی، از جمله کوئری‌های Agent SDK و claude -p، MessageDisplay به‌جای یک‌بار در هر دسته‌ی خطوط، یک‌بار در هر پیامِ دستیار اجرا می‌شود. این فراخوانیِ واحد پس از تکمیلِ پیام می‌رسد و متنِ کاملِ پیام را حمل می‌کند: index برابرِ 0 است، final برابرِ true است، و delta کلِ پیام را نگه می‌دارد. hookی که متنِ delta را برای هر پیام جمع می‌کند همان متنِ کلی را در هر دو حالت دریافت می‌کند.

علاوه بر فیلدهای ورودیِ مشترک، hookهای MessageDisplay شناسه‌های نوبت و پیام، موقعیتِ این فراخوانی درونِ پیام، و متنِ تازه را در delta دریافت می‌کنند. مرزهای دسته به این بستگی دارند که متن چطور جریان می‌یابد، پس از index و final برای ردیابیِ پیشرفت در یک پیام استفاده کن به‌جای انتظارِ این‌که خطوط به شیوه‌ی خاصی گروه‌بندی شوند.

فیلدتوضیح
turn_idUUIDِ نوبتِ جاری
message_idUUIDِ پیامِ دستیاری که نمایش داده می‌شود. در هر دسته‌ی همان پیام پایدار است. این شناسه‌ی API‌ای msg_… نیست، پس نمی‌تواند با شناسه‌های پیامِ transcript همبسته شود
indexاندیسِ صفرپایه‌ی این دسته درونِ پیام
finalروی آخرین دسته‌ی پیام true است. هر پیام دقیقاً یک دسته‌ی نهایی دارد
deltaخطوطِ تازه‌تکمیل‌شده از دسته‌ی قبلی، با newlineهای پایانی. همیشه خطوطِ کامل، به‌جز دسته‌ی نهایی که ممکن است وسطِ خط تمام شود. در اجراهای تعاملی، delta‌ی دسته‌ی نهایی وقتی پیام روی یک newline تمام می‌شود خالی است، پس final را، نه delta‌ی غیرخالی را، سیگنالِ پایانِ پیام بگیر. در اجراهای Agent SDK و claude -p، فراخوانیِ واحد کلِ پیام را حمل می‌کند
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "MessageDisplay",
"turn_id": "0c9e6a2f-7d41-4f4e-9a15-3f4f7c2b8d10",
"message_id": "5b2a9c8e-1f63-4d8a-b7c4-9e0d2a6f1c3b",
"index": 0,
"final": false,
"delta": "Here is the plan:\n"
}

علاوه بر فیلدهای خروجیِ JSONِ در دسترسِ همه‌ی hookها، hookهای MessageDisplay می‌توانند displayContent را برای جایگزینیِ delta روی صفحه برگردانند:

فیلدتوضیح
displayContentمتنی که به‌جای delta نمایش داده می‌شود. برای نمایشِ اصل حذفش کن

hookهای MessageDisplay کنترلِ تصمیم ندارند. نمی‌توانند پیام را مسدود کنند یا آنچه را در transcript ذخیره می‌شود یا به Claude فرستاده می‌شود تغییر دهند.

این مثال فرمت‌بندیِ markdown را از پاسخ‌های Claude برای یک نمایشِ متنِ ساده حذف می‌کند. اسکریپت هر دسته را از stdin می‌خواند، نشانگرهای bold و backtickهای کدِ خطی را از delta حذف می‌کند، و نتیجه را به‌عنوانِ displayContent برمی‌گرداند.

یک hookِ فرمان برای این رویداد در فایلِ تنظیماتت ثبت کن:

{
"hooks": {
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.sh",
"args": []
}
]
}
]
}
}

این اسکریپت را در .claude/hooks/plain-display.sh در پروژه‌ات ذخیره کن و با chmod +x اجراشدنی‌اش کن:

#!/bin/bash
jq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'

اسکریپت به jq روی PATHت نیاز دارد.

دسته‌های بدونِ markdown بدونِ تغییر عبور می‌کنند. اگر اسکریپت شکست بخورد، برای مثال چون jq نیست، Claude Code متنِ اصلی را نمایش می‌دهد و شکست را فقط در خروجیِ دیباگ یادداشت می‌کند، نه در نشست.

پس از این‌که Claude پارامترهای ابزار را می‌سازد و پیش از پردازشِ فراخوانیِ ابزار اجرا می‌شود. روی نامِ ابزار مطابقت می‌کند: Bash، Edit، Write، Read، Glob، Grep، Agent، WebFetch، WebSearch، AskUserQuestion، ExitPlanMode، و هر نامِ ابزارِ MCP.

از کنترلِ تصمیمِ PreToolUse برای مجاز، رد، ask یا defer کردنِ فراخوانیِ ابزار استفاده کن.

علاوه بر فیلدهای ورودیِ مشترک، hookهای PreToolUse فیلدهای tool_name، tool_input و tool_use_id را دریافت می‌کنند. فیلدهای tool_input به ابزار بستگی دارند:

فرمان‌های شِل را اجرا می‌کند.

فیلدنوعمثالتوضیح
commandstring"npm test"فرمانِ شِلی که اجرا می‌شود
descriptionstring"Run test suite"توضیحِ اختیاریِ کارِ فرمان
timeoutnumber120000timeoutِ اختیاری به میلی‌ثانیه
run_in_backgroundbooleanfalseاین‌که فرمان در پس‌زمینه اجرا شود یا نه

یک فایل می‌سازد یا بازنویسی می‌کند.

فیلدنوعمثالتوضیح
file_pathstring"/path/to/file.txt"مسیرِ مطلق به فایلی که نوشته می‌شود
contentstring"file content"محتوایی که در فایل نوشته می‌شود

یک رشته را در یک فایلِ موجود جایگزین می‌کند.

فیلدنوعمثالتوضیح
file_pathstring"/path/to/file.txt"مسیرِ مطلق به فایلی که ویرایش می‌شود
old_stringstring"original text"متنی که پیدا و جایگزین می‌شود
new_stringstring"replacement text"متنِ جایگزین
replace_allbooleanfalseاین‌که همه‌ی رخدادها جایگزین شوند یا نه

محتوای فایل را می‌خواند.

فیلدنوعمثالتوضیح
file_pathstring"/path/to/file.txt"مسیرِ مطلق به فایلی که خوانده می‌شود
offsetnumber10شماره‌خطِ اختیاری برای شروعِ خواندن
limitnumber50تعدادِ اختیاریِ خطوط برای خواندن

فایل‌های مطابق با یک الگوی glob را پیدا می‌کند.

فیلدنوعمثالتوضیح
patternstring"**/*.ts"الگوی glob برای مطابقتِ فایل‌ها
pathstring"/path/to/dir"دایرکتوریِ اختیاری برای جست‌وجو. پیش‌فرض به دایرکتوریِ کاریِ جاری

محتوای فایل را با عباراتِ منظم جست‌وجو می‌کند.

فیلدنوعمثالتوضیح
patternstring"TODO.*fix"الگوی عبارتِ منظم برای جست‌وجو
pathstring"/path/to/dir"فایل یا دایرکتوریِ اختیاری برای جست‌وجو
globstring"*.ts"الگوی globِ اختیاری برای فیلترکردنِ فایل‌ها
output_modestring"content""content"، "files_with_matches"، یا "count". پیش‌فرض به "files_with_matches"
-ibooleantrueجست‌وجوی بی‌حساسیت به حروف
multilinebooleanfalseفعال‌کردنِ مطابقتِ چندخطی

محتوای وب را واکشی و پردازش می‌کند.

فیلدنوعمثالتوضیح
urlstring"https://example.com/api"URLی که محتوا از آن واکشی می‌شود
promptstring"Extract the API endpoints"پرامپتی که روی محتوای واکشی‌شده اجرا می‌شود

وب را جست‌وجو می‌کند.

فیلدنوعمثالتوضیح
querystring"react hooks best practices"عبارتِ جست‌وجو
allowed_domainsarray["docs.example.com"]اختیاری: فقط نتایجِ این دامنه‌ها را شامل کن
blocked_domainsarray["spam.example.com"]اختیاری: نتایجِ این دامنه‌ها را کنار بگذار

یک ساب‌ایجنت را spawn می‌کند.

فیلدنوعمثالتوضیح
promptstring"Find all API endpoints"وظیفه‌ای که ایجنت انجام می‌دهد
descriptionstring"Find API endpoints"توضیحِ کوتاهِ وظیفه
subagent_typestring"Explore"نوعِ ایجنتِ تخصصیِ مورداستفاده
modelstring"sonnet"نام‌مستعارِ مدلِ اختیاری برای بازنویسیِ پیش‌فرض

در PostToolUse، tool_response برای یک فراخوانیِ تکمیل‌شده‌ی Agent متنِ نهاییِ ساب‌ایجنت را به‌همراهِ تله‌متریِ استفاده حمل می‌کند. این فیلدها را بخوان تا هزینه‌ی هر-ساب‌ایجنت را از یک hook ثبت کنی:

فیلدنوعمثالتوضیح
statusstring"completed""completed" برای فراخوانی‌های همگام، "async_launched" برای run_in_background: true
agentIdstring"a4d2c8f1e0b3a297"شناسه برای اجرای ساب‌ایجنت
contentarray[{"type": "text", "text": "Found 12 endpoints..."}]بلاک‌های متنِ نهاییِ ساب‌ایجنت
resolvedModelstring"claude-sonnet-4-5"مدلی که ساب‌ایجنت روی آن اجرا شد، که ممکن است با مدلِ درخواستی فرق کند. {/* min-version: 2.1.174 */}به Claude Code نسخه‌ی v2.1.174 یا بالاتر نیاز دارد
totalTokensnumber12450کلِ توکن‌های صورت‌حساب‌شده در نوبت‌های ساب‌ایجنت
totalDurationMsnumber48211مدتِ ساعتِ دیواریِ اجرای ساب‌ایجنت
totalToolUseCountnumber7شمارشِ فراخوانی‌های ابزاری که ساب‌ایجنت انجام داد
usageobject{"input_tokens": 8320, ...}تفکیکِ توکن به‌ازای نوع: input_tokens، output_tokens، cache_creation_input_tokens، cache_read_input_tokens

برای فراخوانی‌های run_in_background: true، ابزار بلافاصله پس از راه‌اندازیِ ساب‌ایجنت بازمی‌گردد، پس tool_response هیچ فیلدِ usage حمل نمی‌کند. status: "async_launched"، agentId، description، prompt، outputFile و resolvedModel دارد.

فیلدِ resolvedModel مدلی را نام می‌برد که ساب‌ایجنت واقعاً روی آن اجرا می‌شود، که می‌تواند با مقدارِ model در tool_input فرق کند. به Claude Code نسخه‌ی v2.1.174 یا بالاتر نیاز دارد.

یک تا چهار سؤالِ چندگزینه‌ای از کاربر می‌پرسد.

فیلدنوعمثالتوضیح
questionsarray[{"question": "Which framework?", "header": "Framework", "options": [{"label": "React"}], "multiSelect": false}]سؤال‌هایی که ارائه می‌شوند، هرکدام با یک رشته‌ی question، headerِ کوتاه، آرایه‌ی options و پرچمِ اختیاریِ multiSelect
answersobject{"Which framework?": "React"}اختیاری. متنِ سؤال را به برچسبِ گزینه‌ی انتخاب‌شده نگاشت می‌کند. پاسخ‌های چندانتخابی برچسب‌ها را با کاما به هم می‌پیوندند. Claude این فیلد را تنظیم نمی‌کند؛ آن را از طریقِ updatedInput فراهم کن تا برنامه‌ای پاسخ دهی

یک طرح ارائه می‌دهد و از کاربر می‌خواهد پیش از آن‌که Claude از حالتِ plan خارج شود آن را تأیید کند. Claude پیش از فراخوانیِ ابزار طرح را در فایلی روی دیسک می‌نویسد، پس tool_inputِ تحت‌اللفظی از مدل فقط allowedPrompts را حمل می‌کند. Claude Code محتوای طرح و مسیرِ فایل را پیش از پاس‌دادنِ ورودی به hookها تزریق می‌کند.

فیلدنوعمثالتوضیح
planstring"## Refactor auth\n1. Extract..."محتوای طرح در Markdown. از فایلِ طرح روی دیسک تزریق می‌شود
planFilePathstring"/Users/.../plans/refactor-auth.md"مسیر به فایلِ طرح. تزریق‌شده
allowedPromptsarray[{"tool": "Bash", "prompt": "run tests"}]اختیاری. مجوزهای مبتنی بر پرامپتی که Claude برای پیاده‌سازیِ طرح درخواست می‌کند، هرکدام با یک نامِ tool و یک prompt که دسته‌ی اکشن را توصیف می‌کند

در PostToolUse، tool_response شیئی با فیلدهای plan و filePath است که طرحِ تأییدشده را نگه می‌دارند، به‌علاوه‌ی پرچم‌های وضعیتِ داخلی. برای محتوای طرح به‌جای خواندنِ دوباره‌ی فایل از دیسک tool_response.plan را بخوان.

hookهای PreToolUse می‌توانند کنترل کنند که یک فراخوانیِ ابزار ادامه یابد. برخلافِ hookهای دیگری که از فیلدِ decisionِ سطحِ بالا استفاده می‌کنند، PreToolUse تصمیمش را داخلِ یک شیء hookSpecificOutput برمی‌گرداند. این به آن کنترلِ غنی‌تری می‌دهد: چهار نتیجه (allow، deny، ask یا defer) به‌علاوه‌ی توانایی تغییرِ ورودیِ ابزار پیش از اجرا.

فیلدتوضیح
permissionDecision"allow" از پرامپتِ مجوز می‌گذرد. "deny" از فراخوانیِ ابزار جلوگیری می‌کند. "ask" کاربر را به تأیید فرامی‌خواند. "defer" به‌نرمی خارج می‌شود تا ابزار بعداً از سر گرفته شود. قواعدِ deny و ask صرفِ‌نظر از آنچه hook برمی‌گرداند همچنان ارزیابی می‌شوند
permissionDecisionReasonبرای "allow" و "ask"، به کاربر نشان داده می‌شود نه به Claude. برای "deny"، به Claude نشان داده می‌شود. برای "defer"، نادیده گرفته می‌شود
updatedInputپارامترهای ورودیِ ابزار را پیش از اجرا تغییر می‌دهد. کلِ شیء ورودی را جایگزین می‌کند، پس فیلدهای بدون‌تغییر را در کنارِ فیلدهای تغییریافته بگنجان. با "allow" ترکیب کن تا خودکار تأیید شود، یا با "ask" تا ورودیِ تغییریافته را به کاربر نشان دهی. برای "defer"، نادیده گرفته می‌شود
additionalContextرشته‌ای که در کنارِ نتیجه‌ی ابزار به کانتکستِ Claude اضافه می‌شود. وقتی permissionDecision برابرِ "defer" است نادیده گرفته می‌شود. افزودنِ کانتکست برای Claude را ببین

وقتی چند hookِ PreToolUse تصمیماتِ متفاوتی برمی‌گردانند، اولویت deny > defer > ask > allow است.

وقتی یک hook‏ "ask" برمی‌گرداند، پرامپتِ مجوزِ نشان‌داده‌شده به کاربر شاملِ برچسبی است که مشخص می‌کند hook از کجا آمده: برای مثال، [User]، [Project]، [Plugin] یا [Local]. این به کاربران کمک می‌کند بفهمند کدام منبعِ پیکربندی درخواستِ تأیید می‌کند.

{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "My reason here",
"updatedInput": {
"field_to_modify": "new value"
},
"additionalContext": "Current environment: production. Proceed with caution."
}
}

AskUserQuestion و ExitPlanMode به تعاملِ کاربر نیاز دارند و معمولاً در حالتِ غیرتعاملی با پرچمِ -p مسدود می‌شوند. برگرداندنِ permissionDecision: "allow" به‌همراهِ updatedInput آن نیاز را برآورده می‌کند: hook ورودیِ ابزار را از stdin می‌خواند، پاسخ را از طریقِ رابطِ خودت جمع می‌کند، و آن را در updatedInput برمی‌گرداند تا ابزار بدونِ پرامپت اجرا شود. برگرداندنِ تنهای "allow" برای این ابزارها کافی نیست. برای AskUserQuestion، آرایه‌ی questionsِ اصلی را بازتاب بده و یک شیء answers اضافه کن که متنِ هر سؤال را به پاسخِ انتخاب‌شده نگاشت می‌کند.

deferکردنِ یک فراخوانیِ ابزار برای بعد

Section titled “deferکردنِ یک فراخوانیِ ابزار برای بعد”

"defer" برای ادغام‌هایی است که claude -p را به‌عنوانِ یک زیرپروسه اجرا می‌کنند و خروجیِ JSONش را می‌خوانند، مثلِ یک اپلیکیشنِ Agent SDK یا یک رابطِ سفارشی ساخته‌شده روی Claude Code. به آن پروسه‌ی فراخواننده اجازه می‌دهد Claude را در یک فراخوانیِ ابزار متوقف کند، ورودی را از طریقِ رابطِ خودش جمع کند، و از همان‌جا که رها کرده ادامه دهد. Claude Code این مقدار را فقط در حالتِ غیرتعاملی با پرچمِ -p رعایت می‌کند. در نشست‌های تعاملی یک هشدار لاگ می‌کند و نتیجه‌ی hook را نادیده می‌گیرد.

ابزارِ AskUserQuestion موردِ معمول است: Claude می‌خواهد چیزی از کاربر بپرسد، اما ترمینالی برای پاسخ‌دادن نیست. رفت‌وبرگشت این‌طور کار می‌کند:

  1. Claude‏ AskUserQuestion را فراخوانی می‌کند. hookِ PreToolUse فعال می‌شود.
  2. hook‏ permissionDecision: "defer" برمی‌گرداند. ابزار اجرا نمی‌شود. پروسه با stop_reason: "tool_deferred" خارج می‌شود و فراخوانیِ ابزارِ معلق در transcript حفظ می‌شود.
  3. پروسه‌ی فراخواننده deferred_tool_use را از نتیجه‌ی SDK می‌خواند، سؤال را در رابطِ خودش آشکار می‌کند، و منتظرِ یک پاسخ می‌ماند.
  4. پروسه‌ی فراخواننده claude -p --resume <session-id> را اجرا می‌کند. همان فراخوانیِ ابزار دوباره PreToolUse را فعال می‌کند.
  5. hook‏ permissionDecision: "allow" را با پاسخ در updatedInput برمی‌گرداند. ابزار اجرا می‌شود و Claude ادامه می‌دهد.

فیلدِ deferred_tool_useid، name و inputِ ابزار را حمل می‌کند. input پارامترهایی است که Claude برای فراخوانیِ ابزار تولید کرد، که پیش از اجرا ضبط شده:

{
"type": "result",
"subtype": "success",
"stop_reason": "tool_deferred",
"session_id": "abc123",
"deferred_tool_use": {
"id": "toolu_01abc",
"name": "AskUserQuestion",
"input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React"}, {"label": "Vue"}], "multiSelect": false }] }
}
}

هیچ timeout یا محدودیتِ retryای نیست. نشست تا وقتی از سرش بگیری روی دیسک می‌ماند، تابعِ پاک‌سازیِ نگه‌داریِ cleanupPeriodDays که به‌صورتِ پیش‌فرض فایل‌های نشست را پس از ۳۰ روز حذف می‌کند. اگر هنگامِ از سرگیری پاسخ آماده نباشد، hook می‌تواند دوباره "defer" برگرداند و پروسه به همان شیوه خارج می‌شود. پروسه‌ی فراخواننده کنترل می‌کند که چه زمانی حلقه را با برگرداندنِ نهاییِ "allow" یا "deny" از hook بشکند.

"defer" فقط وقتی کار می‌کند که Claude یک فراخوانیِ ابزارِ واحد در نوبت انجام دهد. اگر Claude چند فراخوانیِ ابزار را یک‌جا انجام دهد، "defer" با یک هشدار نادیده گرفته می‌شود و ابزار از مسیرِ عادیِ جریانِ مجوز پیش می‌رود. این محدودیت وجود دارد چون از سرگیری فقط می‌تواند یک ابزار را دوباره اجرا کند: راهی برای deferکردنِ یک فراخوانی از یک دسته بدونِ رهاکردنِ بقیه به‌صورتِ حل‌نشده نیست.

اگر ابزارِ deferشده هنگامِ از سرگیری دیگر در دسترس نباشد، پروسه پیش از فعال‌شدنِ hook با stop_reason: "tool_deferred_unavailable" و is_error: true خارج می‌شود. این وقتی اتفاق می‌افتد که یک سرورِ MCP که آن ابزار را فراهم می‌کرد برای نشستِ از سر گرفته‌شده متصل نباشد. بارِ deferred_tool_use همچنان گنجانده می‌شود تا بتوانی شناسایی کنی کدام ابزار گم شده.

وقتی یک دیالوگِ مجوز به کاربر نشان داده می‌شود اجرا می‌شود. از کنترلِ تصمیمِ PermissionRequest برای مجاز یا ردکردن از طرفِ کاربر استفاده کن.

روی نامِ ابزار مطابقت می‌کند، همان مقادیرِ PreToolUse.

hookهای PermissionRequest فیلدهای tool_name و tool_input را مثلِ hookهای PreToolUse دریافت می‌کنند، اما بدونِ tool_use_id. یک آرایه‌ی اختیاریِ permission_suggestions شاملِ گزینه‌های «همیشه مجاز»ی است که کاربر معمولاً در دیالوگِ مجوز می‌بیند. تفاوت در زمانِ فعال‌شدنِ hook است: hookهای PermissionRequest وقتی اجرا می‌شوند که یک دیالوگِ مجوز قرار است به کاربر نشان داده شود، در حالی که hookهای PreToolUse پیش از اجرای ابزار صرفِ‌نظر از وضعیتِ مجوز اجرا می‌شوند.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PermissionRequest",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf node_modules",
"description": "Remove node_modules directory"
},
"permission_suggestions": [
{
"type": "addRules",
"rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
"behavior": "allow",
"destination": "localSettings"
}
]
}

کنترلِ تصمیمِ PermissionRequest

Section titled “کنترلِ تصمیمِ PermissionRequest”

hookهای PermissionRequest می‌توانند درخواست‌های مجوز را مجاز یا رد کنند. علاوه بر فیلدهای خروجیِ JSONِ در دسترسِ همه‌ی hookها، اسکریپتِ hookت می‌تواند یک شیء decision با این فیلدهای خاصِ رویداد برگرداند:

فیلدتوضیح
behavior"allow" مجوز را اعطا می‌کند، "deny" ردش می‌کند. قواعدِ deny و ask همچنان ارزیابی می‌شوند، پس hookی که "allow" برمی‌گرداند یک قاعده‌ی denyِ مطابق را بازنویسی نمی‌کند
updatedInputفقط برای "allow": پارامترهای ورودیِ ابزار را پیش از اجرا تغییر می‌دهد. کلِ شیء ورودی را جایگزین می‌کند، پس فیلدهای بدون‌تغییر را در کنارِ فیلدهای تغییریافته بگنجان. ورودیِ تغییریافته دوباره در برابرِ قواعدِ deny و ask ارزیابی می‌شود
updatedPermissionsفقط برای "allow": آرایه‌ای از ورودی‌های به‌روزرسانیِ مجوز برای اعمال، مثلِ افزودنِ یک قاعده‌ی allow یا تغییرِ حالتِ مجوزِ نشست
messageفقط برای "deny": به Claude می‌گوید چرا مجوز رد شد
interruptفقط برای "deny": اگر true باشد، Claude را متوقف می‌کند
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": {
"command": "npm run lint"
}
}
}
}

ورودی‌های به‌روزرسانیِ مجوز

Section titled “ورودی‌های به‌روزرسانیِ مجوز”

فیلدِ خروجیِ updatedPermissions و فیلدِ ورودیِ permission_suggestions هر دو از همان آرایه‌ی شیءهای ورودی استفاده می‌کنند. هر ورودی یک type دارد که فیلدهای دیگرش را تعیین می‌کند، و یک destination که کنترل می‌کند تغییر کجا نوشته شود.

typeفیلدهااثر
addRulesrules، behavior، destinationقواعدِ مجوز اضافه می‌کند. rules آرایه‌ای از شیءهای {toolName, ruleContent?} است. برای مطابقت با کلِ ابزار، ruleContent را حذف کن. behavior یا "allow"، "deny" یا "ask" است
replaceRulesrules، behavior، destinationهمه‌ی قواعدِ behaviorِ داده‌شده را در destination با rulesِ ارائه‌شده جایگزین می‌کند
removeRulesrules، behavior، destinationقواعدِ مطابقِ behaviorِ داده‌شده را حذف می‌کند
setModemode، destinationحالتِ مجوز را تغییر می‌دهد. حالت‌های معتبر default، auto، acceptEdits، dontAsk، bypassPermissions و plan هستند
addDirectoriesdirectories، destinationدایرکتوری‌های کاری اضافه می‌کند. directories آرایه‌ای از رشته‌های مسیر است
removeDirectoriesdirectories، destinationدایرکتوری‌های کاری را حذف می‌کند

فیلدِ destination روی هر ورودی تعیین می‌کند که تغییر در حافظه بماند یا در یک فایلِ تنظیمات پایدار شود.

destinationمی‌نویسد در
sessionفقط در حافظه، وقتی نشست تمام می‌شود دور ریخته می‌شود
localSettings.claude/settings.local.json
projectSettings.claude/settings.json
userSettings~/.claude/settings.json

یک hook می‌تواند یکی از permission_suggestionsهایی را که دریافت کرده به‌عنوانِ خروجیِ updatedPermissionsِ خودش بازتاب دهد، که معادلِ انتخابِ آن گزینه‌ی «همیشه مجاز» توسطِ کاربر در دیالوگ است.

بلافاصله پس از موفقیتِ یک ابزار اجرا می‌شود.

روی نامِ ابزار مطابقت می‌کند، همان مقادیرِ PreToolUse.

hookهای PostToolUse پس از این‌که یک ابزار از پیش با موفقیت اجرا شده فعال می‌شوند. ورودی هم tool_input، آرگومان‌های فرستاده‌شده به ابزار، و هم tool_response، نتیجه‌ای که برگرداند، را شامل می‌شود. اسکیمای دقیقِ هر دو به ابزار بستگی دارد.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.txt",
"content": "file content"
},
"tool_response": {
"filePath": "/path/to/file.txt",
"success": true
},
"tool_use_id": "toolu_01ABC123...",
"duration_ms": 12
}
فیلدتوضیح
duration_msاختیاری. زمانِ اجرای ابزار به میلی‌ثانیه. زمانِ صرف‌شده در پرامپت‌های مجوز و hookهای PreToolUse را شامل نمی‌شود

hookهای PostToolUse می‌توانند پس از اجرای ابزار به Claude بازخورد بدهند. علاوه بر فیلدهای خروجیِ JSONِ در دسترسِ همه‌ی hookها، اسکریپتِ hookت می‌تواند این فیلدهای خاصِ رویداد را برگرداند:

فیلدتوضیح
decision"block"reason را کنارِ نتیجه‌ی ابزار اضافه می‌کند. Claude همچنان خروجیِ اصلی را می‌بیند؛ برای جایگزینی‌اش از updatedToolOutput استفاده کن
reasonتوضیحی که وقتی decision برابرِ "block" است به Claude نشان داده می‌شود
additionalContextرشته‌ای که در کنارِ نتیجه‌ی ابزار به کانتکستِ Claude اضافه می‌شود. افزودنِ کانتکست برای Claude را ببین
updatedToolOutputخروجیِ ابزار را پیش از فرستادن به Claude با مقدارِ ارائه‌شده جایگزین می‌کند. مقدار باید با شکلِ خروجیِ ابزار مطابقت کند
updatedMCPToolOutputخروجی را فقط برای ابزارهای MCP جایگزین می‌کند. updatedToolOutput را که برای همه‌ی ابزارها کار می‌کند ترجیح بده

مثالِ زیر خروجیِ یک فراخوانیِ Bash را جایگزین می‌کند. مقدارِ جایگزین با شکلِ خروجیِ ابزارِ Bash مطابقت دارد:

{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Additional information for Claude",
"updatedToolOutput": {
"stdout": "[redacted]",
"stderr": "",
"interrupted": false,
"isImage": false
}
}
}

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

روی نامِ ابزار مطابقت می‌کند، همان مقادیرِ PreToolUse.

hookهای PostToolUseFailure همان فیلدهای tool_name و tool_inputِ PostToolUse را، به‌همراهِ اطلاعاتِ خطا به‌عنوانِ فیلدهای سطحِ بالا، دریافت می‌کنند:

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolUseFailure",
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Run test suite"
},
"tool_use_id": "toolu_01ABC123...",
"error": "Command exited with non-zero status code 1",
"is_interrupt": false,
"duration_ms": 4187
}
فیلدتوضیح
errorرشته‌ای که توصیف می‌کند چه چیزی اشتباه شد
is_interruptبولینِ اختیاری که نشان می‌دهد آیا شکست به‌خاطرِ وقفه‌ی کاربر بود
duration_msاختیاری. زمانِ اجرای ابزار به میلی‌ثانیه. زمانِ صرف‌شده در پرامپت‌های مجوز و hookهای PreToolUse را شامل نمی‌شود

کنترلِ تصمیمِ PostToolUseFailure

Section titled “کنترلِ تصمیمِ PostToolUseFailure”

hookهای PostToolUseFailure می‌توانند پس از شکستِ یک ابزار به Claude کانتکست بدهند. علاوه بر فیلدهای خروجیِ JSONِ در دسترسِ همه‌ی hookها، اسکریپتِ hookت می‌تواند این فیلدهای خاصِ رویداد را برگرداند:

فیلدتوضیح
additionalContextرشته‌ای که در کنارِ خطا به کانتکستِ Claude اضافه می‌شود. افزودنِ کانتکست برای Claude را ببین
{
"hookSpecificOutput": {
"hookEventName": "PostToolUseFailure",
"additionalContext": "Additional information about the failure for Claude"
}
}

یک‌بار پس از resolve‌شدنِ هر فراخوانیِ ابزار در یک دسته، پیش از آن‌که Claude Code درخواستِ بعدی را به مدل بفرستد، اجرا می‌شود. PostToolUse یک‌بار در هر ابزار فعال می‌شود، یعنی وقتی Claude فراخوانی‌های موازیِ ابزار انجام می‌دهد هم‌زمان فعال می‌شود. PostToolBatch دقیقاً یک‌بار با کلِ دسته فعال می‌شود، پس جای درستی برای تزریقِ کانتکستی است که به مجموعه‌ی ابزارهای اجراشده بستگی دارد نه به هیچ ابزارِ منفردی. برای این رویداد matcheری نیست.

علاوه بر فیلدهای ورودیِ مشترک، hookهای PostToolBatch‏ tool_calls را دریافت می‌کنند، آرایه‌ای که هر فراخوانیِ ابزار در دسته را توصیف می‌کند:

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolBatch",
"tool_calls": [
{
"tool_name": "Read",
"tool_input": {"file_path": "/.../ledger/accounts.py"},
"tool_use_id": "toolu_01...",
"tool_response": " 1\tfrom __future__ import annotations\n 2\t..."
},
{
"tool_name": "Read",
"tool_input": {"file_path": "/.../ledger/transactions.py"},
"tool_use_id": "toolu_02...",
"tool_response": " 1\tfrom __future__ import annotations\n 2\t..."
}
]
}

tool_response همان محتوایی را شامل می‌شود که مدل در بلاکِ tool_resultِ متناظر دریافت می‌کند. مقدار یک رشته‌ی سریالایزشده یا آرایه‌ی بلاکِ محتوا است، دقیقاً همان‌طور که ابزار منتشرش کرد. برای Read، یعنی متنِ پیشوندشده با شماره‌خط نه محتوای خامِ فایل. پاسخ‌ها می‌توانند بزرگ باشند، پس فقط فیلدهایی را که نیاز داری پارس کن.

hookهای PostToolBatch می‌توانند برای Claude کانتکست تزریق کنند. علاوه بر فیلدهای خروجیِ JSONِ در دسترسِ همه‌ی hookها، اسکریپتِ hookت می‌تواند این فیلدهای خاصِ رویداد را برگرداند:

فیلدتوضیح
additionalContextرشته‌ی کانتکستی که یک‌بار پیش از فراخوانیِ بعدیِ مدل تزریق می‌شود. برای جزئیاتِ تحویل، آنچه در آن بگذاری، و این‌که نشست‌های از سر گرفته‌شده چطور مقادیرِ گذشته را مدیریت می‌کنند افزودنِ کانتکست برای Claude را ببین
{
"hookSpecificOutput": {
"hookEventName": "PostToolBatch",
"additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
}
}

برگرداندنِ decision: "block" یا continue: false حلقه‌ی ایجنتیک را پیش از فراخوانیِ بعدیِ مدل متوقف می‌کند.

وقتی طبقه‌بندِ حالتِ auto یک فراخوانیِ ابزار را رد می‌کند اجرا می‌شود. این hook فقط در حالتِ auto فعال می‌شود: وقتی یک دیالوگِ مجوز را دستی رد می‌کنی، وقتی یک hookِ PreToolUse یک فراخوانی را مسدود می‌کند، یا وقتی یک قاعده‌ی deny مطابقت می‌کند اجرا نمی‌شود. از آن برای لاگ‌کردنِ ردهای طبقه‌بند، تنظیمِ پیکربندی، یا گفتن به مدل که می‌تواند فراخوانیِ ابزار را دوباره امتحان کند استفاده کن.

روی نامِ ابزار مطابقت می‌کند، همان مقادیرِ PreToolUse.

علاوه بر فیلدهای ورودیِ مشترک، hookهای PermissionDenied فیلدهای tool_name، tool_input، tool_use_id و reason را دریافت می‌کنند.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "auto",
"hook_event_name": "PermissionDenied",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /tmp/build",
"description": "Clean build directory"
},
"tool_use_id": "toolu_01ABC123...",
"reason": "Auto mode denied: command targets a path outside the project"
}
فیلدتوضیح
reasonتوضیحِ طبقه‌بند برای این‌که چرا فراخوانیِ ابزار رد شد

کنترلِ تصمیمِ PermissionDenied

Section titled “کنترلِ تصمیمِ PermissionDenied”

hookهای PermissionDenied می‌توانند به مدل بگویند که می‌تواند فراخوانیِ ردشده‌ی ابزار را دوباره امتحان کند. یک شیء JSON با hookSpecificOutput.retry تنظیم‌شده به true برگردان:

{
"hookSpecificOutput": {
"hookEventName": "PermissionDenied",
"retry": true
}
}

وقتی retry برابرِ true است، Claude Code یک پیام به گفت‌وگو اضافه می‌کند که به مدل می‌گوید می‌تواند فراخوانیِ ابزار را دوباره امتحان کند. خودِ رد برگردانده نمی‌شود. اگر hookت JSON برنگرداند، یا retry: false برگرداند، رد پابرجا می‌ماند و مدل پیامِ ردِ اصلی را دریافت می‌کند.

وقتی Claude Code اعلان می‌فرستد اجرا می‌شود. روی نوعِ اعلان مطابقت می‌کند: permission_prompt، idle_prompt، auth_success، elicitation_dialog، elicitation_complete، elicitation_response. matcher را حذف کن تا hookها برای همه‌ی انواعِ اعلان اجرا شوند.

از matcherهای جداگانه استفاده کن تا بسته به نوعِ اعلان handlerهای متفاوتی اجرا شوند. این پیکربندی وقتی Claude به تأییدِ مجوز نیاز دارد یک اسکریپتِ هشدارِ خاصِ مجوز و وقتی Claude idle بوده یک اعلانِ متفاوت فعال می‌کند:

{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "/path/to/permission-alert.sh"
}
]
},
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "/path/to/idle-notification.sh"
}
]
}
]
}
}

علاوه بر فیلدهای ورودیِ مشترک، hookهای Notification‏ message را با متنِ اعلان، یک titleِ اختیاری، و notification_type را که نشان می‌دهد کدام نوع فعال شده دریافت می‌کنند.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Notification",
"message": "Claude needs your permission",
"title": "Permission needed",
"notification_type": "permission_prompt"
}

hookهای Notification نمی‌توانند اعلان‌ها را مسدود یا تغییر دهند. برای اثرهای جانبی مثلِ ارجاعِ اعلان به یک سرویسِ بیرونی در نظر گرفته شده‌اند. فیلدهای خروجیِ JSONِ مشترک مثلِ systemMessage اعمال می‌شوند.

وقتی یک ساب‌ایجنتِ Claude Code از طریقِ ابزارِ Agent‏ spawn می‌شود اجرا می‌شود. از matcherها برای فیلترکردن بر اساسِ نامِ نوعِ ایجنت پشتیبانی می‌کند. برای ایجنت‌های داخلی، این نامِ ایجنت است مثلِ general-purpose، Explore یا Plan. برای ساب‌ایجنت‌های سفارشی، این فیلدِ name از frontmatterِ ایجنت است، نه نام‌فایل.

علاوه بر فیلدهای ورودیِ مشترک، hookهای SubagentStart‏ agent_id را با شناسه‌ی یکتای ساب‌ایجنت و agent_type را با نامِ ایجنت (ایجنت‌های داخلی مثلِ "general-purpose"، "Explore"، "Plan"، یا نام‌های ایجنتِ سفارشی) دریافت می‌کنند.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SubagentStart",
"agent_id": "agent-abc123",
"agent_type": "Explore"
}

hookهای SubagentStart نمی‌توانند ساختِ ساب‌ایجنت را مسدود کنند، اما می‌توانند به ساب‌ایجنت کانتکست تزریق کنند. علاوه بر فیلدهای خروجیِ JSONِ در دسترسِ همه‌ی hookها، می‌توانی این را برگردانی:

فیلدتوضیح
additionalContextرشته‌ای که در آغازِ گفت‌وگوی ساب‌ایجنت، پیش از اولین پرامپتش، به کانتکستِ آن اضافه می‌شود. افزودنِ کانتکست برای Claude را ببین
{
"hookSpecificOutput": {
"hookEventName": "SubagentStart",
"additionalContext": "Follow security guidelines for this task"
}
}

وقتی یک ساب‌ایجنتِ Claude Code پاسخ‌دادن را تمام کرده اجرا می‌شود. روی نوعِ ایجنت مطابقت می‌کند، همان مقادیرِ SubagentStart.

علاوه بر فیلدهای ورودیِ مشترک، hookهای SubagentStop‏ stop_hook_active، agent_id، agent_type، agent_transcript_path و last_assistant_message را دریافت می‌کنند. فیلدِ agent_type مقداری است که برای فیلترِ matcher استفاده می‌شود. transcript_path transcriptِ نشستِ اصلی است، در حالی که agent_transcript_path transcriptِ خودِ ساب‌ایجنت است که در یک پوشه‌ی تودرتوی subagents/ ذخیره شده. فیلدِ last_assistant_message محتوای متنیِ پاسخِ نهاییِ ساب‌ایجنت را شامل می‌شود، پس hookها می‌توانند بدونِ پارسِ فایلِ transcript به آن دسترسی پیدا کنند.

hookهای SubagentStop آرایه‌های background_tasks و session_cronsِ توصیف‌شده زیرِ ورودیِ Stop را نیز دریافت می‌کنند، که در Claude Code نسخه‌ی v2.1.145 یا بالاتر در دسترس‌اند. هر دو آرایه به نشستِ والد محدودند، نه ساب‌ایجنت.

{
"session_id": "abc123",
"transcript_path": "~/.claude/projects/.../abc123.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "SubagentStop",
"stop_hook_active": false,
"agent_id": "def456",
"agent_type": "Explore",
"agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
"last_assistant_message": "Analysis complete. Found 3 potential issues...",
"background_tasks": [],
"session_crons": []
}

hookهای SubagentStop از همان فرمتِ کنترلِ تصمیمِ hookهای Stop استفاده می‌کنند، از جمله hookSpecificOutput.additionalContext با hookEventName تنظیم‌شده به "SubagentStop"، برای بازخوردِ غیرخطایی که ساب‌ایجنت را در حالِ اجرا نگه می‌دارد. برگرداندنِ decision: "block" با یک reason ساب‌ایجنت را در حالِ اجرا نگه می‌دارد و reason را به‌عنوانِ دستورالعملِ بعدی‌اش به ساب‌ایجنت تحویل می‌دهد. برای تزریقِ کانتکست به نشستِ والد پس از بازگشتِ یک ساب‌ایجنت، به‌جایش از یک hookِ PostToolUse روی ابزارِ Agent استفاده کن.

وقتی یک task از طریقِ ابزارِ TaskCreate در حالِ ساخته‌شدن است اجرا می‌شود. از این برای اعمالِ قراردادهای نام‌گذاری، الزامِ توضیحاتِ task، یا جلوگیری از ساخته‌شدنِ taskهای خاص استفاده کن.

وقتی یک hookِ TaskCreated با کدِ 2 خارج می‌شود، task ساخته نمی‌شود و پیامِ stderr به‌عنوانِ بازخورد به مدل بازخورانده می‌شود. برای متوقف‌کردنِ کاملِ هم‌تیمی به‌جای اجرای دوباره‌اش، JSON با {"continue": false, "stopReason": "..."} برگردان. hookهای TaskCreated از matcher پشتیبانی نمی‌کنند و در هر رخداد فعال می‌شوند.

علاوه بر فیلدهای ورودیِ مشترک، hookهای TaskCreated‏ task_id، task_subject و اختیاراً task_description، teammate_name و team_name را دریافت می‌کنند.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "TaskCreated",
"task_id": "task-001",
"task_subject": "Implement user authentication",
"task_description": "Add login and signup endpoints",
"teammate_name": "implementer",
"team_name": "my-project"
}
فیلدتوضیح
task_idشناسه‌ی taskی که ساخته می‌شود
task_subjectعنوانِ task
task_descriptionتوضیحِ مفصلِ task. ممکن است غایب باشد
teammate_nameنامِ هم‌تیمی‌ای که task را می‌سازد. ممکن است غایب باشد
team_nameنامِ تیم. ممکن است غایب باشد

hookهای TaskCreated دو راه برای کنترلِ ساختِ task پشتیبانی می‌کنند:

  • کدِ خروجِ 2: task ساخته نمی‌شود و پیامِ stderr به‌عنوانِ بازخورد به مدل بازخورانده می‌شود.
  • JSONِ {"continue": false, "stopReason": "..."}: هم‌تیمی را کاملاً متوقف می‌کند، مطابق با رفتارِ hookِ Stop. stopReason به کاربر نشان داده می‌شود.

این مثال taskهایی را که عنوان‌شان از فرمتِ موردنیاز پیروی نمی‌کند مسدود می‌کند:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
exit 2
fi
exit 0

وقتی یک task در حالِ علامت‌خوردن به‌عنوانِ تکمیل‌شده است اجرا می‌شود. این در دو موقعیت فعال می‌شود: وقتی هر ایجنتی صریحاً یک task را از طریقِ ابزارِ TaskUpdate به‌عنوانِ تکمیل‌شده علامت می‌زند، یا وقتی یک هم‌تیمیِ تیمِ ایجنت نوبتش را با taskهای در حالِ انجام تمام می‌کند. از این برای اعمالِ معیارهای تکمیل مثلِ گذرکردنِ تست‌ها یا بررسی‌های lint پیش از بسته‌شدنِ یک task استفاده کن.

وقتی یک hookِ TaskCompleted با کدِ 2 خارج می‌شود، task به‌عنوانِ تکمیل‌شده علامت نمی‌خورد و پیامِ stderr به‌عنوانِ بازخورد به مدل بازخورانده می‌شود. برای متوقف‌کردنِ کاملِ هم‌تیمی به‌جای اجرای دوباره‌اش، JSON با {"continue": false, "stopReason": "..."} برگردان. hookهای TaskCompleted از matcher پشتیبانی نمی‌کنند و در هر رخداد فعال می‌شوند.

علاوه بر فیلدهای ورودیِ مشترک، hookهای TaskCompleted‏ task_id، task_subject و اختیاراً task_description، teammate_name و team_name را دریافت می‌کنند.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "TaskCompleted",
"task_id": "task-001",
"task_subject": "Implement user authentication",
"task_description": "Add login and signup endpoints",
"teammate_name": "implementer",
"team_name": "my-project"
}
فیلدتوضیح
task_idشناسه‌ی taskی که تکمیل می‌شود
task_subjectعنوانِ task
task_descriptionتوضیحِ مفصلِ task. ممکن است غایب باشد
teammate_nameنامِ هم‌تیمی‌ای که task را تکمیل می‌کند. ممکن است غایب باشد
team_nameنامِ تیم. ممکن است غایب باشد

hookهای TaskCompleted دو راه برای کنترلِ تکمیلِ task پشتیبانی می‌کنند:

  • کدِ خروجِ 2: task به‌عنوانِ تکمیل‌شده علامت نمی‌خورد و پیامِ stderr به‌عنوانِ بازخورد به مدل بازخورانده می‌شود.
  • JSONِ {"continue": false, "stopReason": "..."}: هم‌تیمی را کاملاً متوقف می‌کند، مطابق با رفتارِ hookِ Stop. stopReason به کاربر نشان داده می‌شود.

این مثال تست‌ها را اجرا می‌کند و اگر شکست بخورند تکمیلِ task را مسدود می‌کند:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
# Run the test suite
if ! npm test 2>&1; then
echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
exit 2
fi
exit 0

وقتی ایجنتِ اصلیِ Claude Code پاسخ‌دادن را تمام کرده اجرا می‌شود. اگر توقف به‌خاطرِ یک وقفه‌ی کاربر رخ داده باشد اجرا نمی‌شود. خطاهای API به‌جایش StopFailure را فعال می‌کنند.

علاوه بر فیلدهای ورودیِ مشترک، hookهای Stop‏ stop_hook_active، last_assistant_message، background_tasks و session_crons را دریافت می‌کنند. فیلدِ stop_hook_active وقتی true است که Claude Code از پیش در نتیجه‌ی یک hookِ stop در حالِ ادامه است. این مقدار را بررسی کن یا transcript را پردازش کن تا از مسدودشدن روی شرطی که هرگز برقرار نمی‌شود اجتناب کنی. Claude Code پس از ۸ مسدودسازیِ متوالی hook را بازنویسی می‌کند و نوبت را پایان می‌دهد.

فیلدِ last_assistant_message محتوای متنیِ پاسخِ نهاییِ Claude را شامل می‌شود، پس hookها می‌توانند بدونِ پارسِ فایلِ transcript به آن دسترسی پیدا کنند.

آرایه‌های background_tasks و session_crons، که در Claude Code نسخه‌ی v2.1.145 یا بالاتر در دسترس‌اند، به hookها اجازه می‌دهند «نشست تمام شده» را از «نشست متوقف شده و منتظرِ کارِ پس‌زمینه برای بیدارکردنِ دوباره‌اش» تشخیص دهند. هر دو آرایه وقتی رجیستریِ task قابلِ‌دسترس باشد حاضرند و وقتی چیزی در جریان یا زمان‌بندی‌شده نباشد خالی‌اند.

هر ورودی در background_tasks یک taskِ در جریان را توصیف می‌کند و از این فیلدها استفاده می‌کند:

فیلدتوضیح
idشناسه‌ی task
typeبرچسبِ صمیمیِ نوعِ task مثلِ shell، subagent، monitor، workflow، teammate، cloud session یا MCP task. هر برچسب مشخص می‌کند کدام قابلیتِ Claude Code آن task را ساخته. برای نوع‌های ناشناخته به discriminantِ خام برمی‌گردد
statusوضعیتِ جاریِ task
descriptionتوضیحِ متنِ آزاد، با سقفِ ۱۰۰۰ کاراکتر همراه با نشانگرِ درون‌رشته‌ایِ … [+N chars] هنگامِ بریده‌شدن
commandخطِ فرمانِ شِل، با سقفِ ۱۰۰۰ کاراکتر. فقط برای taskهای shell حاضر است
agent_typeنامِ نوعِ ساب‌ایجنت. فقط برای taskهای subagent حاضر است
serverنامِ سرورِ MCP. فقط برای taskهای monitor و MCP task حاضر است
toolنامِ ابزارِ MCP. فقط برای taskهای monitor و MCP task حاضر است
nameنامِ workflow. فقط برای taskهای workflow حاضر است

هر ورودی در session_crons یک بیدارباشِ زمان‌بندی‌شده‌ی محدود به نشست را توصیف می‌کند، که منبعش CronCreate، ScheduleWakeup و /loop است:

فیلدتوضیح
idشناسه‌ی taskِ cron
scheduleعبارتِ cron، برای مثال 0 9 * * 1-5
recurringبرای بیدارباش‌های تک‌شلیکی که schedule‌شان یک زمانِ شلیکِ واحد را کد می‌کند false است، برای taskهایی که در هر مطابقت دوباره شلیک می‌کنند true
promptپرامپتی که هنگامِ شلیکِ cron ثبت می‌شود، با سقفِ ۱۰۰۰ کاراکتر و همان نشانگرِ … [+N chars]

این مثال یک ورودیِ Stop را با یک taskِ shellِ در جریان و یک cronِ recurring نشان می‌دهد:

{
"session_id": "abc123",
"transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "Stop",
"stop_hook_active": true,
"last_assistant_message": "I've completed the refactoring. Here's a summary...",
"background_tasks": [
{
"id": "task-001",
"type": "shell",
"status": "running",
"description": "tail logs",
"command": "tail -f /var/log/syslog"
}
],
"session_crons": [
{
"id": "cron-001",
"schedule": "0 9 * * 1-5",
"recurring": true,
"prompt": "check the build"
}
]
}

hookهای Stop و SubagentStop می‌توانند کنترل کنند که Claude ادامه دهد. علاوه بر فیلدهای خروجیِ JSONِ در دسترسِ همه‌ی hookها، اسکریپتِ hookت می‌تواند این فیلدهای خاصِ رویداد را برگرداند:

فیلدتوضیح
decision"block" از توقفِ Claude جلوگیری می‌کند. برای اجازه‌ی توقفِ Claude حذفش کن
reasonوقتی decision برابرِ "block" است الزامی. به Claude می‌گوید چرا باید ادامه دهد
hookSpecificOutput.additionalContextبازخوردِ غیرخطا برای Claude. گفت‌وگو ادامه می‌یابد تا Claude بتواند بر اساسش عمل کند، اما برخلافِ decision: "block" در transcript به‌عنوانِ بازخوردِ hook نشان داده می‌شود نه یک خطای hook
{
"decision": "block",
"reason": "Must be provided when Claude is blocked from stopping"
}

از additionalContext وقتی استفاده کن که hook طبقِ طراحی کار می‌کند و به Claude راهنمایی می‌دهد، مثلِ «پیش از پایان، مجموعه‌ی تست را اجرا کن». گفت‌وگو را از طریقِ همان محافظت‌های حلقه‌ی decision: "block"، یعنی ورودیِ stop_hook_active و سقفِ ۸-ادامه‌ی-متوالی، ادامه می‌دهد، اما transcript آن را Stop hook feedback برچسب می‌زند و هیچ اعلانِ خطای hookی نشان داده نمی‌شود:

{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Please run the test suite before finishing"
}
}

به‌جای Stop وقتی نوبت به‌خاطرِ یک خطای API پایان می‌یابد اجرا می‌شود. خروجی و کدِ خروج نادیده گرفته می‌شوند. از این برای لاگ‌کردنِ شکست‌ها، فرستادنِ هشدار، یا انجامِ اکشن‌های بازیابی وقتی Claude به‌خاطرِ محدودیت‌های نرخ، مشکلاتِ احراز هویت یا دیگر خطاهای API نمی‌تواند یک پاسخ را تکمیل کند استفاده کن.

علاوه بر فیلدهای ورودیِ مشترک، hookهای StopFailure‏ error، error_detailsِ اختیاری و last_assistant_messageِ اختیاری را دریافت می‌کنند. فیلدِ error نوعِ خطا را مشخص می‌کند و برای فیلترِ matcher استفاده می‌شود.

فیلدتوضیح
errorنوعِ خطا: rate_limit، overloaded، authentication_failed، oauth_org_not_allowed، billing_error، invalid_request، model_not_found، server_error، max_output_tokens، یا unknown
error_detailsجزئیاتِ اضافی درباره‌ی خطا، در صورتِ موجودبودن
last_assistant_messageمتنِ خطای رندرشده‌ی نشان‌داده‌شده در گفت‌وگو. برخلافِ Stop و SubagentStop که این فیلد خروجیِ گفت‌وگوییِ Claude را نگه می‌دارد، برای StopFailure خودِ رشته‌ی خطای API را شامل می‌شود، مثلِ "API Error: Rate limit reached"
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "StopFailure",
"error": "rate_limit",
"error_details": "429 Too Many Requests",
"last_assistant_message": "API Error: Rate limit reached"
}

hookهای StopFailure کنترلِ تصمیم ندارند. فقط برای اهدافِ اعلان و لاگ اجرا می‌شوند.

وقتی یک هم‌تیمیِ تیمِ ایجنت پس از تمام‌کردنِ نوبتش قرار است idle شود اجرا می‌شود. از این برای اعمالِ دروازه‌های کیفیت پیش از این‌که هم‌تیمی از کارکردن بایستد استفاده کن، مثلِ الزامِ گذرکردنِ بررسی‌های lint یا تأییدِ وجودِ فایل‌های خروجی.

وقتی یک hookِ TeammateIdle با کدِ 2 خارج می‌شود، هم‌تیمی پیامِ stderr را به‌عنوانِ بازخورد دریافت می‌کند و به‌جای idle‌شدن به کارکردن ادامه می‌دهد. برای متوقف‌کردنِ کاملِ هم‌تیمی به‌جای اجرای دوباره‌اش، JSON با {"continue": false, "stopReason": "..."} برگردان. hookهای TeammateIdle از matcher پشتیبانی نمی‌کنند و در هر رخداد فعال می‌شوند.

علاوه بر فیلدهای ورودیِ مشترک، hookهای TeammateIdle‏ teammate_name و team_name را دریافت می‌کنند.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "TeammateIdle",
"teammate_name": "researcher",
"team_name": "my-project"
}
فیلدتوضیح
teammate_nameنامِ هم‌تیمی‌ای که قرار است idle شود
team_nameنامِ تیم

hookهای TeammateIdle دو راه برای کنترلِ رفتارِ هم‌تیمی پشتیبانی می‌کنند:

  • کدِ خروجِ 2: هم‌تیمی پیامِ stderr را به‌عنوانِ بازخورد دریافت می‌کند و به‌جای idle‌شدن به کارکردن ادامه می‌دهد.
  • JSONِ {"continue": false, "stopReason": "..."}: هم‌تیمی را کاملاً متوقف می‌کند، مطابق با رفتارِ hookِ Stop. stopReason به کاربر نشان داده می‌شود.

این مثال پیش از اجازه‌ی idle‌شدنِ هم‌تیمی بررسی می‌کند که یک artifactِ build وجود دارد:

#!/bin/bash
if [ ! -f "./dist/output.js" ]; then
echo "Build artifact missing. Run the build before stopping." >&2
exit 2
fi
exit 0

وقتی یک فایلِ پیکربندی در طولِ یک نشست تغییر می‌کند اجرا می‌شود. از این برای ممیزیِ تغییراتِ تنظیمات، اعمالِ سیاست‌های امنیتی، یا مسدودکردنِ تغییراتِ غیرمجاز در فایل‌های پیکربندی استفاده کن.

hookهای ConfigChange برای تغییراتِ فایل‌های تنظیمات، تنظیماتِ سیاستِ مدیریت‌شده و فایل‌های skill فعال می‌شوند. فیلدِ source در ورودی به تو می‌گوید کدام نوعِ پیکربندی تغییر کرده، و فیلدِ اختیاریِ file_path مسیرِ فایلِ تغییریافته را فراهم می‌کند.

matcher روی منبعِ پیکربندی فیلتر می‌کند:

Matcherچه زمانی فعال می‌شود
user_settingsتغییراتِ ~/.claude/settings.json
project_settingsتغییراتِ .claude/settings.json
local_settingsتغییراتِ .claude/settings.local.json
policy_settingsتغییرِ تنظیماتِ سیاستِ مدیریت‌شده
skillsتغییرِ یک فایلِ skill در .claude/skills/

این مثال همه‌ی تغییراتِ پیکربندی را برای ممیزیِ امنیتی لاگ می‌کند:

{
"hooks": {
"ConfigChange": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/audit-config-change.sh",
"args": []
}
]
}
]
}
}

علاوه بر فیلدهای ورودیِ مشترک، hookهای ConfigChange‏ source و اختیاراً file_path را دریافت می‌کنند. فیلدِ source نشان می‌دهد کدام نوعِ پیکربندی تغییر کرده، و file_path مسیرِ فایلِ خاصی را که اصلاح شده فراهم می‌کند.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "ConfigChange",
"source": "project_settings",
"file_path": "/Users/.../my-project/.claude/settings.json"
}

hookهای ConfigChange می‌توانند از اعمال‌شدنِ تغییراتِ پیکربندی جلوگیری کنند. از کدِ خروجِ 2 یا یک decisionِ JSON برای جلوگیری از تغییر استفاده کن. وقتی مسدود شود، تنظیماتِ جدید به نشستِ در حالِ اجرا اعمال نمی‌شوند.

فیلدتوضیح
decision"block" از اعمال‌شدنِ تغییرِ پیکربندی جلوگیری می‌کند. برای اجازه‌ی تغییر حذفش کن
reasonتوضیحی که وقتی decision برابرِ "block" است به کاربر نشان داده می‌شود
{
"decision": "block",
"reason": "Configuration changes to project settings require admin approval"
}

تغییراتِ policy_settings را نمی‌توان مسدود کرد. hookها همچنان برای منابعِ policy_settings فعال می‌شوند، پس می‌توانی از آن‌ها برای ممیزیِ لاگ استفاده کنی، اما هر تصمیمِ مسدودسازی نادیده گرفته می‌شود. این تضمین می‌کند تنظیماتِ مدیریت‌شده‌ی سازمانی همیشه اعمال شوند.

وقتی دایرکتوریِ کاری در طولِ یک نشست تغییر می‌کند اجرا می‌شود، برای مثال وقتی Claude یک فرمانِ cd اجرا می‌کند. از این برای واکنش به تغییراتِ دایرکتوری استفاده کن: بارگذاریِ دوباره‌ی متغیرهای محیطی، فعال‌کردنِ toolchainهای خاصِ پروژه، یا اجرای خودکارِ اسکریپت‌های راه‌اندازی. با FileChanged برای ابزارهایی مثلِ direnv که محیطِ هر-دایرکتوری را مدیریت می‌کنند جفت می‌شود.

hookهای CwdChanged به CLAUDE_ENV_FILE دسترسی دارند. متغیرهای نوشته‌شده در آن فایل به فرمان‌های Bashِ بعدیِ نشست پایدار می‌مانند، درست مثلِ hookهای SessionStart.

CwdChanged از matcher پشتیبانی نمی‌کند و در هر تغییرِ دایرکتوری فعال می‌شود.

علاوه بر فیلدهای ورودیِ مشترک، hookهای CwdChanged‏ old_cwd و new_cwd را دریافت می‌کنند.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project/src",
"hook_event_name": "CwdChanged",
"old_cwd": "/Users/my-project",
"new_cwd": "/Users/my-project/src"
}

علاوه بر فیلدهای خروجیِ JSONِ در دسترسِ همه‌ی hookها، hookهای CwdChanged می‌توانند watchPaths را برای تنظیمِ پویای این‌که FileChanged کدام مسیرهای فایل را watch می‌کند برگردانند:

فیلدتوضیح
watchPathsآرایه‌ای از مسیرهای مطلق. فهرستِ watchِ پویای جاری را جایگزین می‌کند (مسیرهای پیکربندیِ matcherت همیشه watch می‌شوند). برگرداندنِ یک آرایه‌ی خالی فهرستِ پویا را پاک می‌کند، که هنگامِ ورود به یک دایرکتوریِ جدید معمول است

hookهای CwdChanged کنترلِ تصمیم ندارند. نمی‌توانند تغییرِ دایرکتوری را مسدود کنند.

وقتی یک فایلِ تحتِ نظر روی دیسک تغییر می‌کند اجرا می‌شود. برای بارگذاریِ دوباره‌ی متغیرهای محیطی وقتی فایل‌های پیکربندیِ پروژه اصلاح می‌شوند مفید است.

matcher برای این رویداد دو نقش ایفا می‌کند:

  • ساختنِ فهرستِ watch: مقدار روی | تقسیم می‌شود و هر بخش به‌عنوانِ یک نام‌فایلِ واقعی در دایرکتوریِ کاری ثبت می‌شود، پس ".envrc|.env" دقیقاً همان دو فایل را watch می‌کند. الگوهای regex اینجا مفید نیستند: مقداری مثلِ ^\.env فایلی را که نامش واقعاً ^\.env است watch می‌کند.
  • فیلترکردنِ این‌که کدام hookها اجرا شوند: وقتی یک فایلِ تحتِ نظر تغییر می‌کند، همان مقدار با استفاده از قواعدِ استانداردِ matcher در برابرِ basenameِ فایلِ تغییریافته فیلتر می‌کند که کدام گروه‌های hook اجرا شوند.

hookهای FileChanged به CLAUDE_ENV_FILE دسترسی دارند. متغیرهای نوشته‌شده در آن فایل به فرمان‌های Bashِ بعدیِ نشست پایدار می‌مانند، درست مثلِ hookهای SessionStart.

علاوه بر فیلدهای ورودیِ مشترک، hookهای FileChanged‏ file_path و event را دریافت می‌کنند.

فیلدتوضیح
file_pathمسیرِ مطلق به فایلی که تغییر کرد
eventچه اتفاقی افتاد: "change" (فایل اصلاح شد)، "add" (فایل ساخته شد)، یا "unlink" (فایل حذف شد)
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "FileChanged",
"file_path": "/Users/my-project/.envrc",
"event": "change"
}

علاوه بر فیلدهای خروجیِ JSONِ در دسترسِ همه‌ی hookها، hookهای FileChanged می‌توانند watchPaths را برای به‌روزرسانیِ پویای این‌که کدام مسیرهای فایل watch می‌شوند برگردانند:

فیلدتوضیح
watchPathsآرایه‌ای از مسیرهای مطلق. فهرستِ watchِ پویای جاری را جایگزین می‌کند (مسیرهای پیکربندیِ matcherت همیشه watch می‌شوند). از این وقتی استفاده کن که اسکریپتِ hookت فایل‌های اضافی برای watch بر اساسِ فایلِ تغییریافته کشف می‌کند

hookهای FileChanged کنترلِ تصمیم ندارند. نمی‌توانند از رخ‌دادنِ تغییرِ فایل جلوگیری کنند.

وقتی claude --worktree را اجرا می‌کنی یا یک ساب‌ایجنت از isolation: "worktree" استفاده می‌کند، Claude Code با استفاده از git worktree یک نسخه‌ی کاریِ ایزوله می‌سازد. اگر یک hookِ WorktreeCreate پیکربندی کنی، رفتارِ پیش‌فرضِ git را جایگزین می‌کند و به تو اجازه می‌دهد از یک سیستمِ کنترلِ نسخه‌ی متفاوت مثلِ SVN، Perforce یا Mercurial استفاده کنی.

چون hook رفتارِ پیش‌فرض را کاملاً جایگزین می‌کند، .worktreeinclude پردازش نمی‌شود. اگر نیاز داری فایل‌های پیکربندیِ محلی مثلِ .env را به worktreeِ جدید کپی کنی، آن را داخلِ اسکریپتِ hookت انجام بده.

hook باید مسیرِ مطلق به دایرکتوریِ worktreeِ ساخته‌شده را برگرداند. Claude Code از این مسیر به‌عنوانِ دایرکتوریِ کاری برای نشستِ ایزوله استفاده می‌کند. hookهای فرمان آن را روی stdout چاپ می‌کنند؛ hookهای HTTP آن را از طریقِ hookSpecificOutput.worktreePath برمی‌گردانند.

این مثال یک نسخه‌ی کاریِ SVN می‌سازد و مسیر را برای استفاده‌ی Claude Code چاپ می‌کند. URLِ مخزن را با مالِ خودت جایگزین کن:

{
"hooks": {
"WorktreeCreate": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
}
]
}
]
}
}

hook نامِ worktree یعنی name را از ورودیِ JSON روی stdin می‌خواند، یک نسخه‌ی تازه را در یک دایرکتوریِ جدید checkout می‌کند، و مسیرِ دایرکتوری را چاپ می‌کند. echo روی خطِ آخر همان چیزی است که Claude Code به‌عنوانِ مسیرِ worktree می‌خواند. هر خروجیِ دیگری را به stderr هدایت کن تا با مسیر تداخل نکند.

علاوه بر فیلدهای ورودیِ مشترک، hookهای WorktreeCreate فیلدِ name را دریافت می‌کنند. این یک شناسه‌ی slug برای worktreeِ جدید است، یا توسطِ کاربر مشخص‌شده یا خودکار-تولیدشده (برای مثال، bold-oak-a3f2).

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "WorktreeCreate",
"name": "feature-auth"
}

hookهای WorktreeCreate از مدلِ استانداردِ تصمیمِ allow/block استفاده نمی‌کنند. در عوض، موفقیت یا شکستِ hook نتیجه را تعیین می‌کند. hook باید مسیرِ مطلق به دایرکتوریِ worktreeِ ساخته‌شده را برگرداند:

  • hookهای فرمان (type: "command"): مسیر را روی stdout چاپ کن.
  • hookهای HTTP (type: "http"): { "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } } را در بدنه‌ی پاسخ برگردان.

اگر hook شکست بخورد یا هیچ مسیری تولید نکند، ساختِ worktree با یک خطا شکست می‌خورد.

همتای پاک‌سازیِ WorktreeCreate. این hook وقتی یک worktree در حالِ حذف است فعال می‌شود، چه وقتی از یک نشستِ --worktree خارج می‌شوی و حذفش را انتخاب می‌کنی، چه وقتی یک ساب‌ایجنت با isolation: "worktree" تمام می‌شود. برای worktreeهای مبتنی بر git، Claude پاک‌سازی را به‌صورتِ خودکار با git worktree remove انجام می‌دهد. اگر یک hookِ WorktreeCreate برای یک سیستمِ کنترلِ نسخه‌ی غیر-git پیکربندی کردی، آن را با یک hookِ WorktreeRemove جفت کن تا پاک‌سازی را مدیریت کند. بدونِ آن، دایرکتوریِ worktree روی دیسک باقی می‌ماند.

Claude Code مسیرِ بازگردانده‌شده توسطِ WorktreeCreate را به‌عنوانِ worktree_path در ورودیِ hook پاس می‌دهد. این مثال آن مسیر را می‌خواند و دایرکتوری را حذف می‌کند:

{
"hooks": {
"WorktreeRemove": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
}
]
}
]
}
}

علاوه بر فیلدهای ورودیِ مشترک، hookهای WorktreeRemove فیلدِ worktree_path را دریافت می‌کنند، که مسیرِ مطلق به worktreeِ در حالِ حذف است.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "WorktreeRemove",
"worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
}

hookهای WorktreeRemove کنترلِ تصمیم ندارند. نمی‌توانند حذفِ worktree را مسدود کنند اما می‌توانند وظایفِ پاک‌سازی مثلِ حذفِ حالتِ کنترلِ نسخه یا آرشیوِ تغییرات را انجام دهند. شکست‌های hook فقط در حالتِ دیباگ لاگ می‌شوند.

پیش از این‌که Claude Code قرار است یک عملیاتِ فشرده‌سازی اجرا کند اجرا می‌شود.

مقدارِ matcher نشان می‌دهد فشرده‌سازی دستی فعال شده یا خودکار:

Matcherچه زمانی فعال می‌شود
manual/compact
autoفشرده‌سازیِ خودکار وقتی پنجره‌ی کانتکست پر است

با کدِ 2 خارج شو تا فشرده‌سازی را مسدود کنی. برای یک /compactِ دستی، پیامِ stderr به کاربر نشان داده می‌شود. همچنین می‌توانی با برگرداندنِ JSON با "decision": "block" مسدود کنی.

مسدودکردنِ فشرده‌سازیِ خودکار بسته به زمانِ فعال‌شدنش اثرهای متفاوتی دارد. اگر فشرده‌سازی به‌صورتِ پیش‌دستانه پیش از محدودیتِ کانتکست فعال شده باشد، Claude Code از آن می‌گذرد و گفت‌وگو بدونِ فشرده‌سازی ادامه می‌یابد. اگر فشرده‌سازی برای بازیابی از یک خطای محدودیتِ کانتکست که از پیش توسطِ API برگردانده شده فعال شده باشد، خطای زیرین آشکار می‌شود و درخواستِ جاری شکست می‌خورد.

علاوه بر فیلدهای ورودیِ مشترک، hookهای PreCompact‏ trigger و custom_instructions را دریافت می‌کنند. برای manual، custom_instructions آنچه را کاربر به /compact پاس می‌دهد شامل می‌شود. برای auto، custom_instructions خالی است.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "PreCompact",
"trigger": "manual",
"custom_instructions": ""
}

پس از این‌که Claude Code یک عملیاتِ فشرده‌سازی را تکمیل می‌کند اجرا می‌شود. از این رویداد برای واکنش به حالتِ فشرده‌ی جدید استفاده کن، برای مثال برای لاگ‌کردنِ خلاصه‌ی تولیدشده یا به‌روزرسانیِ حالتِ بیرونی.

همان مقادیرِ matcherِ PreCompact اعمال می‌شوند:

Matcherچه زمانی فعال می‌شود
manualپس از /compact
autoپس از فشرده‌سازیِ خودکار وقتی پنجره‌ی کانتکست پر است

علاوه بر فیلدهای ورودیِ مشترک، hookهای PostCompact‏ trigger و compact_summary را دریافت می‌کنند. فیلدِ compact_summary خلاصه‌ی گفت‌وگوی تولیدشده توسطِ عملیاتِ فشرده‌سازی را شامل می‌شود.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "PostCompact",
"trigger": "manual",
"compact_summary": "Summary of the compacted conversation..."
}

hookهای PostCompact کنترلِ تصمیم ندارند. نمی‌توانند روی نتیجه‌ی فشرده‌سازی اثر بگذارند اما می‌توانند وظایفِ پیگیری انجام دهند.

وقتی یک نشستِ Claude Code پایان می‌یابد اجرا می‌شود. برای وظایفِ پاک‌سازی، لاگ‌کردنِ آمارِ نشست، یا ذخیره‌ی حالتِ نشست مفید است. از matcherها برای فیلترکردن بر اساسِ دلیلِ خروج پشتیبانی می‌کند.

فیلدِ reason در ورودیِ hook نشان می‌دهد چرا نشست پایان یافت:

دلیلتوضیح
clearنشست با فرمانِ /clear پاک شد
resumeنشست از طریقِ /resumeِ تعاملی عوض شد
logoutکاربر logout کرد
prompt_input_exitکاربر هنگامِ نمایانیِ ورودیِ پرامپت خارج شد
bypass_permissions_disabledحالتِ bypass permissions غیرفعال شد
otherدلایلِ خروجِ دیگر

علاوه بر فیلدهای ورودیِ مشترک، hookهای SessionEnd یک فیلدِ reason دریافت می‌کنند که نشان می‌دهد چرا نشست پایان یافت. برای همه‌ی مقادیر جدولِ reason در بالا را ببین.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SessionEnd",
"reason": "other"
}

hookهای SessionEnd کنترلِ تصمیم ندارند. نمی‌توانند خاتمه‌ی نشست را مسدود کنند اما می‌توانند وظایفِ پاک‌سازی انجام دهند.

hookهای SessionEnd یک timeoutِ پیش‌فرضِ ۱.۵ ثانیه دارند. این برای خروجِ نشست، /clear و عوض‌کردنِ نشست‌ها از طریقِ /resumeِ تعاملی اعمال می‌شود. اگر یک hook به زمانِ بیشتری نیاز دارد، یک timeoutِ هر-hook در پیکربندیِ hook تنظیم کن. بودجه‌ی کلی به‌صورتِ خودکار تا بالاترین timeoutِ هر-hookِ پیکربندی‌شده در فایل‌های تنظیمات، تا ۶۰ ثانیه، بالا می‌رود. timeoutهای تنظیم‌شده روی hookهای فراهم‌شده توسطِ پلاگین بودجه را بالا نمی‌برند. برای بازنویسیِ صریحِ بودجه، متغیرِ محیطیِ CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS را به میلی‌ثانیه تنظیم کن.

Terminal window
CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude

وقتی یک سرورِ MCP در میانِ کار درخواستِ ورودیِ کاربر می‌کند اجرا می‌شود. به‌صورتِ پیش‌فرض، Claude Code یک دیالوگِ تعاملی برای پاسخِ کاربر نشان می‌دهد. hookها می‌توانند این درخواست را میانجی‌گری کنند و به‌صورتِ برنامه‌ای پاسخ دهند و دیالوگ را کاملاً کنار بگذارند.

فیلدِ matcher در برابرِ نامِ سرورِ MCP مطابقت می‌کند.

علاوه بر فیلدهای ورودیِ مشترک، hookهای Elicitation‏ mcp_server_name، message و فیلدهای اختیاریِ mode، url، elicitation_id و requested_schema را دریافت می‌کنند.

برای elicitationِ حالتِ form (معمول‌ترین مورد):

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "Elicitation",
"mcp_server_name": "my-mcp-server",
"message": "Please provide your credentials",
"mode": "form",
"requested_schema": {
"type": "object",
"properties": {
"username": { "type": "string", "title": "Username" }
}
}
}

برای elicitationِ حالتِ URL (احراز هویتِ مبتنی بر مرورگر):

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "Elicitation",
"mcp_server_name": "my-mcp-server",
"message": "Please authenticate",
"mode": "url",
"url": "https://auth.example.com/login"
}

برای پاسخِ برنامه‌ای بدونِ نشان‌دادنِ دیالوگ، یک شیء JSON با hookSpecificOutput برگردان:

{
"hookSpecificOutput": {
"hookEventName": "Elicitation",
"action": "accept",
"content": {
"username": "alice"
}
}
}
فیلدمقادیرتوضیح
actionaccept، decline، cancelاین‌که درخواست را accept، decline یا cancel کنی
contentobjectمقادیرِ فیلدهای فرم برای ثبت. فقط وقتی action برابرِ accept است استفاده می‌شود

کدِ خروجِ 2 elicitation را رد می‌کند و stderr را به کاربر نشان می‌دهد.

پس از این‌که کاربر به یک elicitationِ MCP پاسخ می‌دهد اجرا می‌شود. hookها می‌توانند پاسخ را پیش از فرستادنِ دوباره‌اش به سرورِ MCP مشاهده، تغییر یا مسدود کنند.

فیلدِ matcher در برابرِ نامِ سرورِ MCP مطابقت می‌کند.

علاوه بر فیلدهای ورودیِ مشترک، hookهای ElicitationResult‏ mcp_server_name، action و فیلدهای اختیاریِ mode، elicitation_id و content را دریافت می‌کنند.

{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "ElicitationResult",
"mcp_server_name": "my-mcp-server",
"action": "accept",
"content": { "username": "alice" },
"mode": "form",
"elicitation_id": "elicit-123"
}

برای بازنویسیِ پاسخِ کاربر، یک شیء JSON با hookSpecificOutput برگردان:

{
"hookSpecificOutput": {
"hookEventName": "ElicitationResult",
"action": "decline",
"content": {}
}
}
فیلدمقادیرتوضیح
actionaccept، decline، cancelاکشنِ کاربر را بازنویسی می‌کند
contentobjectمقادیرِ فیلدهای فرم را بازنویسی می‌کند. فقط وقتی action برابرِ accept است معنادار است

کدِ خروجِ 2 پاسخ را مسدود می‌کند و اکشنِ مؤثر را به decline تغییر می‌دهد.

علاوه بر hookهای command، HTTP و ابزارِ MCP، Claude Code از hookهای مبتنی بر پرامپت (type: "prompt") پشتیبانی می‌کند که از یک LLM برای ارزیابیِ این‌که یک اکشن را مجاز یا مسدود کند استفاده می‌کنند، و از hookهای ایجنت (type: "agent") که یک تأییدگرِ ایجنتیک با دسترسی به ابزار spawn می‌کنند. همه‌ی رویدادها هر نوعِ hook را پشتیبانی نمی‌کنند.

رویدادهایی که هر پنج نوعِ hook (command، http، mcp_tool، prompt و agent) را پشتیبانی می‌کنند:

  • PermissionDenied
  • PermissionRequest
  • PostToolBatch
  • PostToolUse
  • PostToolUseFailure
  • PreToolUse
  • Stop
  • SubagentStop
  • TaskCompleted
  • TaskCreated
  • TeammateIdle
  • UserPromptExpansion
  • UserPromptSubmit

رویدادهایی که hookهای command، http و mcp_tool را پشتیبانی می‌کنند اما prompt یا agent را نه:

  • ConfigChange
  • CwdChanged
  • Elicitation
  • ElicitationResult
  • FileChanged
  • InstructionsLoaded
  • Notification
  • PostCompact
  • PreCompact
  • SessionEnd
  • StopFailure
  • SubagentStart
  • WorktreeCreate
  • WorktreeRemove

SessionStart و Setup از hookهای command و mcp_tool پشتیبانی می‌کنند. از hookهای http، prompt یا agent پشتیبانی نمی‌کنند.

hookهای مبتنی بر پرامپت چطور کار می‌کنند

Section titled “hookهای مبتنی بر پرامپت چطور کار می‌کنند”

به‌جای اجرای یک فرمانِ Bash، hookهای مبتنی بر پرامپت:

  1. ورودیِ hook و پرامپتت را به یک مدلِ Claude، به‌صورتِ پیش‌فرض Haiku، می‌فرستند
  2. LLM با یک JSONِ ساختاریافته‌ی شاملِ یک تصمیم پاسخ می‌دهد
  3. Claude Code تصمیم را به‌صورتِ خودکار پردازش می‌کند

type را به "prompt" تنظیم کن و به‌جای یک command یک رشته‌ی prompt فراهم کن. از جانگهدارِ $ARGUMENTS برای تزریقِ داده‌ی ورودیِ JSONِ hook به متنِ پرامپتت استفاده کن. Claude Code پرامپت و ورودیِ ترکیب‌شده را به یک مدلِ سریعِ Claude می‌فرستد، که یک تصمیمِ JSON برمی‌گرداند.

این hookِ Stop از LLM می‌خواهد ارزیابی کند که آیا همه‌ی taskها تکمیل شده‌اند پیش از این‌که به Claude اجازه‌ی پایان دهد:

{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
}
]
}
]
}
}
فیلدالزامیتوضیح
typeبلهباید "prompt" باشد
promptبلهمتنِ پرامپتی که به LLM فرستاده می‌شود. از $ARGUMENTS به‌عنوانِ جانگهدارِ JSONِ ورودیِ hook استفاده کن. اگر $ARGUMENTS حاضر نباشد، JSONِ ورودی به پرامپت پیوست می‌شود
modelخیرمدلی که برای ارزیابی استفاده می‌شود. پیش‌فرض یک مدلِ سریع
timeoutخیرtimeout به ثانیه. پیش‌فرض: 30
continueOnBlockخیروقتی پرامپت ok: false برمی‌گرداند، دلیل را به Claude بازخوران و به‌جای توقف نوبت را ادامه بده. پیش‌فرض: false. به‌صورتِ continue: true روی decision: "block"ِ حاصل پیاده‌سازی شده. برای رفتارِ هر-رویداد اسکیمای پاسخ را ببین

LLM باید با JSONِ شاملِ این پاسخ دهد:

{
"ok": true | false,
"reason": "Explanation for the decision"
}
فیلدتوضیح
oktrue برای مجازکردن. false یک decision: "block" تولید می‌کند. رفتارِ هر-رویداد را در پایین ببین
reasonوقتی ok برابرِ false است الزامی. به‌عنوانِ دلیلِ مسدودسازی استفاده می‌شود

آنچه در ok: false اتفاق می‌افتد به رویداد بستگی دارد:

  • Stop و SubagentStop: دلیل به‌عنوانِ دستورالعملِ بعدیِ Claude به آن بازخورانده می‌شود و نوبت ادامه می‌یابد
  • PreToolUse: فراخوانیِ ابزار رد می‌شود و دلیل به‌عنوانِ خطای ابزار به Claude برگردانده می‌شود، معادلِ permissionDecision: "deny"ِ یک hookِ فرمان
  • PostToolUse: به‌صورتِ پیش‌فرض نوبت پایان می‌یابد و دلیل به‌عنوانِ یک خطِ هشدار در چت ظاهر می‌شود. continueOnBlock: true را تنظیم کن تا به‌جایش دلیل به Claude بازخورانده شود و نوبت ادامه یابد
  • PostToolBatch، UserPromptSubmit و UserPromptExpansion: نوبت پایان می‌یابد و دلیل به‌عنوانِ یک خطِ هشدار ظاهر می‌شود. این رویدادها روی decision: "block" صرفِ‌نظر از continue نوبت را پایان می‌دهند
  • PostToolUseFailure، TaskCreated و TaskCompleted: دلیل به‌عنوانِ خطای ابزار به Claude برگردانده می‌شود، مشابهِ PreToolUse
  • TeammateIdle: به‌صورتِ پیش‌فرض هم‌تیمی می‌ایستد و دلیل به‌عنوانِ یک خطِ هشدار ظاهر می‌شود. continueOnBlock: true را تنظیم کن تا به‌جایش دلیل به هم‌تیمی بازخورانده شود و در حالِ کارکردن نگه داشته شود
  • PermissionRequest: ok: false اثری ندارد. برای ردِ یک تأیید از یک hook، از یک hookِ فرمان که hookSpecificOutput.decision.behavior: "deny" برمی‌گرداند استفاده کن
  • PermissionDenied: ok: false اثری ندارد چون رد از پیش اتفاق افتاده. تنها خروجی‌ای که این رویداد می‌خواند hookSpecificOutput.retry است، که hookهای پرامپت و ایجنت نمی‌توانند تنظیمش کنند — روی این رویداد اجرا می‌شوند، اما خروجی‌شان دور ریخته می‌شود. از یک hookِ فرمان برای برگرداندنِ retry استفاده کن

اگر روی هر رویدادی به کنترلِ دقیق‌تری نیاز داری، از یک hookِ فرمان با فیلدهای هر-رویدادِ توصیف‌شده در کنترلِ تصمیم استفاده کن.

مثال: hookِ Stopِ چندمعیاره

Section titled “مثال: hookِ Stopِ چندمعیاره”

این hookِ Stop از یک پرامپتِ مفصل برای بررسیِ سه شرط پیش از اجازه‌ی توقفِ Claude استفاده می‌کند. اگر "ok" برابرِ false باشد، Claude با دلیلِ ارائه‌شده به‌عنوانِ دستورالعملِ بعدی‌اش به کارکردن ادامه می‌دهد. hookهای SubagentStop از همان فرمت برای ارزیابیِ این‌که آیا یک ساب‌ایجنت باید بایستد استفاده می‌کنند:

{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
"timeout": 30
}
]
}
]
}
}

hookهای مبتنی بر ایجنت (type: "agent") مثلِ hookهای مبتنی بر پرامپت‌اند اما با دسترسیِ چندنوبتی به ابزار. به‌جای یک فراخوانیِ واحدِ LLM، یک hookِ ایجنت یک ساب‌ایجنت spawn می‌کند که می‌تواند فایل‌ها را بخواند، کد را جست‌وجو کند و کدبیس را بازرسی کند تا شرایط را تأیید کند. hookهای ایجنت همان رویدادهای hookهای مبتنی بر پرامپت را پشتیبانی می‌کنند.

hookهای ایجنت چطور کار می‌کنند

Section titled “hookهای ایجنت چطور کار می‌کنند”

وقتی یک hookِ ایجنت فعال می‌شود:

  1. Claude Code یک ساب‌ایجنت با پرامپتت و ورودیِ JSONِ hook‏ spawn می‌کند
  2. ساب‌ایجنت می‌تواند از ابزارهایی مثلِ Read، Grep و Glob برای بررسی استفاده کند
  3. پس از حداکثر ۵۰ نوبت، ساب‌ایجنت یک تصمیمِ ساختاریافته‌ی { "ok": true/false } برمی‌گرداند
  4. Claude Code تصمیم را به همان شیوه‌ی یک hookِ پرامپت پردازش می‌کند

hookهای ایجنت وقتی مفیدند که تأیید نیازمندِ بازرسیِ فایل‌های واقعی یا خروجیِ تست باشد، نه فقط ارزیابیِ داده‌ی ورودیِ hook به‌تنهایی.

type را به "agent" تنظیم کن و یک رشته‌ی prompt فراهم کن. فیلدهای پیکربندی همان hookهای پرامپت هستند، با یک timeoutِ پیش‌فرضِ طولانی‌تر:

فیلدالزامیتوضیح
typeبلهباید "agent" باشد
promptبلهپرامپتی که توصیف می‌کند چه چیزی را تأیید کند. از $ARGUMENTS به‌عنوانِ جانگهدارِ JSONِ ورودیِ hook استفاده کن
modelخیرمدلی که استفاده می‌شود. پیش‌فرض یک مدلِ سریع
timeoutخیرtimeout به ثانیه. پیش‌فرض: 60

اسکیمای پاسخ همان hookهای پرامپت است: { "ok": true } برای مجازکردن یا { "ok": false, "reason": "..." } برای مسدودکردن.

این hookِ Stop تأیید می‌کند که همه‌ی تست‌های واحد می‌گذرند پیش از این‌که به 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ها در پس‌زمینه

Section titled “اجرای hookها در پس‌زمینه”

به‌صورتِ پیش‌فرض، hookها اجرای Claude را تا تکمیل‌شان مسدود می‌کنند. برای وظایفِ طولانی مثلِ استقرارها، مجموعه‌های تست یا فراخوانی‌های APIِ بیرونی، "async": true را تنظیم کن تا hook در پس‌زمینه اجرا شود در حالی که Claude به کارکردن ادامه می‌دهد. hookهای async نمی‌توانند رفتارِ Claude را مسدود یا کنترل کنند: فیلدهای پاسخی مثلِ decision، permissionDecision و continue اثری ندارند، چون اکشنی که قرار بود کنترل کنند از پیش تکمیل شده.

"async": true را به پیکربندیِ یک hookِ فرمان اضافه کن تا در پس‌زمینه بدونِ مسدودکردنِ Claude اجرا شود. این فیلد فقط روی hookهای type: "command" در دسترس است.

این hook پس از هر فراخوانیِ ابزارِ Write یک اسکریپتِ تست اجرا می‌کند. Claude بلافاصله به کارکردن ادامه می‌دهد در حالی که run-tests.sh تا ۱۲۰ ثانیه اجرا می‌شود. وقتی اسکریپت تمام می‌شود، خروجی‌اش در نوبتِ بعدیِ گفت‌وگو تحویل داده می‌شود:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "/path/to/run-tests.sh",
"async": true,
"timeout": 120
}
]
}
]
}
}

فیلدِ timeout حداکثر زمان به ثانیه برای پروسه‌ی پس‌زمینه را تعیین می‌کند. اگر مشخص نشود، hookهای async از همان پیش‌فرضِ ۱۰-دقیقه‌ای hookهای sync استفاده می‌کنند.

hookهای async چطور اجرا می‌شوند

Section titled “hookهای async چطور اجرا می‌شوند”

وقتی یک hookِ async فعال می‌شود، Claude Code پروسه‌ی hook را شروع می‌کند و بلافاصله بدونِ انتظار برای اتمامش ادامه می‌دهد. hook همان ورودیِ JSON را از طریقِ stdin مثلِ یک hookِ همگام دریافت می‌کند.

پس از خروجِ پروسه‌ی پس‌زمینه، اگر hook یک پاسخِ JSON با فیلدِ additionalContext تولید کرده باشد، آن محتوا به‌عنوانِ کانتکست در نوبتِ بعدیِ گفت‌وگو به Claude تحویل می‌شود. یک فیلدِ systemMessage به تو نشان داده می‌شود، نه به Claude.

اعلان‌های تکمیلِ hookِ async به‌صورتِ پیش‌فرض سرکوب می‌شوند. برای دیدنشان، حالتِ verbose را با Ctrl+O فعال کن یا Claude Code را با --verbose راه بینداز.

مثال: اجرای تست‌ها پس از تغییراتِ فایل

Section titled “مثال: اجرای تست‌ها پس از تغییراتِ فایل”

این hook هر وقت Claude فایلی می‌نویسد یک مجموعه‌ی تست را در پس‌زمینه شروع می‌کند، سپس وقتی تست‌ها تمام می‌شوند نتایج را به Claude گزارش می‌دهد. این اسکریپت را در .claude/hooks/run-tests-async.sh در پروژه‌ات ذخیره کن و با chmod +x اجراشدنی‌اش کن:

run-tests-async.sh
#!/bin/bash
# Read hook input from stdin
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Only run tests for source files
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
exit 0
fi
# Run tests and report results to Claude via additionalContext
RESULT=$(npm test 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
MSG="Tests passed after editing $FILE_PATH"
else
MSG="Tests failed after editing $FILE_PATH: $RESULT"
fi
jq -nc --arg msg "$MSG" '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $msg}}'

سپس این پیکربندی را به .claude/settings.json در ریشه‌ی پروژه‌ات اضافه کن. پرچمِ async: true به Claude اجازه می‌دهد در حالی که تست‌ها اجرا می‌شوند به کارکردن ادامه دهد:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",
"args": [],
"async": true,
"timeout": 300
}
]
}
]
}
}

hookهای async در مقایسه با hookهای همگام چند محدودیت دارند:

  • فقط hookهای type: "command"async را پشتیبانی می‌کنند. hookهای مبتنی بر پرامپت نمی‌توانند به‌صورتِ asynchronous اجرا شوند.
  • hookهای async نمی‌توانند فراخوانی‌های ابزار را مسدود کنند یا تصمیم برگردانند. تا وقتی hook تکمیل می‌شود، اکشنِ فعال‌کننده از پیش پیش رفته.
  • خروجیِ hook در نوبتِ بعدیِ گفت‌وگو تحویل داده می‌شود. اگر نشست idle باشد، پاسخ تا تعاملِ بعدیِ کاربر منتظر می‌ماند. استثنا: یک hookِ asyncRewake که با کدِ 2 خارج می‌شود Claude را فوراً بیدار می‌کند حتی وقتی نشست idle است.
  • هر اجرا یک پروسه‌ی پس‌زمینه‌ی جداگانه می‌سازد. هیچ حذفِ تکراری‌ای میانِ چند شلیکِ همان hookِ async نیست.

hookهای فرمان با مجوزهای کاملِ کاربرِ سیستمت اجرا می‌شوند.

بهترین شیوه‌های امنیتی

Section titled “بهترین شیوه‌های امنیتی”

این شیوه‌ها را هنگامِ نوشتنِ hookها در نظر داشته باش:

  • ورودی‌ها را اعتبارسنجی و پاک‌سازی کن: هرگز به داده‌ی ورودی کورکورانه اعتماد نکن
  • همیشه متغیرهای شِل را نقل‌قول کن: از "$VAR" استفاده کن نه $VAR
  • پیمایشِ مسیر را مسدود کن: .. را در مسیرهای فایل بررسی کن
  • از مسیرهای مطلق استفاده کن: مسیرهای کاملِ اسکریپت‌ها را مشخص کن. در فرمِ exec، از ${CLAUDE_PROJECT_DIR} استفاده کن و مسیر به نقل‌قول نیاز ندارد. در فرمِ shell، آن را در نقل‌قولِ دوگانه بپیچ
  • از فایل‌های حساس بگذر: از .env، .git/، کلیدها و غیره اجتناب کن

روی Windows، می‌توانی hookهای منفرد را در PowerShell با تنظیمِ "shell": "powershell" روی یک hookِ فرمان اجرا کنی. hookها مستقیماً PowerShell را spawn می‌کنند، پس این صرفِ‌نظر از این‌که CLAUDE_CODE_USE_POWERSHELL_TOOL تنظیم شده یا نه کار می‌کند. Claude Code به‌صورتِ خودکار pwsh.exe (PowerShell 7+) را با بازگشت به powershell.exe (5.1) تشخیص می‌دهد.

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"shell": "powershell",
"command": "Write-Host 'File written'"
}
]
}
]
}
}

جزئیاتِ اجرای hook، از جمله این‌که کدام hookها مطابقت کردند، کدهای خروجشان، و stdout و stderrِ کامل، در فایلِ لاگِ دیباگ نوشته می‌شوند. Claude Code را با claude --debug-file <path> راه بینداز تا لاگ در یک محلِ مشخص نوشته شود، یا claude --debug را اجرا کن و لاگ را در ~/.claude/debug/<session-id>.txt بخوان. پرچمِ --debug در ترمینال چاپ نمی‌کند.

[DEBUG] Executing hooks for PostToolUse:Write
[DEBUG] Found 1 hook commands to execute
[DEBUG] Executing hook command: <Your command> with timeout 600000ms
[DEBUG] Hook command completed with status 0: <Your stdout>

برای جزئیاتِ ریزدانه‌ترِ مطابقتِ hook، CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose را تنظیم کن تا خطوطِ لاگِ اضافی مثلِ شمارشِ matcherهای hook و مطابقتِ کوئری را ببینی.

برای عیب‌یابیِ مشکلاتِ رایج مثلِ فعال‌نشدنِ hookها، hookهای Stop که مدام مسدود می‌کنند، یا خطاهای پیکربندی، محدودیت‌ها و عیب‌یابی در راهنما را ببین. برای راهنماییِ تشخیصیِ گسترده‌تری که /context، /doctor و تقدمِ تنظیمات را پوشش می‌دهد، دیباگِ پیکربندی‌ات را ببین.