Claude Code hooks: एजेंट के नियमों को गारंटी में कैसे बदलें
Claude Code hooks वे shell कमांड हैं जिन्हें Claude Code खुद कुछ events पर चलाता है: टूल कॉल से पहले और बाद में, प्रॉम्प्ट भेजने पर, सत्र शुरू होने पर और एजेंट के रुकने से पहले। इन्हें settings.json में सेट किया जाता है, और exit code 2 कार्रवाई को रोकता है तथा एजेंट को त्रुटि संदेश लौटाता है, इसलिए hook CLAUDE.md के नियम को ऐसी गारंटी बना देता है जिसे model भूल नहीं सकता।
Claude Code hooks क्या हैं
Hook एक कमांड है, जिसे model नहीं, बल्कि Claude Code प्रोग्राम स्वयं एजेंट के काम के एक तय क्षण पर चलाता है। एजेंट rm -rf चलाने वाला है — hook चलता है और तय करता है कि कमांड जाने दी जाए या नहीं। एजेंट ने कोई फ़ाइल बदली — hook formatter चलाता है। एजेंट काम खत्म करना चाहता है — hook टेस्ट चलाता है और टेस्ट लाल हों तो रुकने नहीं देता।
CLAUDE.md के निर्देशों से मुख्य अंतर यह है: निर्देश एक टेक्स्ट है, जिसे model कुछ संभावना के साथ ध्यान में रखता है, जबकि hook एक निर्धारित (deterministic) कोड है। वह हर बार चलेगा, चाहे context कितना भी लंबा हो, एजेंट ने कुछ भी «तय» किया हो, या सत्र के सौवें मिनट में वह नियम भूल गया हो। इसीलिए claude code hooks इच्छा को गारंटी में बदलने का मुख्य तरीका हैं।
CLAUDE.md में नियम क्यों काफ़ी नहीं हैं
एक सामान्य तस्वीर: एजेंट ने फ़ीचर जोड़ दिया, unit tests हरे हैं, और चैट में आत्मविश्वास से लिखा है «हो गया»। पर end-to-end परिदृश्य किसी ने नहीं चलाया, linter नहीं चला, और migration बीच में गिर गई। CLAUDE.md में लिखा है «समाप्त करने से पहले टेस्ट चलाओ»। model जान-बूझकर धोखा नहीं दे रहा — वह बस खुद अपना मूल्यांकन करता है, और वह आत्म-मूल्यांकन भरोसेमंद नहीं है।
यहीं से हार्नेस का सिद्धांत निकलता है: जो चीज़ मशीनी तरीके से जाँची जा सकती है, उसे मशीनरी जाँचे, model नहीं। «linter चलाओ» नियम एक hook बन जाता है जो linter चलाता है। «production config मत छुओ» नियम एक hook बन जाता है जो उस फ़ाइल में लिखना रोकता है। टेक्स्ट वाला निर्देश बना रहता है, पर अब वह केवल यह समझाता है कि एजेंट को क्यों रोका गया।
Events: hooks कब चलते हैं
हर hook सत्र के जीवन-चक्र के किसी event से जुड़ा होता है। मुख्य events:
| Event | कब चलता है | सामान्य उपयोग |
|---|---|---|
PreToolUse | टूल कॉल से पहले (Bash, Edit, Write आदि) | खतरनाक कमांड पर रोक, फ़ाइलों की सुरक्षा |
PostToolUse | टूल कॉल सफल होने के बाद | formatter, linter, संपादन के बाद तेज़ टेस्ट |
UserPromptSubmit | उपयोगकर्ता ने प्रॉम्प्ट भेजा, प्रोसेस होने से पहले | संदर्भ जोड़ना, सीक्रेट वाला प्रॉम्प्ट रोकना |
Stop | जब एजेंट जवाब खत्म करने वाला हो | जाँच पूरी होने तक रुकने न देना |
SubagentStop | जब subagent काम खत्म करे | subagent के परिणाम की जाँच |
SessionStart | सत्र शुरू या फिर से शुरू होने पर | स्थिति लोड करना: git status, योजना, प्रगति का लॉग |
SessionEnd | सत्र खत्म होने पर | सफ़ाई, लॉग लिखना |
Notification | जब Claude Code सूचना भेजे (जैसे अनुमति का इंतज़ार) | आवाज़ या फ़ोन पर संदेश |
PreCompact | context संक्षिप्त करने से पहले | कॉम्पैक्शन से पहले ज़रूरी बातें सहेजना |
विश्वसनीयता के लिए सबसे उपयोगी हैं PreToolUse, PostToolUse और Stop: ये एजेंट की तीन कमज़ोर जगहों को ढकते हैं — खतरनाक कार्य, बिना जाँचे संपादन और समय से पहले «हो गया»।
Hooks कहाँ सेट होते हैं: settings.json
Hooks Claude Code की settings फ़ाइलों में लिखे जाते हैं:
~/.claude/settings.json— आपकी निजी सेटिंग्स, सभी प्रोजेक्ट्स के लिए;- रिपॉज़िटरी में
.claude/settings.json— पूरी टीम के लिए, git में commit होती है; .claude/settings.local.json— केवल इस प्रोजेक्ट के लिए स्थानीय, git में नहीं जाती।
ढाँचा इस तरह है: event → matcher वाले समूहों की सूची → कमांड की सूची।
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/lint.sh",
"timeout": 60
}
]
}
]
}
}
matcher एक टूल का नाम है या उस पर regular expression: Bash, Edit|Write, .*। यह उन events के लिए ज़रूरी है जो टूल से जुड़े हैं; Stop या UserPromptSubmit के लिए इसे छोड़ा जा सकता है। चर CLAUDE_PROJECT_DIR प्रोजेक्ट की जड़ की ओर इशारा करता है, इसलिए scripts को .claude/hooks/ में रखकर इसी के ज़रिए बुलाना सुविधाजनक है। मौजूदा hooks देखने के लिए Claude Code के भीतर /hooks कमांड चलाएँ।
Hook डेटा कैसे लेता है और कैसे जवाब देता है
इनपुट: stdin में JSON
Claude Code hook को event की जानकारी JSON में देता है: session id, कार्य-डायरेक्टरी, event का नाम, और टूल के मामले में tool_name और tool_input। Bash के लिए tool_input.command में कमांड होती है, Edit और Write के लिए tool_input.file_path में फ़ाइल का पथ। इसे पढ़ना jq से आसान है।
{
"session_id": "abc123",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "rm -rf build/" },
"cwd": "/home/dev/project"
}
आउटपुट: return code
| Code | क्या होता है |
|---|---|
0 | सफलता, काम जारी; UserPromptSubmit और SessionStart में stdout एजेंट के संदर्भ में जुड़ता है |
2 | रोकने वाली त्रुटि: stderr एजेंट तक जाता है और कार्रवाई रुकती है (PreToolUse में टूल कॉल रद्द, Stop में एजेंट काम जारी रखता है) |
| अन्य | गैर-रोकने वाली त्रुटि: stderr उपयोगकर्ता को दिखता है, काम आगे चलता है |
Exit code 2 मुख्य तंत्र है। एजेंट stderr के टेक्स्ट को प्रतिक्रिया के रूप में पाता है और खुद सुधार सकता है: कमांड दोबारा लिखे, टेस्ट ठीक करे, या फ़ाइल न छुए। इसलिए stderr में केवल «त्रुटि» न लिखें, बल्कि साफ़ कारण लिखें और यह भी कि इसके बजाय क्या करना है।
Return code के अलावा hook stdout में JSON भेजकर फ़ैसला बता सकता है, जैसे "decision": "block" और "reason"। यह बारीक सेटिंग के लिए है, पर अधिकांश कामों के लिए code 2 और stderr का संदेश काफ़ी है।
उदाहरण 1: हर संपादन के बाद linter और टेस्ट
Edit|Write matcher वाला PostToolUse hook फ़ाइल बदलने के बाद चलता है। टूल पहले ही चल चुका है, संपादन वापस नहीं हो सकता, पर code 2 linter का आउटपुट एजेंट को लौटा देता है, और वह गलती तुरंत सुधार लेता है, आधे घंटे बाद नहीं।
#!/usr/bin/env bash
# .claude/hooks/lint.sh
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in
*.py)
if ! out=$(ruff check "$file" 2>&1); then
echo "Linter को $file में त्रुटियाँ मिलीं:" >&2
echo "$out" >&2
exit 2
fi ;;
*.php)
if ! out=$(php -l "$file" 2>&1); then
echo "$out" >&2
exit 2
fi ;;
esac
exit 0
यहाँ दस मिनट का पूरा टेस्ट-रन न डालें: hook हर संपादन पर चलता है। संपादन के बाद तेज़ जाँचें (syntax, linter, एक मॉड्यूल के टेस्ट) रखें, और भारी जाँचें Stop hook में।
उदाहरण 2: खतरनाक कमांड पर रोक
Bash matcher वाला PreToolUse hook कमांड को चलने से पहले देखता है। Code 2 टूल कॉल रद्द करता है, और एजेंट को कारण मिलता है।
#!/usr/bin/env bash
# .claude/hooks/guard.sh
cmd=$(jq -r '.tool_input.command // empty')
if echo "$cmd" | grep -Eq 'rm -rf /|git push (-f|--force)|DROP (TABLE|DATABASE)'; then
echo "प्रोजेक्ट नीति के कारण कमांड रोकी गई: $cmd. खतरनाक कार्य इंसान करता है।" >&2
exit 2
fi
exit 0
फ़ाइलों की सुरक्षा भी इसी तरह होती है: Edit|Write पर hook file_path जाँचता है और .env, production config, generated code या लागू हो चुकी migration फ़ाइलों में लिखना रोकता है। ध्यान रहे कि regular expression से छूट संभव है — एजेंट कमांड अलग तरह से लिख सकता है। Hook मज़बूत सुरक्षा-जाल है, पर यह access rights और sandbox की जगह नहीं लेता।
उदाहरण 3: टेस्ट लाल हों तो एजेंट «हो गया» नहीं कह सकता
Stop hook तब चलता है जब एजेंट खत्म करने वाला हो। अगर टेस्ट फ़ेल हैं, तो code 2 उसे रुकने नहीं देता, और टेस्ट का आउटपुट अगले काम के रूप में एजेंट तक पहुँचता है। यह Definition of Done का मशीनी रूप है।
#!/usr/bin/env bash
# .claude/hooks/done.sh
input=$(cat)
# अनंत लूप से बचाव: hook एक बार काम बढ़ा चुका है
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0
fi
if ! out=$(npm test --silent 2>&1); then
echo "टेस्ट फ़ेल हुए, काम पूरा नहीं हुआ:" >&2
echo "$out" | tail -n 40 >&2
exit 2
fi
exit 0
Field stop_hook_active true होता है, अगर एजेंट stop-hook की वजह से पहले ही काम जारी रख रहा है। इस जाँच के बिना hook एजेंट को अनंत चक्कर में घुमा सकता है — वही स्थिति जब एजेंट «पूरी रात गलत दिशा में मेहनत» करता है। स्वचालित चक्रों में प्रयासों की सीमा और ऐसी शर्त भी जोड़ें जिस पर इंसान को बुलाया जाए।
उदाहरण 4: सत्र की शुरुआत में स्थिति लोड करना
SessionStart पर hook का stdout संदर्भ में जाता है। यहाँ वर्तमान ब्रांच, बिना commit के बदलाव और योजना से अगला कार्य दिखाना सुविधाजनक है, ताकि एजेंट शून्य से शुरू न करे।
#!/usr/bin/env bash
echo "ब्रांच: $(git branch --show-current)"
git status --short | head -n 20
[ -f PROGRESS.md ] && tail -n 15 PROGRESS.md
exit 0
Hooks नियमों को गारंटी कैसे बनाते हैं
हार्नेस के नियमों को तीन स्तरों में बाँटना उपयोगी है। पहला — CLAUDE.md का टेक्स्ट: समझाता है कि कैसे काम करना है, पर पालन संभावना पर टिका है। दूसरा — चलने योग्य जाँचें: टेस्ट, E2E, और आर्किटेक्चर के नियम जो उल्लंघन पर स्क्रिप्ट के रूप में विफल हों। तीसरा — hooks, जो ये जाँचें सही समय पर अपने-आप चलाते हैं, model की भागीदारी के बिना।
व्यावहारिक नियम: अगर एजेंट एक ही नियम दो बार तोड़ चुका है, तो CLAUDE.md में एक और बड़े अक्षरों वाली चेतावनी न जोड़ें। पूछें कि क्या यह उल्लंघन किसी स्क्रिप्ट से जाँचा जा सकता है। अगर हाँ, तो स्क्रिप्ट बनाकर उसे hook पर लगाएँ। तब टेक्स्ट वाला निर्देश छोटा हो जाता है और व्यवहार अधिक स्थिर।
| टेक्स्ट में नियम | Hook से गारंटी |
|---|---|
| «कोड मानक के अनुसार फ़ॉर्मेट करो» | PostToolUse बदली फ़ाइल पर formatter चलाता है |
| «force के साथ push मत करो» | Bash पर PreToolUse कमांड रोकता है, exit 2 |
| «टेस्ट लाल हों तो «हो गया» मत कहो» | Stop टेस्ट चलाता है और समाप्ति रोकता है |
| «.env मत बदलो» | Edit|Write पर PreToolUse पथ जाँचता है |
| «याद रखो कहाँ रुके थे» | SessionStart योजना और git status दिखाता है |
आम गलतियाँ
- Code 2 की जगह code 1। Script code 1 से गिरती है, एजेंट को कुछ पता नहीं चलता और कार्रवाई हो जाती है। केवल code 2 रोकता है।
- stdout में संदेश। Code 2 पर एजेंट को stderr जाता है।
>&2से लिखें। - बार-बार चलने वाले event पर धीमा hook। PostToolUse में पूरा टेस्ट-रन हर संपादन को एक मिनट का बना देता है। भारी काम
Stopमें रखें। - अनंत Stop।
stop_hook_activeकी जाँच या सीमा न होने पर एजेंट बिना अंत के घूमता है। - सापेक्ष पथ। Hook सत्र की कार्य-डायरेक्टरी में चलता है;
$CLAUDE_PROJECT_DIRका उपयोग करें। - बिना जाँचे किसी और का hook। Hook आपके अधिकारों से बिना पुष्टि के चलता है। किसी दूसरी रिपॉज़िटरी के scripts भरोसा करने से पहले पढ़ें।
निष्कर्ष
Claude Code hooks सबसे ज़रूरी नियमों को टेक्स्ट से कोड में ले जाते हैं: PreToolUse खतरनाक कार्य रोकता है, PostToolUse हर संपादन की जाँच करता है, Stop समय से पहले «तैयार» की रिपोर्ट नहीं देने देता, और SessionStart एजेंट को प्रोजेक्ट की स्मृति लौटाता है। शुरुआत उन दो-तीन नियमों पर hooks से करें जिन्हें एजेंट पहले तोड़ चुका है, और stderr के संदेश साफ़ रखें — तब एजेंट खुद गलतियाँ सुधारता है, और आप प्रक्रिया नहीं, परिणाम जाँचते हैं। सत्यापन, तैयारी की जाँच और ऑटोनॉमस चक्रों को हम कोर्स में व्यावहारिक रूप से समझाते हैं।
अक्सर पूछे जाने वाले प्रश्न
क्या hooks बदलने के बाद Claude Code को दोबारा शुरू करना पड़ता है?
settings.json के बदलाव नए सत्र में सबसे भरोसेमंद तरीके से लागू होते हैं; मौजूदा hook कॉन्फ़िगरेशन <code>/hooks</code> कमांड से देखा जा सकता है।
क्या hooks bash के अलावा किसी और भाषा में लिखे जा सकते हैं?
हाँ, hook कोई भी कमांड हो सकता है: Python, Node.js की script या binary। बस stdin से JSON पढ़ना और सही exit code लौटाना ज़रूरी है।
क्या एजेंट सफल hook का आउटपुट देखता है?
आम तौर पर नहीं: code 0 पर आउटपुट संदर्भ में नहीं जाता। अपवाद UserPromptSubmit और SessionStart हैं, जहाँ stdout एजेंट के संदर्भ में जुड़ता है।
क्या hooks subagents पर भी लागू होते हैं?
हाँ, subagents के टूल कॉल उन्हीं PreToolUse और PostToolUse hooks से गुज़रते हैं, और उनके समाप्त होने के लिए अलग event SubagentStop है।
क्या hooks को repository में commit करना सुरक्षित है?
हाँ, टीम के लिए <code>.claude/settings.json</code> और <code>.claude/hooks/</code> के scripts आम तरीका है; निजी hooks को settings.local.json या ~/.claude/settings.json में रखें।
यह विषय “Harness Engineering: инженерия надёжности AI-агентов” कोर्स में अभ्यास के साथ विस्तार से समझाया गया है।
- Агент написал «готово», а фича не работает: verification gap и Definition of Done
- Все тесты зелёные, а фича сломана: E2E-тесты и архитектурные правила для AI-агента
- Автономный AI-агент часами без присмотра: Loop Engineering и условия остановки