SSD Nodes Learn Hosting plans →
تعلیمی Matt Connorتحریر: Matt Connor · اپ ڈیٹ شدہ 2026-08-29

Claude Code hooks: events، exit code 2 اور سیکیورٹی

Claude Code hooks model کی رضامندی کے بغیر چلتے ہیں۔ جانیں یہ کہاں درج ہوتے ہیں، کون سے events fire ہوتے ہیں، exit code 2 tool call کیوں روکتا ہے اور سیکیورٹی لاگت کیا ہے۔

Claude Code hook کیا ہے

Claude Code hooks وہ shell commands ہیں جنہیں Claude Code اپنے lifecycle کے مخصوص مراحل پر خود چلاتا ہے۔ hook اور rules file کے درمیان بنیادی فرق یہی ہے۔ CLAUDE.md میں موجود instruction ایک ہدایت ہے، اور model اسے اپنے context میں موجود باقی تمام چیزوں کے ساتھ ملا کر اہمیت دیتا ہے۔ hook code ہوتا ہے، اور model کے متفق ہونے یا نہ ہونے سے قطع نظر چلتا ہے۔ اگر آپ کا agent اس formatter کو بار بار skip کرتا ہے جس کے بارے میں آپ اسے 2 مرتبہ بتا چکے ہیں، تو آپ کو زیادہ سخت instruction کی ضرورت نہیں۔ آپ کو hook کی ضرورت ہے۔

یہ mechanism مختصر ہے۔ آپ settings file میں کسی event name کے تحت ایک command register کرتے ہیں۔ جب وہ event fire ہوتا ہے، تو Claude Code آپ کی command چلاتا ہے اور event data کو JSON (JavaScript object notation) کی صورت میں اس کے standard input (stdin) پر لکھتا ہے۔ آپ کی command یہ data پڑھتی ہے، اپنا کام کرتی ہے، اور exit status کے ذریعے جواب دیتی ہے۔ PreToolUse hook سے exit 2 tool call کو چلنے سے پہلے cancel کر دیتا ہے، اور آپ کی script نے standard error (stderr) پر جو کچھ لکھا ہو، اسے model کو وجہ کے طور پر واپس بھیج دیا جاتا ہے۔

یہاں event names اور field names Claude Code hooks reference سے لیے گئے ہیں۔ ان کی August 2026 میں release 2.1.232 کے مطابق تصدیق کی گئی تھی۔ یہ surface تیزی سے تبدیل ہوتا رہتا ہے، اس لیے کسی بھی blog post، بشمول اس تحریر، سے JSON copy کرنے سے پہلے اپنے version کی reference دیکھیں۔ اپنا version claude --version سے print کریں۔

ہُک کی configuration کہاں موجود ہوتی ہے

ہُک settings file میں موجود JSON block ہوتا ہے۔ چھ مقامات میں ہُک رکھا جا سکتا ہے، اور file کا scope ہی ہُک کا scope ہوتا ہے۔

  • ~/.claude/settings.json: آپ کی machine کے ہر project کے لیے، اور کسی دوسرے صارف کے لیے نہیں۔
  • .claude/settings.json: ایک project کے لیے، repository میں commit ہوتا ہے، اس لیے اسے clone کرنے والے ہر شخص کو یہ ہُک مل جاتا ہے۔
  • .claude/settings.local.json: ایک project کے لیے، صرف آپ کی machine پر۔
  • Managed policy settings: پوری organisation کے لیے، administrator کے ذریعے مقرر کردہ۔
  • hooks/hooks.json plugin کے اندر: plugin کے enabled رہنے تک فعال۔
  • Skill یا subagent frontmatter: اس component کے فعال رہنے تک موجود۔

ان files میں موجود ہُک entries ایک دوسرے کو override کرنے کے بجائے merge ہوتی ہیں۔ Project settings file اپنے ہُکس user settings میں موجود ہُکس کے ساتھ شامل کرتی ہے، انہیں replace نہیں کرتی۔ اس لیے ایک event میں کئی files کے کئی ہُکس ہو سکتے ہیں۔ "disableAllHooks": true مقرر کرنے سے یہ ہُکس بند ہو جاتے ہیں۔ ایک استثنا ہے: Managed policy settings کے ہُکس چلتے رہتے ہیں، جب تک یہی setting managed settings میں بھی لاگو نہ کی جائے۔

کسی session کے اندر /hooks چلائیں تاکہ اس وقت registered ہر ہُک کی فہرست event کے لحاظ سے grouped شکل میں دیکھ سکیں۔ ہر ہُک کے لیے source file اور matcher بھی دکھایا جاتا ہے۔ یہ menu صرف read-only ہے، اس لیے ہُک تبدیل کرنے کے لیے settings file میں ترمیم کریں۔ File watcher عموماً restart کے بغیر ترمیم کا پتا لگا لیتا ہے۔

Claude Code میں کون سے hook events موجود ہیں

Release 2.1.232 میں اکتیس events درج ہیں، جو SessionStart سے SessionEnd تک پھیلے ہوئے ہیں۔ ان میں compaction، subagents، worktrees اور configuration files شامل ہیں۔ سرور کے کام میں ان میں سے چند events استعمال ہوتے ہیں۔

  • PreToolUse: tool call کے اجرا سے پہلے۔ یہی وہ event ہے جو اجرا کو روک سکتا ہے۔
  • PostToolUse: tool call کامیاب ہونے کے بعد۔ ناکامی کی صورت میں PostToolUseFailure چلتا ہے، اس لیے ہر نتیجے کو دیکھنے والے hook کو دونوں events درکار ہوتے ہیں۔
  • PermissionRequest: جب tool call کے لیے permission decision درکار ہو۔ اسی وقت approval prompt ظاہر ہوتا ہے۔
  • UserPromptSubmit: prompt submit کرنے کے وقت، Claude کے اسے process کرنے سے پہلے۔ یہ hook stdout پر جو بھی لکھتا ہے، وہ model کے context میں شامل کر دیا جاتا ہے۔
  • SessionStart اور SessionEnd: session کے ہر اختتام پر۔ SessionStart compaction کے بعد بھی چلتا ہے، اور اس وقت matcher value compact ہوتی ہے۔
  • Stop: جب Claude جواب دینا مکمل کرتا ہے۔ یہ ہر turn میں ایک بار چلتا ہے، ہر مکمل task میں ایک بار نہیں۔

ہر group میں matcher شامل ہوتا ہے، جو طے کرتا ہے کہ کن occurrences پر hook چلایا جائے۔ tool events میں یہ tool name کے مطابق filter کرتا ہے، اس لیے "Edit|Write" صرف file edits پر چلتا ہے، کسی اور چیز پر نہیں۔ Matchers case sensitive ہوتے ہیں۔ خالی matcher ہر occurrence پر چلتا ہے۔ MCP (model context protocol) server کے tools کے نام mcp__<server>__<tool> کی صورت میں ہوتے ہیں، اس لیے "mcp__github__.*" کا matcher صرف ایک server کے tools کو پکڑتا ہے اور باقی کو الگ رکھتا ہے۔

Stop hooks میں ایک اہم trap ہے جسے hook لکھنے سے پہلے سمجھنا ضروری ہے۔ ایسا Stop hook جو block کرے، model کو دوبارہ کام کرنے کے لیے بھیج دیتا ہے، اور Claude Code مسلسل آٹھ blocks کے بعد hook کو override کر دیتا ہے۔ hook input سے stop_hook_active field پڑھیں اور اس کی قدر true ہونے پر exit 0 کریں، ورنہ hook اس حد تک پہنچنے تک loop کرتا رہے گا۔

stdin پر hook کو موصول ہونے والا ڈیٹا

جب Claude، npm test چلانے والا ہو تو Bash پر موجود PreToolUse hook، stdin سے یہ ڈیٹا پڑھتا ہے:

{
  "session_id": "abc123",
  "cwd": "/home/deploy/myproject",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

ہر event میں session_id، cwd، permission_mode، transcript_path اور hook_event_name شامل ہوتے ہیں۔ tool events میں tool_name، tool_input اور tool_use_id بھی شامل ہوتے ہیں۔ دیگر events میں اپنے مخصوص fields ہوتے ہیں: UserPromptSubmit میں prompt کا متن ملتا ہے، جبکہ SessionStart میں startup، resume، clear، compact یا fork کی source ملتی ہے۔

shell script کے اندر اسے پڑھنے کا معمول کا طریقہ jq ہے، اور minimal server image میں یہ موجود نہیں ہوتا۔ Ubuntu اور Debian پر اسے پہلے sudo apt install -y jq کے ذریعے install کریں۔

ٹول کال کے دوران exit status اس پر کیا اثر ڈالتا ہے

اس کے 3 نتائج ہوتے ہیں۔

  • Exit 0 کا مطلب ہے کہ آپ کے hook نے کوئی اعتراض نہیں کیا۔ PreToolUse میں یہ منظوری کے برابر نہیں ہوتا، اور معمول کا permission flow پھر بھی چلتا ہے۔ UserPromptSubmit اور SessionStart میں stdout کو model کے context میں شامل کر دیا جاتا ہے۔
  • Exit 2 ان events پر action کو روک دیتا ہے جنہیں روکا جا سکتا ہے، جن میں PreToolUse بھی شامل ہے، اور stderr وہ وجہ بن جاتا ہے جو model کو دکھائی جاتی ہے۔ جن events کو روکا نہیں جا سکتا، جیسے PostToolUse، ان میں block نظرانداز کر دیا جاتا ہے، لیکن stderr پھر بھی feedback کے طور پر model تک پہنچتا ہے۔
  • کوئی بھی دوسرا exit code non-blocking error ہوتا ہے۔ Action جاری رہتا ہے۔ Transcript میں hook error کا notice دکھایا جاتا ہے، جس میں Failed with non-blocking status code: کے بعد stderr کی پہلی سطر شامل ہوتی ہے۔

اگر block کرنے یا خاموش رہنے کے علاوہ کوئی اور رویہ درکار ہو تو exit 0 دیں اور stdout پر JSON object print کریں۔ PreToolUse hook فیصلہ permissionDecision کے ذریعے کرتا ہے:

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

"allow" interactive prompt کو چھوڑ دیتا ہے، "deny" call منسوخ کر کے وجہ model کو بھیجتا ہے، اور "ask" prompt کو معمول کے مطابق دکھاتا ہے۔ ہر hook کے لیے ایک ہی style منتخب کریں۔ stdout پر JSON decision کے ساتھ exit 2 ملانے سے ایسا نتیجہ پیدا ہوتا ہے جس کی تشریح آپ کو خود تلاش کرنی پڑتی ہے۔

جب ایک event سے متعدد hooks match ہوں تو وہ parallel میں چلتے ہیں، اور ہر hook completion تک چلتا ہے۔ ایک hook کا deny اس کے sibling hooks کو نہیں روکتا؛ اس لیے logging hook اپنی line لکھتا رہتا ہے جبکہ guardrail hook اسی call کو deny کر دیتا ہے۔ اس کے بعد Claude Code جوابات merge کرتا ہے اور زیادہ restrictive جواب برقرار رکھتا ہے۔ ترتیب یہ ہے: deny، defer، ask، allow۔

مثال 1: تباہ کن command کو چلنے سے پہلے روکیں

اسے اپنے project میں .claude/hooks/block-destructive.sh کے نام سے محفوظ کریں:

#!/bin/bash
# Deny a Bash tool call whose command matches a banned pattern.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

for pattern in 'rm -rf /' 'mkfs' 'dd if=' 'DROP TABLE'; do
  if printf '%s' "$COMMAND" | grep -qiF -- "$pattern"; then
    echo "Blocked by policy: the command matches '$pattern'. A human runs this one." >&2
    exit 2
  fi
done

exit 0

اسے executable بنائیں، پھر .claude/settings.json میں PreToolUse پر register کریں:

chmod +x .claude/hooks/block-destructive.sh
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive.sh",
            "timeout": 10,
            "statusMessage": "Checking the command against policy"
          }
        ]
      }
    ]
  }
}

اس script پر اعتماد کرنے سے پہلے اسے دستی طور پر test کریں، کیونکہ جو hook اپنے input پر crash ہو جائے وہ fail open ہوتا ہے:

echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /var/lib/postgresql"}}' \
  | .claude/hooks/block-destructive.sh
echo $?

آپ کو stderr پر Blocked by policy: لائن اور 2 کا exit code نظر آنا چاہیے۔ اسے ls -la جیسی بے ضرر command دیں؛ آپ کو کوئی output نہیں ملنا چاہیے اور exit code 0 ہونا چاہیے۔ session میں denied call transcript میں آپ کے message کو وجہ کے طور پر دکھاتی ہے، اور model وہ message پڑھ کر اپنا طریقہ اختیار کرتا ہے۔

اس کی افادیت کی ایک اہم وجہ یہ ہے: PreToolUse hooks ہر permission mode میں permission-mode check سے پہلے fire ہوتے ہیں، اس لیے bypassPermissions کے تحت بھی deny برقرار رہتا ہے۔ یہی وجہ ہے کہ hook Claude Code کے auto mode اور اس کی permission settings کے ساتھ مفید ہے، جہاں prompts کم کر دیے جاتے ہیں لیکن hook پھر بھی fire ہوتا ہے۔

اس کی حدود کے بارے میں واضح رہیں۔ command string پر pattern matching کسی agent کی لاپروائی کے خلاف guardrail ہے۔ یہ کسی سمجھ دار agent کے خلاف boundary نہیں ہے، کیونکہ وہی command ایسی شکل میں لکھی جا سکتی ہے جسے آپ کا grep کبھی دیکھ ہی نہ سکے۔ سخت قواعد permission system اور اس account میں نافذ ہونے چاہییں جس کے تحت process چلتا ہے۔

مثال 2: ہر ترمیم کے بعد format اور lint چلائیں

PostToolUse، Edit|Write matcher کے ساتھ، فائل میں ترمیم کرنے والے ہر tool کے بعد چلتا ہے۔ اسے .claude/hooks/after-edit.sh کے طور پر محفوظ کریں:

#!/bin/bash
# Format the edited file, then report lint failures back to the model.
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0

case "$FILE" in
  *.py)
    ruff format "$FILE" >/dev/null 2>&1
    if ! ruff check "$FILE" >&2; then
      exit 2
    fi
    ;;
  *.sh)
    if ! shellcheck "$FILE" >&2; then
      exit 2
    fi
    ;;
esac

exit 0
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/after-edit.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

Claude سے کہیں کہ Python فائل میں غلط indentation والا function شامل کرے، پھر فائل کھولیں۔ فائل formatted حالت میں نظر آئے گی۔ اس سے تصدیق ہوتی ہے کہ hook چلا، کیونکہ کامیاب hook گفتگو میں کچھ ظاہر نہیں کرتا۔

یہاں exit 2 کسی چیز کو undo نہیں کرتا۔ PostToolUse اس وقت چلتا ہے جب tool پہلے ہی execute ہو چکا ہوتا ہے، اس لیے edit ہر صورت disk پر محفوظ رہتی ہے۔ exit 2 کا فائدہ یہ ہے کہ ruff check کا output model کو feedback کے طور پر مل جاتا ہے۔ اس طرح وہ ابھی پیدا کی گئی error کو درست کرتا ہے اور آگے بڑھنے کے بجائے اسی turn میں مسئلہ حل کر دیتا ہے۔ یہی فرق ہے اس lint failure میں جو آپ commit کے وقت دریافت کرتے ہیں، اور اس lint failure میں جسے agent اسی turn میں درست کر دیتا ہے۔

یہاں matcher کی 2 حدود اہم ہیں۔ Edit|Write کو shell command سے تبدیل ہونے والی فائلیں نظر نہیں آتیں، اور Claude اکثر Bash کے ذریعے فائلیں لکھتا ہے، اس لیے یہ خلا حقیقی اہمیت رکھتا ہے۔ ہر call کی coverage کے لیے Bash کو بھی match کریں اور script سے git status --porcelain کے ذریعے تبدیل شدہ فائلوں کی فہرست بنوائیں۔ ہر turn میں ایک بار coverage کے لیے scan کو Stop hook میں رکھیں۔

مثال 3: audit کے لیے ہر tool call کا log رکھیں

PostToolUse پر خالی matcher ہر tool پر فعال ہوتا ہے۔ ریکارڈ کو home directory کی فائل کے بجائے system journal میں بھیجنے سے یہ agent کے اپنے shell کی رسائی سے باہر رہتا ہے:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{time: now|todate, session: .session_id, cwd: .cwd, tool: .tool_name, input: .tool_input}' | logger -t claude-code -p local0.info"
          }
        ]
      }
    ]
  }
}

journalctl -t claude-code -o cat | tail -n 5 سے اسے دوبارہ پڑھیں۔ ہر tool call کے لیے ایک JSON line نظر آنی چاہیے، اور سب سے نئی line آخر میں ہونی چاہیے۔ اگر کچھ بھی ظاہر نہ ہو تو hook نہیں چلا۔ ذیل کا troubleshooting سیکشن اس صورتِ حال کا احاطہ کرتا ہے۔

PostToolUseFailure کے تحت یہی block شامل کریں تاکہ ناکام calls بھی capture ہوں، کیونکہ PostToolUse صرف کامیابی پر فعال ہوتا ہے، جبکہ ناکام command عموماً زیادہ اہم ہوتی ہے۔ logger استعمال کرنے کی وجہ ownership ہے، نہ کہ home directory کی فائل میں append کرنا: hook اسی user کے طور پر چلتا ہے جس کے طور پر agent کا shell چل رہا ہوتا ہے۔ اس لیے جس چیز میں وہ user append کر سکتا ہے، اسے truncate بھی کر سکتا ہے۔ journal کو systemd-journald اپنے account کے تحت لکھتا ہے۔

ہک کتنی دیر تک چل سکتا ہے

ChartDefault hook timeout in seconds, by hook type and event
The data behind this chart
[
  {
    "label": "command, http or mcp_tool hook",
    "default_timeout_seconds": 600
  },
  {
    "label": "agent hook",
    "default_timeout_seconds": 60
  },
  {
    "label": "prompt hook",
    "default_timeout_seconds": 30
  },
  {
    "label": "command hook on UserPromptSubmit",
    "default_timeout_seconds": 30
  },
  {
    "label": "command hook on MessageDisplay",
    "default_timeout_seconds": 10
  },
  {
    "label": "any hook on SessionEnd",
    "default_timeout_seconds": 1.5
  }
]

پہلے سے طے شدہ طور پر command hook کو 600 سیکنڈ ملتے ہیں، یعنی 10 منٹ۔ بعض events اس مدت کو بہت کم کر دیتے ہیں۔ SessionEnd hooks کے درمیان مجموعی طور پر 1.5 سیکنڈ کا مشترکہ budget ہوتا ہے، اس لیے session کے اختتام پر ہونے والی cleanup تیزی سے مکمل ہونی چاہیے۔ تاہم hook پر طویل timeout مقرر کرنے سے یہ مشترکہ budget بھی اسی کے مطابق بڑھ جاتا ہے، زیادہ سے زیادہ 60 سیکنڈ تک۔

جو hook اپنے timeout تک پہنچ جائے، اسے cancel کر دیا جاتا ہے اور وہ کوئی فیصلہ جاری نہیں کرتا۔ PreToolUse guardrail کے لیے اس کا مطلب یہ ہے کہ وہ عمل کو block نہیں کرتا؛ tool call معمول کے permission flow میں آگے بڑھ جاتی ہے۔ اسی وجہ سے guardrail scripts مختصر رکھیں۔ ایسی سست کارروائی کے لیے جس کا کوئی انتظار نہیں کر رہا، مثلاً log کو کسی دوسری جگہ بھیجنے کے لیے، "async": true مقرر کریں۔ اس صورت میں hook background میں چلتا ہے اور tool call کو روکتا نہیں۔

Hooks، rules files، skills اور MCP servers

یہ چاروں چیزیں ایک دوسرے سے اس لیے خلط ملط ہو جاتی ہیں کہ یہ سب agent کے طرزِ عمل کو تبدیل کرتی ہیں۔ ان میں صرف ایک چیز suggestion نہیں رہتی۔

rules file (CLAUDE.md، یا .claude/rules/ کے اندر موجود file) وہ متن ہے جو model کے context میں load ہوتا ہے۔ یہ طرزِ عمل کو shape کرتی ہے، لیکن کسی چیز کو enforce نہیں کرتی۔ ایک طویل conversation، بڑے diff اور user کی نئی request کے مقابلے میں اس کی ایک سطر نظرانداز ہو سکتی ہے۔ یہی عام طریقہ ہے جس کے باعث agents آپ کی لکھی ہوئی instructions کو نظرانداز کرتے ہیں۔

skill، instructions اور scripts کا ایک folder ہوتی ہے جسے model اس وقت load کرتا ہے جب اسے skill متعلقہ معلوم ہو۔ یہی فیصلہ skill کا بنیادی مقصد ہے، اور یہی اس کی حد بھی ہے: فیصلہ پھر بھی model ہی کرتا ہے۔ اس کی دونوں جہتیں Ponytail، جو agent کو کام کرنے والی کم سے کم تبدیلی کی طرف لے جاتا ہے جیسی skill میں نظر آتی ہیں، کیونکہ یہ پورے task سے نمٹنے کے طریقے کو اس طرح shape کرتی ہے جو کوئی hook نہیں کر سکتا، اور صرف اسی وقت فعال ہوتی ہے جب model اسے load کرنے کا انتخاب کرے۔

MCP (model context protocol) server، model کو call کرنے کے لیے نئے tools فراہم کرتا ہے۔ اس سے agent کی رسائی کا دائرہ وسیع ہوتا ہے۔ لیکن یہ agent کو کسی چیز تک رسائی حاصل کرنے پر مجبور نہیں کرتا۔ مزید یہ کہ یہ ایک الگ process ہے جسے آپ کو operate کرنا پڑتا ہے، اور یہ خود ایک الگ کام ہے: VPS پر MCP servers چلانے کا طریقہ دیکھیں۔

ان چاروں میں صرف hook ایسا ہے جو model کے انتخاب کے بغیر run ہوتا ہے۔ preference کے لیے rules file استعمال کریں، اور اس procedure کے لیے skill استعمال کریں جس پر model کو متعلقہ ہونے کی صورت میں عمل کرنا چاہیے۔ اس step کے لیے hook استعمال کریں جو ہر بار لازماً ہونا چاہیے، یا اس کام کے لیے جو کبھی نہیں ہونا چاہیے۔ مزید تفصیلی موازنہ، جس میں یہ بھی شامل ہے کہ skill کب rules file سے بہتر ہوتی ہے، skills، MCP اور rules files کا موازنہ میں موجود ہے۔

plugin پانچواں mechanism نہیں بلکہ packaging ہے۔ یہ hooks کو skills کے ساتھ ایک installable unit میں bundle کرتا ہے۔ اسی طریقے سے team ہر machine پر ایک ہی guardrail ship کرتی ہے: Claude Code plugins کیسے کام کرتے ہیں دیکھیں۔

مشترکہ VPS پر سکیورٹی کا فیصلہ

Hook وہ code ہے جسے agent trigger کرتا ہے، اور یہ اسی user کے طور پر چلتا ہے جس نے Claude Code شروع کیا تھا۔ اسے اس user کا environment اور file permissions وراثت میں ملتے ہیں۔ Laptop پر یہ workflow کا معاملہ ہے۔ VPS پر، جہاں agent unattended چلتا ہے، یہ سکیورٹی کا معاملہ ہے جس کے چار عملی پہلو ہیں۔

Repository میں موجود hook ایسا code ہے جو آپ نے خود نہیں لکھا۔ .claude/settings.json committed ہے، اس لیے repository کو clone کرکے اس کے اندر session شروع کرنے سے repository کے ساتھ آنے والے hooks register ہو سکتے ہیں۔ Claude Code اس folder کے لیے workspace trust dialog کے پیچھے project hooks کو محدود رکھتا ہے۔ اس کا مطلب ہے کہ trust قبول کرنا ہی وہ لمحہ ہے جب آپ انہیں چلانے کا فیصلہ کرتے ہیں۔ پہلے hooks block پڑھیں۔

Hook کو tool input مکمل طور پر دکھائی دیتا ہے۔ ایسا audit hook جو tool_input log کرتا ہے، ہر command کی ہر argument کو file میں لکھ دیتا ہے، اس میں وہ token بھی شامل ہو سکتا ہے جو اتفاقاً command line پر موجود تھا۔ اس log کو پھر secret جیسی ہی protection درکار ہوتی ہے۔ یہ اس وسیع تر مسئلے کا حصہ ہے کہ secrets کو AI agent کی رسائی سے دور رکھنا ضروری ہے۔

Hook model کے context میں لکھ سکتا ہے۔ SessionStart یا UserPromptSubmit hook stdout پر جو بھی print کرتا ہے، وہ conversation میں شامل ہو جاتا ہے۔ ایسا hook جو کسی بیرونی source، issue tracker یا log file سے text pipe کرتا ہے، untrusted text کو model کے حوالے کر رہا ہوتا ہے، جیسے آپ نے اسے خود type کیا ہو۔ ایسا hook جو اسی VPS پر موجود کسی دوسرے Claude Code session سے note forward کرتا ہے، وہ بھی یہی کر رہا ہے، اور ایک agent کے output کو issue tracker کے output سے زیادہ قابلِ اعتماد نہیں سمجھا جا سکتا۔ اس stdout کو output نہیں بلکہ input سمجھیں۔

اصل control privilege ہے۔ Agent کو ایک dedicated unprivileged user کے طور پر چلائیں، جس کے پاس صرف مطلوبہ sudo rules ہوں۔ PreToolUse deny رکھنا مفید ہے، اور design کے لحاظ سے یہ best effort ہے۔ Reference بھی if filter کے بارے میں یہی کہتا ہے اور ہدایت دیتا ہے کہ hard deny درکار ہو تو permission system استعمال کریں۔ Permission rules اور وہ account جس کے تحت process چلتا ہے، دباؤ کی صورت میں بھی مؤثر رہنے والے اجزا ہیں۔

ایک خاصیت ہر configuration میں برقرار رہتی ہے۔ PreToolUse hooks ہر permission mode میں permission-mode check سے پہلے fire ہوتے ہیں، اس لیے deny واپس کرنے والا hook، bypassPermissions کے تحت بھی tool کو block کر دیتا ہے۔ Hooks ان چیزوں پر مزید پابندی لگا سکتے ہیں جن کی اجازت permission rules دیتے ہیں۔ وہ ان پابندیوں کو کم نہیں کر سکتے۔

میرا hook کیوں فعال نہیں ہو رہا؟

اسے اسی ترتیب سے دیکھیں۔ ہر مرحلے میں وہ علامت بیان کی گئی ہے جو آپ کو حقیقت میں نظر آئے گی۔

  • /hooks چلائیں اور دیکھیں کہ hook متوقع event کے تحت موجود ہے۔ اگر hook menu میں موجود نہ ہو تو عموماً settings file میں JSON syntax error ہوتا ہے، کیونکہ trailing commas اور comments کی اجازت نہیں ہے، یا file اوپر بیان کردہ چھ مقامات میں سے کسی ایک میں موجود نہیں ہے۔
  • Matcher کا tool name سے عین مطابق موازنہ کریں۔ Matchers case-sensitive ہوتے ہیں، اس لیے "bash" کبھی بھی Bash tool سے match نہیں کرتا۔
  • اوپر دی گئی example 1 کے مطابق sample input کے ساتھ script کو دستی طور پر چلائیں۔ اگر exit code آپ کی توقع کے مطابق نہ ہو تو script میں bug ہے۔ Claude Code اسے decision کے بجائے hook error کے طور پر report کرتا ہے۔
  • jq: command not found کا notice ظاہر ہونے کا مطلب ہے کہ اس machine پر jq موجود نہیں ہے۔ اپنی script کے لیے command not found کا مطلب ہے کہ path resolve نہیں ہوا، اس لیے ${CLAUDE_PROJECT_DIR} یا absolute path استعمال کریں۔ اگر script بالکل run نہ ہو تو غالباً وہ executable نہیں ہے۔
  • Hook valid JSON print کرتا ہے، لیکن کچھ نہیں ہوتا۔ Shell-form hook sh -c کے ذریعے چلتا ہے۔ اگر آپ کا shell profile banner print کرتا ہے تو وہ banner آپ کے JSON کے شروع میں شامل ہو جاتا ہے۔ اس طرح stdout اب { سے شروع نہیں ہوتا، لہٰذا Claude Code پوری output کو plain text سمجھ کر decision نظرانداز کر دیتا ہے۔ Exit 0 پر debug log کے علاوہ کہیں بھی کچھ report نہیں ہوتا۔ اپنے profile میں موجود کسی بھی echo کو اس طرح wrap کریں کہ وہ صرف interactive shells میں چلے۔
  • اگر مسئلہ برقرار رہے تو session کو claude --debug-file /tmp/claude.log کے ساتھ شروع کریں اور دوسرے terminal میں tail -f /tmp/claude.log چلائیں۔ Debug log میں درج ہوتا ہے کہ کون سے hooks match ہوئے، ہر hook نے کون سا exit code واپس کیا، اور انہوں نے stdout اور stderr پر کیا لکھا۔

FAQ

Claude Code hook اور CLAUDE.md instruction میں کیا فرق ہے؟

CLAUDE.md instruction ماڈل کے context میں موجود متن ہوتی ہے، اس لیے یہ conversation اور موجودہ request کے ساتھ توجہ حاصل کرنے کے لیے مقابلہ کرتی ہے، اور ماڈل ان سب کے مقابلے میں اس کی اہمیت کا تعین کر سکتا ہے۔ hook ایک shell command ہوتی ہے جسے Claude Code اپنے lifecycle کے مقررہ مرحلے پر چلاتا ہے، اس لیے یہ اپنے event کے ہر وقوع پر، ماڈل کے فیصلے سے قطع نظر، execute ہوتی ہے۔ ترجیح کے لیے instruction استعمال کریں۔ ایسے مرحلے کے لیے hook استعمال کریں جو ہمیشہ ہونا چاہیے یا ایسی کارروائی کے لیے جو کبھی نہیں ہونی چاہیے۔

Claude Code کو مخصوص shell command چلانے سے کیسے روکوں؟

PreToolUse hook رجسٹر کریں جس میں Bash matcher ہو۔ یہ matcher command کو .tool_input.command سے پڑھتا ہے، stderr میں وجہ لکھتا ہے اور 2 پر exit کرتا ہے۔ Claude Code call منسوخ کر دیتا ہے اور ماڈل کو آپ کی وجہ دکھاتا ہے۔ یہ permission-mode check سے پہلے ہوتا ہے، اس لیے deny، bypassPermissions mode میں بھی برقرار رہتا ہے۔ Command string پر pattern matching ایک guardrail ہے، security boundary نہیں، کیونکہ وہی command ایسی شکل میں لکھی جا سکتی ہے جسے pattern شناخت نہ کر سکے۔ اس لیے اسے permission rules اور unprivileged account کے ساتھ استعمال کریں۔

میرا hook درست JSON print کرتا ہے، لیکن کچھ نہیں ہوتا۔ کیوں؟

سب سے عام وجہ آپ کا shell profile ہے۔ args field کے بغیر hook، sh -c کے ذریعے چلتا ہے۔ کچھ profiles ہر shell میں banner print کرتے ہیں، جو آپ کے JSON سے پہلے stdout پر آ جاتا ہے۔ چونکہ output اب { سے شروع نہیں ہوتا، Claude Code اسے مکمل طور پر plain text سمجھتا ہے اور decision کو نظرانداز کر دیتا ہے۔ exit 0 ہونے پر transcript میں کچھ بھی رپورٹ نہیں ہوتا۔ اپنے profile میں موجود ہر echo کو interactive-shell test کے اندر رکھیں۔ پھر claude --debug-file /tmp/claude.log سے debug log پڑھ کر fix کی تصدیق کریں۔

کیا shared server پر Claude Code hooks چلانا محفوظ ہے؟

Hooks اسی user کے طور پر چلتے ہیں جس نے Claude Code شروع کیا ہو۔ انہیں اس user کی file permissions حاصل ہوتی ہیں، اس لیے hook وہ سب کچھ کر سکتا ہے جو وہ account کر سکتا ہے۔ زیادہ تر خطرات کم کرنے کے لیے دو عادات کافی ہیں: agent کو narrow sudo policy والے dedicated unprivileged account کے طور پر چلائیں، اور workspace trust dialog قبول کرنے سے پہلے ہر repository کا hooks block پڑھیں، کیونکہ project hooks .claude/settings.json کے اندر شامل ہوتے ہیں۔ جب آپ چاہتے ہوں کہ ان میں سے کوئی بھی hook نہ چلے تو اپنی settings file میں "disableAllHooks": true مقرر کریں۔