Claude Code'da Hooks: Olay Tabanlı Otomasyon Rehberi

Daha önce Agent Skills ve slash komutlarını, ardından Claude Code'un CLI ve print mode ile script/CI ortamlarında kullanımını ele almıştım. Skill'ler Claude'un kendi kararıyla ya da sizin çağırmanızla devreye giren, isteğe bağlı talimat setleriydi. Bu yazıda ele alacağım hooks ise tamamen farklı bir kontrol katmanı sunuyor: Claude'un "uygun görmesine" bırakılmayan, Claude Code'un yaşam döngüsündeki belirli noktalarda her seferinde otomatik çalışan kabuk komutlarıdır. Resmi code.claude.com/docs dokümantasyonunun tanımıyla, hook'lar "Claude Code'un davranışı üzerinde deterministik kontrol sağlar; belirli eylemlerin, LLM'in çalıştırmayı seçmesine güvenmek yerine her zaman gerçekleşmesini garanti eder."

Bu fark pratikte çok şey değiştiriyor. Bir skill'e "her commit'ten önce testleri çalıştır" yazabilirsiniz, ama Claude o talimatı atlayabilir ya da yanlış yorumlayabilir. Bir PreToolUse hook'u ise git commit komutunu fiilen engelleyip Claude'a neden engellendiğini bildirebilir -- kararı modele bırakmaz. Bu yazıda hook yapılandırma dosyalarını, tam olay (event) listesini, girdi/çıktı JSON şemasını, çıkış kodlarını, matcher ve if filtrelemesini, ve gerçek dünyadan örnekleri (tehlikeli komutları engelleme, otomatik formatlama, korumalı dosyalar, masaüstü bildirimleri, bağlam enjeksiyonu) inceleyeceğiz.

1. Hook Nedir, Nasıl Çalışır?

Bir hook, Claude Code'un yaşam döngüsündeki bir olay (event) tetiklendiğinde çalışan bir işleyicidir. En yaygın tür kabuk komutu çalıştıran command hook'udur, ancak dört tür daha vardır: bir URL'e POST isteği atan http, bağlı bir MCP sunucusundaki aracı çağıran mcp_tool, kararı tek seferlik bir LLM çağrısına bırakan prompt, ve araç erişimi olan bir subagent başlatan agent (deneysel). Bu yazıda ağırlıklı olarak en yaygın ve üretime en uygun tür olan command hook'larına odaklanacağız.

Hook'lar Claude Code'a üç kanaldan konuşur: stdin üzerinden olayla ilgili JSON verisini alırlar, stdout/stderr'e yazarlar ve bir exit code ile dönerler. Exit code, işlemin devam edip etmeyeceğini; stdout'a yazılan JSON ise Claude'a ek bağlam veya yapılandırılmış bir karar iletmek için kullanılır.

2. İlk Hook'unuzu Kurmak

Bir hook eklemek için bir ayarlar dosyasına hooks bloğu eklersiniz. Örneğin Claude her izin istediğinde masaüstü bildirimi almak isterseniz, ~/.claude/settings.json dosyasına şunu eklersiniz (Linux için notify-send kullanan sürüm):

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
          }
        ]
      }
    ]
  }
}

Ayarlar dosyanızda zaten bir hooks anahtarı varsa, mevcut olay anahtarlarının tamamını değiştirmek yerine Notification'ı bir kardeş anahtar olarak ekleyin -- her olay adı, tek bir hooks nesnesi içindeki ayrı bir anahtırdır. Kaydettikten sonra terminalde /hooks yazarak hook tarayıcısını açabilir, olayın altında hook'unuzun göründüğünü doğrulayabilirsiniz. /hooks menüsü salt okunurdur; eklemek, değiştirmek veya kaldırmak için ayarlar JSON'unu doğrudan düzenlemeniz (ya da Claude'dan düzenlemesini istemeniz) gerekir.

3. Yapılandırma Dosyalarının Konumu

Bir hook'u nereye yazdığınız, onun kapsamını belirler:

Konum Kapsam Paylaşılabilir mi
~/.claude/settings.json Tüm projeleriniz Hayır, yalnızca sizin makineniz
.claude/settings.json Tek proje Evet, repoya commit edilebilir
.claude/settings.local.json Tek proje Hayır, Claude Code oluşturduğunda gitignore'lanır
Yönetilen politika ayarları Organizasyon geneli Evet, yönetici kontrollü
Plugin hooks/hooks.json Plugin etkinken Evet, plugin ile birlikte paketlenir
Skill veya subagent frontmatter'ı Skill/agent aktifken Evet, bileşen dosyasında tanımlı

Hook'ları tamamen kapatmak için ayarlar dosyanıza "disableAllHooks": true ekleyebilirsiniz; ancak yönetilen ayarlardaki hook'lar bu bayrak orada da ayarlanmadıkça çalışmaya devam eder. Ayarlar dosyalarını Claude Code çalışırken doğrudan düzenlerseniz, dosya izleyici değişiklikleri genellikle otomatik olarak algılar.

4. Yapılandırma Yapısı: matcher ve hooks Dizisi

Her olay altında bir dizi girdi tanımlanır; her girdi bir matcher (hangi araç/olay alt türüne uygulanacağı) ve bir hooks dizisi (o eşleşmede çalışacak işleyiciler) içerir:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
            "timeout": 600
          }
        ]
      }
    ]
  }
}

Matcher desenleri şu şekilde değerlendirilir:

Desen türü Değerlendirme Örnekler
"*", "", belirtilmemiş Her şeyle eşleşir Her tekrar
Harf, rakam, _, -, boşluk, ,, | Tam string veya liste Bash, Edit|Write
Diğer karakterler JavaScript regex, çapasız (unanchored) ^Notebook, mcp__memory__.*

Claude Code v2.1.191 ve sonrasında | ve , birbirinin yerine kullanılabilir, yani "Edit, Write" ile "Edit|Write" aynı sonucu verir. MCP araçları için mcp__<sunucu>__<araç> deseni (örn. mcp__github__search_repositories) ya da bir sunucunun tüm araçlarını hedeflemek için mcp__<sunucu>__.* kullanılır. Hangi olayın hangi alanı eşleştirdiği olaydan olaya değişir: PreToolUse/PostToolUse araç adını, SessionStart oturumun nasıl başladığını (startup, resume, clear, compact), Notification bildirim türünü eşleştirir. UserPromptSubmit, Stop, PostToolBatch, CwdChanged gibi bazı olaylar matcher desteklemez ve her tekrarında çalışır.

5. Tam Olay (Event) Listesi

Claude Code, oturum başına bir kez tetiklenen olaylardan (SessionStart, SessionEnd) her tur (UserPromptSubmit, Stop) ve her araç çağrısına (PreToolUse, PostToolUse) kadar geniş bir olay yelpazesi sunar. En sık kullanılanlar:

Olay Ne zaman tetiklenir
SessionStart Oturum başladığında veya devam ettirildiğinde
UserPromptSubmit Prompt gönderildiğinde, Claude işlemeden önce
PreToolUse Bir araç çağrısından önce; engellenebilir
PermissionRequest İzin diyaloğu göründüğünde
PostToolUse Bir araç çağrısı başarıyla tamamlandıktan sonra
PostToolUseFailure Bir araç çağrısı başarısız olduktan sonra
Notification Claude Code bir bildirim gönderdiğinde
SubagentStart / SubagentStop Bir subagent başlatıldığında / tamamlandığında
Stop Claude yanıt vermeyi bitirdiğinde
StopFailure Tur bir API hatası nedeniyle sona erdiğinde
PreCompact / PostCompact Bağlam sıkıştırmasından önce / sonra
CwdChanged Çalışma dizini değiştiğinde (örn. Claude cd çalıştırdığında)
FileChanged İzlenen bir dosya diskte değiştiğinde
ConfigChange Bir yapılandırma dosyası oturum sırasında değiştiğinde
SessionEnd Oturum sona erdiğinde

Bunların dışında PermissionDenied, PostToolBatch, MessageDisplay, TaskCreated, TaskCompleted, InstructionsLoaded, WorktreeCreate/WorktreeRemove, Elicitation/ElicitationResult, Setup ve UserPromptExpansion gibi daha uç senaryolara yönelik olaylar da bulunuyor; tam listeyi /hooks menüsünden veya resmi dokümantasyondan görebilirsiniz. Bir olayda birden fazla hook eşleşirse hepsi paralel çalışır ve aynı komut birden fazla kez tanımlanmışsa otomatik tekilleştirilir.

6. Hook'lara Gelen Girdi (stdin)

Her olay, ortak alanlarla birlikte olaya özgü veriler taşıyan bir JSON nesnesini stdin üzerinden gönderir. Örneğin Claude bir Bash komutu çalıştıracağında, bir PreToolUse hook'u şuna benzer bir girdi alır:

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

Ortak alanlar arasında session_id (oturum kimliği), cwd (olayın tetiklendiği çalışma dizini), transcript_path (konuşma kaydının yolu), permission_mode (default, plan, acceptEdits, bypassPermissions vb.) ve hook_event_name bulunur. UserPromptSubmit hook'ları ek olarak prompt alanını, SessionStart hook'ları source alanını (startup, resume, clear, compact) alır. Script'iniz bu JSON'u jq veya tercih ettiğiniz herhangi bir dille ayrıştırıp ilgili alanları okuyabilir.

7. Çıktı: Exit Kodları ve JSON

Script'iniz stdout/stderr'e yazıp bir exit code ile döner:

Exit code Anlamı
0 İtiraz yok; normal akış devam eder. UserPromptSubmit ve SessionStart için stdout'a yazılan her şey Claude'un bağlamına eklenir.
2 Engelleyici hata; stderr'e yazılan mesaj Claude'a geri bildirim olarak iletilir. SessionStart, Setup, Notification gibi bazı olaylarda engelleyemez, yalnızca stderr'i kullanıcıya gösterir.
Diğer Engellemeyen hata; işlem devam eder, transcript'te "hook error" bildirimi görünür, tam stderr debug log'una gider.

Exit code'lar yalnızca engelleme/sessiz kalma seçeneği sunar. Daha ince kontrol için exit 0 ile birlikte stdout'a bir JSON nesnesi yazabilirsiniz. Önemli bir kural: exit 2 ile JSON'u karıştırmayın -- Claude Code exit 2 döndüğünde JSON çıktısını yok sayar.

{
  "continue": true,
  "stopReason": "Build failed",
  "suppressOutput": false,
  "systemMessage": "Uyarı: veritabanı salt okunur",
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Yıkıcı komut engellendi"
  }
}

Her olayın karar bildirme şekli farklıdır. PreToolUse için hookSpecificOutput.permissionDecision kullanılır ve dört değer alır: "allow" (izin isteğini atlar, ancak reddetme kuralları hâlâ geçerlidir), "deny" (aracı iptal eder ve nedeni Claude'a iletir), "ask" (normal izin istemini gösterir), ve headless -p modunda kullanılabilen "defer". PostToolUse ve Stop gibi olaylar ise üst seviye decision: "block" alanını kullanır; UserPromptSubmit için bağlam eklemek üzere hookSpecificOutput.additionalContext alanına yazılır -- bu alan hookSpecificOutput içine yerleştirilmezse Claude Code onu sessizce yok sayar.

8. Pratik Örnek: Tehlikeli Bash Komutlarını Engelleme

Aşağıdaki script, komut içinde drop table geçiyorsa çalıştırmayı engeller ve Claude'a nedenini bildirir:

#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q "drop table"; then
  echo "Blocked: dropping tables is not allowed" >&2
  exit 2
fi

exit 0

Bu script'i .claude/hooks/block-drop-table.sh olarak kaydedip chmod +x ile çalıştırılabilir yaptıktan sonra .claude/settings.json'a şu şekilde bağlarsınız:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-drop-table.sh"
          }
        ]
      }
    ]
  }
}

Yol yer tutucularından ${CLAUDE_PROJECT_DIR} proje kökünü, ${CLAUDE_PLUGIN_ROOT} plugin dizinini temsil eder ve hem command hem args alanlarında kullanılabilir; ayrıca çalıştırılan sürece ortam değişkeni olarak da aktarılır.

9. Pratik Örnek: Düzenlemeden Sonra Otomatik Formatlama

Claude'un düzenlediği her dosyada Prettier'i otomatik çalıştırmak için PostToolUse olayını Edit|Write matcher'ı ile eşleştirin. Komut, düzenlenen dosya yolunu jq ile çıkarıp Prettier'e aktarır. Bunu .claude/settings.json'a ekleyin:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

PostToolUse hook'ları aracın çalışmasından sonra tetiklendiği için işlemi geri alamaz; bu olayı yalnızca "zaten oldu, şimdi düzelt/kaydet/bildir" türü işler için kullanın. Claude, dosyaları Bash aracıyla da değiştirebileceğinden, her değişikliği yakalamanız gerekiyorsa (uyumluluk taraması gibi) Bash'i de eşleştirip git status --porcelain ile değişen dosyaları listeleyen bir Stop hook'u eklemek daha güvenilir bir yaklaşımdır.

10. Pratik Örnek: Korumalı Dosyaları Engelleme

.env, package-lock.json veya .git/ altındaki herhangi bir dosyanın Claude tarafından değiştirilmesini önlemek isteyebilirsiniz. .claude/hooks/protect-files.sh olarak kaydedin:

#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done

exit 0

chmod +x .claude/hooks/protect-files.sh ile çalıştırılabilir yaptıktan sonra Edit veya Write aracı çağrılmadan önce çalışacak şekilde kaydedin:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

11. Pratik Örnek: Masaüstü Bildirimleri

Notification olayı, Claude giriş veya izin beklediğinde tetiklenir. Boş matcher tüm bildirim türlerinde çalışır; belirli bir türe daraltmak için matcher'ı şu değerlerden birine ayarlayabilirsiniz:

Matcher Ne zaman tetiklenir
permission_prompt Claude bir araç kullanımını onaylamanızı istediğinde
idle_prompt Claude bitirdi ve bir sonraki isteğinizi beklediğinde
auth_success Kimlik doğrulama tamamlandığında
elicitation_dialog Bir MCP sunucusu bir form açtığında
agent_needs_input / agent_completed Arka plan oturumu girdi beklerken / tamamlandığında (agent view açıkken)

Linux için notify-send, macOS için osascript -e 'display notification ...', Windows PowerShell için System.Windows.Forms.MessageBox kullanılabilir. Bu hook'u ekledikten sonra /hooks menüsünden Notification'ı seçip kaydolduğunu doğrulayın, sonra Claude'dan izin gerektiren bir şey isteyip terminalden uzaklaşarak test edin.

12. Pratik Örnek: Sıkıştırma (Compaction) Sonrası Bağlam Enjeksiyonu

Bağlam penceresi dolduğunda Claude Code konuşmayı özetleyerek yer açar; bu süreçte önemli ayrıntılar kaybolabilir. compact matcher'lı bir SessionStart hook'u, her sıkıştırmadan sonra kritik bağlamı yeniden enjekte etmenizi sağlar. Komutunuzun stdout'a yazdığı her şey Claude'un bağlamına eklenir:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Hatırlatma: npm yerine bun kullan. Commit öncesi bun test çalıştır.'"
          }
        ]
      }
    ]
  }
}

echo yerine git log --oneline -5 gibi dinamik çıktı üreten herhangi bir komut kullanılabilir. Her oturum başlangıcında bağlam enjekte etmek istiyorsanız bunun yerine CLAUDE.md kullanmayı değerlendirin -- hook, özellikle sıkıştırma sonrası kaybı telafi etmek için daha uygundur.

13. Matcher ve if Alanı ile Hassas Filtreleme

matcher alanı grup seviyesinde yalnızca araç adına göre filtreler. Araç adı ve argümanları birlikte filtrelemek için if alanı kullanılır; bu alan izin kuralı sözdizimini kullanır, böylece hook süreci yalnızca gerçekten eşleşen çağrılarda başlatılır:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(git *)",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
          }
        ]
      }
    ]
  }
}

Bu filtre "en iyi çaba" ile çalışır; Bash komutu ayrıştırılamıyorsa hook yine de "açık" davranarak çalışır. Bu yüzden kesin bir izin/red kuralı için if yerine izin sistemini kullanmak gerekir. if alanı yalnızca PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest ve PermissionDenied gibi araç olaylarında çalışır; başka bir olaya eklenirse hook'un çalışmasını engeller.

if deseni Bash komutu Hook çalışır mı
Bash(git *) git push Evet
Bash(git *) npm test && git push Evet (alt komutlar ayrı ayrı kontrol edilir)
Bash(git *) echo $(date) Hayır

14. Prompt ve Agent Tabanlı Hook'lar

Bazı kararlar tamamen deterministik kurallarla verilemez. type: "prompt" hook'ları, hook girdisini bir Claude modeline (varsayılan olarak Haiku, model alanıyla değiştirilebilir) göndererek yalnızca evet/hayır kararı ister:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Tüm görevler tamamlandı mı? Değilse {\"ok\": false, \"reason\": \"ne eksik\"} döndür."
          }
        ]
      }
    ]
  }
}

"ok": false döndüğünde davranış olaya göre değişir: Stop/SubagentStop'ta reason Claude'a geri beslenir ve çalışmaya devam eder; PreToolUse'da araç çağrısı reddedilir. Dosyaların gerçek içeriğini okumak veya komut çalıştırarak doğrulama yapmak gerektiğinde type: "agent" kullanılır -- bu, araç erişimi olan bir subagent başlatır (varsayılan 60 saniye zaman aşımı, en fazla 50 araç kullanım turu). Resmi dokümantasyon agent hook'ların deneysel olduğunu ve üretim iş akışlarında komut hook'larının tercih edilmesi gerektiğini açıkça belirtiyor.

15. Birden Fazla Hook'un Birleştirilmesi

Aynı olaya birden fazla hook eşleştiğinde, hepsi paralel çalışır ve tamamlanana kadar beklenir -- bir hook'un deny döndürmesi kardeş hook'ların çalışmasını durdurmaz. Tüm hook'lar tamamlandıktan sonra sonuçlar birleştirilir: PreToolUse izin kararlarında en kısıtlayıcı yanıt kazanır (deny > defer > ask > allow sırasıyla), additionalContext metinleri ise her hook'tan toplanıp birlikte Claude'a iletilir. Birden fazla PreToolUse hook'u aynı aracın girdisini updatedInput ile yeniden yazarsa, hook'lar paralel çalıştığından son biten kazanır -- bu yüzden aynı aracın girdisini birden fazla hook'un değiştirmesinden kaçının.

16. Güvenlik Değerlendirmeleri

  • PreToolUse hook'ları izin modu kontrolünden önce çalışır. permissionDecision: "deny" döndüren bir hook, bypassPermissions modunda veya --dangerously-skip-permissions ile bile aracı engeller -- kullanıcıların izin modunu değiştirerek atlayamayacağı bir politika uygulamanızı sağlar.
  • Tersi geçerli değildir. Bir hook'un "allow" döndürmesi, ayarlardaki reddetme kurallarını veya kullanıcı etkileşimi gerektiren bağlayıcı/MCP araçlarının onay istemini atlatmaz. Hook'lar kısıtlamayı sıkılaştırabilir ama izin kurallarının izin verdiğinden fazla gevşetemez.
  • PostToolUse hook'ları geri alamaz. Araç zaten çalıştığı için sadece tepki verebilirsiniz, engelleyemezsiniz.
  • disableAllHooks ile kapatma yalnızca kullanıcı/proje hook'larını devre dışı bırakır; yönetilen politika hook'ları bu bayraktan etkilenmez.
  • HTTP hook'ları için yalnızca 2xx + doğru hookSpecificOutput alanlarını içeren bir yanıt gövdesi engelleyebilir; HTTP durum kodu tek başına yeterli değildir.

17. Sorun Giderme

  • Hook hiç tetiklenmiyor: /hooks ile doğru olayın altında göründüğünü doğrulayın; matcher büyük/küçük harfe duyarlıdır; script'i chmod +x ile çalıştırılabilir yaptığınızdan emin olun.
  • "JSON validation failed" hatası: Kabuk formunda (args belirtilmemiş) komutlar sh -c ile çalışır ve kabuk profiliniz (.bashrc/.zshrc) koşulsuz echo içeriyorsa bu metin hook'un JSON çıktısının önüne eklenip ayrıştırmayı bozar. Profildeki echo'ları [[ $- == *i* ]] kontrolüyle yalnızca etkileşimli kabuklarda çalışacak şekilde sarın.
  • "command not found": Script'e mutlak yol veya ${CLAUDE_PROJECT_DIR} ile referans verin; kabuk alıntılamasından tamamen kaçınmak için "args": [] ekleyip exec formuna geçebilirsiniz.
  • Stop hook engelleme sınırına takılıyor: Claude Code, bir Stop hook'u ilerleme kaydetmeden art arda sekiz kez engellediğinde onu geçersiz kılar. Script'inizde stop_hook_active alanını kontrol edip true ise erken çıkın; gerekiyorsa CLAUDE_CODE_STOP_HOOK_BLOCK_CAP ile sınırı yükseltin.
  • Ayrıntılı hata ayıklama: claude --debug-file /tmp/claude.log ile başlatıp tail -f /tmp/claude.log ile hangi hook'ların eşleştiğini, exit code'larını, stdout/stderr'ini görebilirsiniz; oturum ortasında /debug ile de etkinleştirilebilir.

Gerçek Dünya Örneği: Ekip İçin Kombine Bir Yapılandırma

Bir ekibin .claude/settings.json dosyasına commit edebileceği, hem güvenlik hem de kalite kontrolü sağlayan bir örnek: korumalı dosyaları engelleme, düzenlemeden sonra otomatik formatlama, ve sıkıştırma sonrası bağlam hatırlatması bir arada:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "git log --oneline -5"
          }
        ]
      }
    ]
  }
}

Bu yapılandırma repoya commit edilirse tüm ekip aynı korumaları ve otomasyonu paylaşır. Kişisel tercihler (örneğin masaüstü bildirimleri) için bunları ~/.claude/settings.json'a ayrı tutmak, ekip standardını kişisel alışkanlıklardan ayırır.

Sıkça Sorulan Sorular

Hook ile skill arasındaki fark nedir?

Bir skill, Claude'un kendi kararıyla ya da siz /isim yazarak çağırdığınız, isteğe bağlı bir talimat setidir. Bir hook ise Claude Code'un yaşam döngüsündeki belirli bir noktada -- örneğin bir Bash komutu çalıştırılmadan hemen önce -- her seferinde otomatik olarak çalışan, deterministik bir kabuk komutudur. Hook, Claude'un o an ne düşündüğünden bağımsız çalışır; skill ise Claude'un uygun görmesine bağlıdır.

Bir hook'u hangi dosyaya yazmalıyım?

Tüm projelerinizde geçerli kişisel hook'lar için ~/.claude/settings.json, yalnızca bu projede geçerli ve ekiple paylaşılabilir hook'lar için .claude/settings.json, yalnızca sizin makinenizde kalacak proje bazlı hook'lar için .claude/settings.local.json kullanılır. Kurumsal ortamlarda yönetilen politika ayarları ve pluginlerin hooks/hooks.json dosyaları da ek katmanlardır.

Exit code 2 ile exit code 1 arasındaki fark nedir?

Exit code 0 hook'un bir itirazı olmadığını bildirir. Exit code 2 işlemi engeller; stderr'e yazdığınız mesaj Claude'a geri bildirim olarak iletilir. Bunların dışındaki bir exit code (örn. 1) işlemi engellemez; sadece transcript'te bir hata bildirimi gösterilir. SessionStart, Setup ve Notification gibi bazı olaylarda exit code 2 işlemi engelleyemez, yalnızca stderr'i kullanıcıya gösterir.

PreToolUse hook'u bypassPermissions modunu atlayabilir mi?

Evet, tersi yönde değil. PreToolUse hook'ları herhangi bir izin modu kontrolünden önce çalışır; permissionDecision: "deny" döndüren bir hook, bypassPermissions modunda bile aracı engeller. Ancak bir hook'un "allow" döndürmesi, ayarlardaki reddetme kurallarını atlatamaz -- hook'lar kısıtlamayı sıkılaştırabilir ama gevşetemez.

Hook'lar hangi sırayla çalışır, biri diğerini engelleyebilir mi?

Aynı olaya eşleşen tüm hook'lar paralel çalışır ve aynı komut birden fazla kez tanımlanmışsa tekilleştirilir. Bir hook'un deny döndürmesi diğer kardeş hook'ların çalışmasını durdurmaz; hepsi tamamlanana kadar beklenir, sonra sonuçlar birleştirilir. PreToolUse izin kararlarında en kısıtlayıcı yanıt kazanır: deny, defer, ask, allow sırasıyla önceliklidir.

Karar gerektiren ama deterministik olmayan kontroller için ne kullanılır?

type: "prompt" hook'ları, hook girdisini bir Claude modeline (varsayılan Haiku) gönderip yalnızca evet/hayır kararı almanızı sağlar. Gerçek doğrulama (dosya okuma, komut çalıştırma) gerektiğinde type: "agent" kullanılır; bu araç erişimi olan bir subagent başlatır. Resmi dokümantasyona göre agent hook'lar deneyseldir ve üretim iş akışlarında komut hook'ları tercih edilmelidir.

Özet

Hook'lar, Claude Code'a "belki yapar" yerine "her zaman yapar" garantisi ekleyen deterministik kontrol katmanıdır. Bir hooks bloğu; matcher ile hangi araç/olayı hedefleyeceğinizi, type ile nasıl çalışacağını (command, http, mcp_tool, prompt, agent), if ile araç argümanlarına göre ince filtrelemeyi belirler. Girdi stdin'den JSON olarak gelir, karar exit code ve/veya hookSpecificOutput içeren bir JSON ile iletilir. PreToolUse ile tehlikeli komutları veya korumalı dosyaları engelleyebilir, PostToolUse ile otomatik formatlama yapabilir, SessionStart ile sıkıştırma sonrası bağlamı geri kazandırabilir, Notification ile masaüstü uyarıları alabilirsiniz. Kişisel (~/.claude/settings.json) veya proje (.claude/settings.json) seviyesinde tanımlayarak kapsamı belirleyebilir, ekip standartlarını versiyon kontrolüne ekleyerek paylaşabilirsiniz. Skill'lerinizi hook'larla nasıl birlikte kullanacağınızı görmek için Agent Skills rehberimi, script/CI entegrasyonu için de CLI ve otomasyon rehberimi okuyabilirsiniz.

Bu yazı Ahmet Bilgiç tarafından ahmetbilgic.com için yazılmıştır. Diğer yazılar için: medium.com/@ahmet_bilgic07