SSD Nodes Learn 🎉 VPS $5.50/aydan başlayan
Rehberler Matt ConnorYazan Matt Connor · Güncellendi 2026-08-15

Claude Code Hook Yapısı ve Çalışma Mantığı

Claude Code hook mekanizmasının çalışma prensiplerini inceleyin. Modelin onayından bağımsız tetiklenen hook olayları, 2 numaralı çıkış kodu ile araç iptali ve güvenlik detayları.

Claude Code hook nedir

Claude Code hook'ları, Claude Code'un kendi yaşam döngüsü içindeki belirli noktalarda otomatik olarak çalıştırdığı kabuk komutlarıdır. Hook ile kural dosyası arasındaki temel fark budur. CLAUDE.md içindeki bir talimat tavsiye niteliğindedir ve model bunu bağlamındaki diğer her şeyle birlikte değerlendirir. Hook ise koddur; model kabul etse de etmese de çalışır. Eğer temsilciniz belirttiğiniz biçimlendiriciyi (formatter) sürekli atlıyorsa, daha kesin bir talimata değil, bir hook'a ihtiyacınız var demektir.

Mekanizma oldukça basittir. Bir ayar dosyasında, bir olay adı altında bir komut tanımlarsınız. O olay tetiklendiğinde, Claude Code komutunuzu çalıştırır ve olay verilerini standart girdi (stdin) üzerinden JSON (JavaScript object notation) formatında yazar. Komutunuz bu veriyi okur, işlemini yapar ve bir çıkış durumu (exit status) ile yanıt verir. Bir PreToolUse hook'undan dönen 2 çıkış kodu, araç çağrısını çalışmadan iptal eder; betiğinizin standart hata (stderr) çıktısına yazdığı her şey ise modelin önüne gerekçe olarak sunulur.

Buradaki olay adları ve alan adları, Ağustos 2026 itibarıyla 2.1.232 sürümü için doğrulanmış olan Claude Code hooks referansı belgesinden alınmıştır. Bu arayüz hızla değiştiğinden, bu yazı da dahil olmak üzere herhangi bir blog gönderisinden JSON kopyalamadan önce kendi sürümünüz için referansı kontrol edin. Kendi çıktınızı claude --version ile yazdırabilirsiniz.

Hook yapılandırmasının konumu

Hook, bir ayar dosyasındaki JSON bloğudur. Bir hook, altı farklı konumda bulunabilir ve dosyanın kapsamı, hook'un kapsamını belirler.

  • ~/.claude/settings.json: makinenizdeki her proje için geçerlidir, başkalarını etkilemez.
  • .claude/settings.json: tek bir proje için geçerlidir, depoya (repository) işlendiğinden projeyi klonlayan herkes bu hook'u alır.
  • .claude/settings.local.json: tek bir proje için geçerlidir, yalnızca sizin makinenizde çalışır.
  • Yönetilen ilke ayarları: organizasyon genelinde geçerlidir, bir yönetici tarafından belirlenir.
  • hooks/hooks.json bir eklentinin içinde yer alır, eklenti etkin olduğu sürece çalışır.
  • Skill veya subagent ön bilgisi (frontmatter), ilgili bileşen etkin olduğu sürece çalışır.

Bu dosyalardaki hook girdileri birbirinin üzerine yazmak yerine birleştirilir. Bir proje ayar dosyası, hook'larını kullanıcı ayarlarınızdaki hook'ların yerine geçirmek yerine onlara ekler; böylece tek bir olay, farklı dosyalardan gelen birden fazla hook'u barındırabilir. "disableAllHooks": true ayarı, bir istisna dışında hepsini devre dışı bırakır: yönetilen ilke ayarlarından gelen hook'lar, bu ayar yönetilen ayarlarda da uygulanmadığı sürece çalışmaya devam eder.

Şu anda kayıtlı olan tüm hook'ları, olay bazında gruplandırılmış şekilde, kaynak dosyaları ve eşleştiricileriyle birlikte listelemek için bir oturum içinde /hooks komutunu çalıştırın. Menü salt okunurdur; bu nedenle bir hook'u değiştirmek için ilgili ayar dosyasını düzenlemeniz gerekir. Dosya izleyici (file watcher), yapılan düzenlemeyi genellikle yeniden başlatmaya gerek kalmadan algılar.

Hangi Claude Code kanca olayları mevcuttur

Release 2.1.232, SessionStart ile SessionEnd arasında, sıkıştırma (compaction), alt aracılar (subagents), çalışma ağaçları (worktrees) ve yapılandırma dosyalarını kapsayan otuz bir olay listeler. Sunucu işlemleri bunlardan birkaçını kullanır.

  • PreToolUse: bir araç çağrısı yürütülmeden önce. Engelleme yapabilen tek olay budur.
  • PostToolUse: bir araç çağrısı başarıyla tamamlandıktan sonra. PostToolUseFailure ise başarısız olduğunda tetiklenir; bu nedenle her sonucu görmesi gereken bir kancanın her ikisine de ihtiyacı vardır.
  • PermissionRequest: bir araç çağrısı izin kararı gerektirdiğinde; bu, onay isteminin görüneceği andır.
  • UserPromptSubmit: bir istem gönderdiğinizde, Claude onu işlemeden önce. Bu kancanın stdout'a yazdırdığı her şey modelin bağlamına eklenir.
  • SessionStart ve SessionEnd: bir oturumun her iki ucunda. SessionStart ayrıca compact eşleştirici değeri altında sıkıştırma işleminden sonra tetiklenir.
  • Stop: Claude yanıt vermeyi bitirdiğinde. Bu, tamamlanan görev başına değil, tur başına bir kez gerçekleşir.

Her grup, kancanın hangi durumlarda çalışacağını belirleyen bir matcher taşır. Araç olaylarında bu, araç adına göre filtreleme yapar; dolayısıyla "Edit|Write" yalnızca dosya düzenlemelerinde tetiklenir ve başka hiçbir şeyde çalışmaz. Eşleştiriciler büyük/küçük harfe duyarlıdır. Boş bir eşleştirici her durumda tetiklenir. Bir MCP (model context protocol) sunucusundan gelen araçlar mcp__<server>__<tool> olarak adlandırılır; bu nedenle "mcp__github__.*" eşleştiricisi, bir sunucunun araçlarını yakalar ve diğerlerini olduğu gibi bırakır.

Stop kancaları, yazmadan önce bilinmesi gereken bir tuzağa sahiptir. Engelleyici bir Stop kancası, modeli tekrar çalışmaya gönderir ve Claude Code, sekiz ardışık engellemeden sonra kancayı geçersiz kılar. Kanca girdisinden stop_hook_active alanını okuyun ve doğru (true) olduğunda 0 koduyla çıkış yapın; aksi takdirde kancanız bu sınıra ulaşana kadar döngüye girecektir.

Bir kancanın stdin üzerinden aldığı veriler

Claude npm test çalıştırmak üzereyken, Bash üzerindeki bir PreToolUse kancası stdin üzerinden şu veriyi okur:

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

Her olay session_id, cwd, permission_mode, transcript_path ve hook_event_name bilgilerini taşır. Araç olayları ise tool_name, tool_input ve tool_use_id verilerini ekler. Diğer olaylar kendi alanlarını taşır: UserPromptSubmit, prompt metnini alır; SessionStart ise startup, resume, clear, compact veya fork değerlerinden oluşan bir source alır.

jq, bir shell scripti içerisinde bu veriyi okumanın standart yoludur ve minimal bir sunucu imajında bu araç bulunmayabilir. Ubuntu ve Debian üzerinde öncelikle sudo apt install -y jq komutu ile kurulumunu yapın.

Çıkış durumunun devam eden araç çağrısı üzerindeki etkisi

Üç olası sonuç vardır.

  • Çıkış 0, kancanızın herhangi bir itirazı olmadığını belirtir. PreToolUse üzerinde bu onay anlamına gelmez ve normal izin akışı çalışmaya devam eder. UserPromptSubmit ve SessionStart üzerinde, stdout içeriği modelin bağlamına eklenir.
  • Çıkış 2, PreToolUse dahil olmak üzere engellenebilir olaylardaki eylemi durdurur ve stderr içeriği modele gösterilen gerekçe haline gelir. PostToolUse gibi engellenemeyen olaylarda ise engelleme göz ardı edilir, ancak stderr yine de geri bildirim olarak modele iletilir.
  • Diğer tüm çıkış kodları, engelleme yapmayan bir hatadır. Eylem devam eder. Transkript, Failed with non-blocking status code: metninden sonra stderr içeriğinin ilk satırını içeren bir kanca hata bildirimi gösterir.

Engelleme veya sessiz kalma dışında bir işlem için çıkış 0 değerini döndürün ve stdout'a bir JSON nesnesi yazdırın. Bir PreToolUse kancası, permissionDecision ile karar verir:

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

"allow" etkileşimli istemi atlar, "deny" çağrıyı iptal eder ve gerekçeyi modele gönderir, "ask" ise istemi normal şekilde gösterir. Kanca başına tek bir stil seçin. Çıkış 2 ile stdout üzerinde bir JSON kararını birleştirmek, sonucunu ayrıca kontrol etmeniz gereken bir duruma yol açar.

Birden fazla kanca aynı olayla eşleştiğinde, bu kancalar paralel olarak çalışır ve her biri tamamlanana kadar devam eder. Bir kancadan gelen deny, diğerlerini durdurmaz; bu nedenle bir güvenlik kancası çağrıyı reddetse bile, bir günlük kaydı kancası kendi satırını yazmaya devam eder. Claude Code daha sonra yanıtları birleştirir ve reddetme, erteleme, sorma, izin verme sırasına göre en kısıtlayıcı olanı uygular.

Örnek 1: yıkıcı bir komutu çalışmadan önce engelleme

Bunu projenizde .claude/hooks/block-destructive.sh olarak kaydedin:

#!/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

Çalıştırılabilir hale getirin ve ardından .claude/settings.json içindeki PreToolUse üzerinde kaydedin:

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"
          }
        ]
      }
    ]
  }
}

Kendi girdisiyle çöken bir kanca (hook) korumasız kalacağı için, güvenmeden önce betiği manuel olarak test edin:

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

stderr üzerinde Blocked by policy: satırını ve 2 çıkış kodunu görmelisiniz. ls -la gibi zararsız bir komut gönderdiğinizde ise herhangi bir çıktı görmemeli ve 0 çıkış kodunu almalısınız. Bir oturumda, reddedilen çağrı gerekçe olarak mesajınızla birlikte dökümde görünür; model bu mesajı okur ve uyum sağlar.

Bir özellik, bunu yapmaya değer kılar: PreToolUse kancaları, izin modu denetiminden önce ve her izin modunda tetiklenir; bu nedenle bir engelleme, bypassPermissions altında bile geçerliliğini korur. Bir kancayı, istemlerin kısıldığı ancak kancanın hala tetiklendiği Claude Code otomatik modu ve izin ayarları ile birlikte kullanışlı kılan şey budur.

Bunun ne olduğu konusunda dürüst olun. Bir komut dizisi üzerinde desen eşleştirme (pattern matching), bir ajanın dikkatsizliğine karşı bir güvenlik önlemidir; ajanın kurnazlığına karşı bir sınır değildir, çünkü aynı komut grep tarafından asla görülmeyecek bir biçimde yazılabilir. Katı kurallar, izin sistemine ve sürecin altında çalıştığı hesaba aittir.

Örnek 2: her düzenlemeden sonra biçimlendirme ve lint işlemi

PostToolUse, bir Edit|Write eşleştiricisi ile birlikte herhangi bir dosya düzenleme aracından sonra çalışır. Bunu .claude/hooks/after-edit.sh olarak kaydedin:

#!/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'dan bir Python dosyasına kötü girintilenmiş bir fonksiyon eklemesini isteyin, ardından dosyayı açın. Dosya biçimlendirilmiş olarak gelecektir. Bu, kancanın (hook) çalıştığının kanıtıdır; çünkü başarılı bir kanca işleminde konuşma ekranında herhangi bir çıktı görünmez.

Buradaki exit 2 komutu hiçbir işlemi geri almaz. PostToolUse, araç zaten yürütüldükten sonra tetiklendiği için düzenleme her halükarda diske yazılmıştır. exit 2 kullanmanın sağladığı avantaj, ruff check çıktısının modele geri bildirim olarak ulaşmasıdır; böylece model bir sonraki adıma geçmek yerine az önce oluşturduğu hatayı düzeltir. Commit zamanında fark edilen bir lint hatası ile aracın aynı turda onardığı bir hata arasındaki fark budur.

Burada iki eşleştirici sınırı önemlidir. Edit|Write, bir shell komutu tarafından değiştirilen dosyaları görmez ve Claude, dosyaları Bash aracılığıyla yeterince sık yazar; bu nedenle söz konusu boşluk gerçek bir sorundur. Çağrı bazında kapsama sağlamak için Bash eşleşmesini de ekleyin ve betiğin değiştirilen dosyaları git status --porcelain ile listelemesini sağlayın. Tur başına kapsama için tarama işlemini bir Stop kancasına yerleştirin.

Örnek 3: denetim için her araç çağrısını günlüğe kaydetme

PostToolUse üzerindeki boş bir eşleştirici, her araç çağrısında tetiklenir. Kaydı ana dizindeki bir dosyaya yazmak yerine sistem günlüğüne (system journal) göndermek, kaydın aracın kendi kabuğunun erişim alanı dışında kalmasını sağlar:

{
  "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"
          }
        ]
      }
    ]
  }
}

Kayıtları journalctl -t claude-code -o cat | tail -n 5 ile okuyun. Her araç çağrısı için bir JSON satırı görmelisiniz; en yenisi en altta yer alacaktır. Hiçbir şey görünmüyorsa kanca (hook) çalışmamıştır; aşağıdaki sorun giderme bölümü bu durumu ele almaktadır.

Başarısız olan çağrıları yakalamak için aynı bloğu PostToolUseFailure altına ekleyin, çünkü PostToolUse yalnızca başarı durumunda tetiklenir ve genellikle başarısız olan komutlar daha önemlidir. Ana dizininizdeki bir dosyaya ekleme yapmak yerine logger kullanılmasının nedeni sahiplik durumudur: bir kanca, aracın kabuğuyla aynı kullanıcı yetkileriyle çalışır; dolayısıyla o kullanıcının ekleme yapabildiği her dosyayı, aynı kullanıcı içeriğini silerek boşaltabilir de. Günlük kaydı ise systemd-journald tarafından kendi hesabı altında yazılır.

Bir kancanın çalışma süresi

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
  }
]

Bir komut kancası varsayılan olarak 600 saniye, yani on dakika süreye sahiptir. Bazı olaylar bu süreyi ciddi oranda kısıtlar. SessionEnd kancaları, aralarında toplam 1.5 saniyelik bir bütçeyi paylaşır; bu nedenle oturum sonu temizlik işlemlerinin hızlı olması gerekir. Ancak kanca üzerinde daha uzun bir timeout ayarlanması, paylaşılan bütçeyi 60 saniyeye kadar yükseltir.

Zaman aşımına uğrayan bir kanca iptal edilir ve herhangi bir karar üretmez. Bir PreToolUse güvenlik bariyeri için bu, kancanın engelleme yapmadığı anlamına gelir: araç çağrısı normal izin akışına devam eder. Bu nedenle güvenlik bariyeri betiklerini kısa tutun. Log göndermek gibi kimsenin beklemediği yavaş işlemler için "async": true ayarını kullanın; böylece kanca, araç çağrısını bekletmeden arka planda çalışır.

Kancalar, kural dosyaları, yetenekler ve MCP sunucuları

Dört farklı kavram, ajanın davranışını değiştirdikleri için birbirleriyle karıştırılmaktadır. Bunlardan yalnızca biri öneri olmaktan çıkar.

Bir kural dosyası (CLAUDE.md veya .claude/rules/ altındaki bir dosya), modelin bağlamına yüklenen metindir. Davranışı şekillendirir ancak hiçbir şeyi zorunlu kılmaz. Uzun bir konuşma, büyük bir fark (diff) ve yeni bir kullanıcı isteği karşısında, kural dosyasındaki tek bir satır etkisini yitirebilir. Ajanların yazdığınız talimatları görmezden gelmesinin arkasındaki olağan mekanizma budur.

Bir yetenek (skill), modelin ilgili olduğunu düşündüğünde yüklediği talimatlar ve betikler klasörüdür. Bu değerlendirme, yeteneğin temel amacı olduğu kadar sınırıdır da: kararı yine model verir. Ajani çalışan en küçük değişikliği yapmaya zorlayan Ponytail gibi bir yetenekte her iki tarafı da görebilirsiniz; çünkü bu, hiçbir kancanın yapamayacağı bir şekilde tüm göreve yaklaşım biçimini şekillendirir ve bunu yalnızca model onu yüklemeyi seçtiği sürece yapar.

Bir MCP (model context protocol) sunucusu, modele çağırabileceği yeni araçlar sağlar. Ajanın erişebileceği alanı genişletir. Ajanı herhangi bir şeyi yapmaya zorlamaz ve ayrıca yönetmeniz gereken ayrı bir süreçtir; bu başlı başına bir iştir: VPS üzerinde MCP sunucuları çalıştırma konusuna bakınız.

Kanca (hook), bu dördü arasında modelin seçimi olmaksızın çalışan tek mekanizmadır. Bir tercih için kural dosyasını, modelin uygularken izlemesi gereken bir prosedür için yeteneği kullanın. Her seferinde gerçekleşmesi gereken veya asla gerçekleşmemesi gereken adımlar için ise kancayı kullanın. Bir yeteneğin kural dosyasından ne zaman daha üstün olduğu dahil olmak üzere daha derinlemesine karşılaştırma için yetenekler, MCP ve kural dosyaları karşılaştırması bölümüne bakınız.

Eklenti (plugin), beşinci bir mekanizma değil, bir paketleme yöntemidir. Kancaları ve yetenekleri tek bir kurulabilir birimde birleştirir; bir ekibin aynı güvenlik bariyerini her makineye dağıtma yöntemi budur: Claude Code eklentilerinin çalışma mantığı konusuna bakınız.

Paylaşımlı bir VPS üzerinde güvenlik kararı

Hook, aracın tetiklediği ve Claude Code'u başlatan kullanıcı yetkileriyle çalışan koddur. Bu kod, ilgili kullanıcının ortamını ve dosya izinlerini devralır. Bir dizüstü bilgisayarda bu bir iş akışı sorusudur. Bir aracın gözetimsiz çalıştığı bir VPS üzerinde ise bu, dört pratik boyutu olan bir güvenlik sorusudur.

Bir depodaki hook, sizin yazmadığınız koddur. .claude/settings.json commit edildiği için, bir depoyu klonlamak ve içinde bir oturum başlatmak, depo ile birlikte gelen hook'ların kaydedilmesine neden olabilir. Claude Code, proje hook'larını o klasör için çalışma alanı güven (workspace trust) diyaloğu arkasında tutar; yani güveni kabul etmek, onları çalıştırmaya karar verdiğiniz andır. Önce hooks bloğunu okuyun.

Bir hook, tüm araç girdisini görür. tool_input kaydı tutan bir denetim hook'u, her komutun tüm argümanlarını bir dosyaya yazar; buna komut satırında yer alan herhangi bir token da dahildir. Bu günlük dosyası, gizli verinin kendisiyle aynı korumaya ihtiyaç duyar; bu da gizli verileri bir yapay zeka aracının erişiminden uzak tutma konusundaki daha geniş sorunun bir parçasıdır.

Bir hook, modelin bağlamına yazabilir. Bir SessionStart veya UserPromptSubmit hook'unun stdout'a yazdırdığı her şey konuşmaya eklenir. Dışarıdan, bir sorun takip sisteminden veya bir günlük dosyasından metin aktaran bir hook, güvenilmeyen metni sanki siz yazmışsınız gibi modele sunar. Bu stdout çıktısını, bir çıktıdan ziyade bir girdi olarak değerlendirin.

Ayrıcalık, asıl kontroldür. Aracı, yalnızca ihtiyaç duyduğu sudo kurallarına sahip, ayrıcalıksız ve özel bir kullanıcı olarak çalıştırın. Bir PreToolUse reddi (deny) uygulamak faydalıdır ancak bu tasarım gereği "en iyi çaba" (best effort) prensibiyle çalışır: referans dokümanı, if filtresi için de aynı şeyi söyler ve kesin bir engelleme gerektiğinde işletim sisteminin izin sistemini kullanmanızı önerir. İzin kuralları ve sürecin altında çalıştığı hesap, baskı altında ayakta kalan kısımlardır.

Her yapılandırmada geçerli olan bir özellik vardır. PreToolUse hook'ları, her izin modunda izin denetiminden önce çalışır; bu nedenle deny döndüren bir hook, bypassPermissions modunda bile aracı engeller. Hook'lar, izin kurallarının izin verdiğini kısıtlayabilir. Ancak izin verilenleri genişletemezler.

Kancam neden tetiklenmiyor?

Bu adımları sırasıyla izleyin. Her adım, karşılaşacağınız belirtiyi tanımlar.

  • /hooks komutunu çalıştırın ve kancanın beklediğiniz etkinlik altında görünüp görünmediğini kontrol edin. Menüde görünmeyen bir kanca, genellikle ayarlar dosyasında bir JSON sözdizimi hatası olduğu anlamına gelir; çünkü sondaki virgüllere ve yorum satırlarına izin verilmez veya dosya yukarıda belirtilen altı konumdan birinde değildir.
  • Eşleştiriciyi (matcher) araç adıyla tam olarak karşılaştırın. Eşleştiriciler büyük/küçük harfe duyarlıdır, bu nedenle "bash" hiçbir zaman Bash aracıyla eşleşmez.
  • Yukarıdaki 1. örnekte olduğu gibi, betiği örnek girdiyle manuel olarak çalıştırın. Beklemediğiniz bir çıkış kodu betiğinizdeki bir hatadır ve Claude Code bunu bir karar olarak değil, bir kanca hatası olarak bildirir.
  • jq: command not found uyarısı, ilgili makinede jq eksik olduğu anlamına gelir. Kendi betiğiniz için alınan bir command not found hatası, yolun çözülemediğini gösterir; bu durumda ${CLAUDE_PROJECT_DIR} veya mutlak bir yol kullanın. Betik hiçbir şekilde çalışmıyorsa, muhtemelen çalıştırılabilir (executable) değildir.
  • Kanca geçerli JSON çıktısı veriyor ancak hiçbir şey olmuyor. Shell formundaki bir kanca sh -c üzerinden çalışır; eğer shell profiliniz bir banner yazdırıyorsa, bu banner JSON çıktınızın başına eklenir. Standart çıktı (stdout) artık { ile başlamadığı için Claude Code çıktının tamamını düz metin olarak okur ve kararı yok sayar. Çıkış kodu 0 olduğunda, hata ayıklama günlüğü (debug log) dışında hiçbir yerde raporlama yapılmaz. Profilinizdeki tüm echo komutlarını, yalnızca etkileşimli (interactive) shell'lerde çalışacak şekilde sarmalayın.
  • Hâlâ sorun yaşıyorsanız: oturumu claude --debug-file /tmp/claude.log ile başlatın ve ikinci bir terminalde tail -f /tmp/claude.log komutunu çalıştırın. Hata ayıklama günlüğü, hangi kancaların eşleştiğini, her birinin hangi çıkış kodunu döndürdüğünü ve standart çıktı ile standart hata (stderr) kanallarına neler yazdıklarını kaydeder.

FAQ

Claude Code hook ile CLAUDE.md talimatı arasındaki fark nedir?

CLAUDE.md talimatı, modelin bağlamı içindeki bir metindir; bu nedenle konuşma ve mevcut istek ile dikkat çekmek için yarışır, model de bu talimatı diğerleriyle kıyaslayarak değerlendirebilir. Hook ise Claude Code'un yaşam döngüsünde sabit bir noktada çalıştırdığı bir kabuk komutudur; dolayısıyla modelin neye karar verdiğinden bağımsız olarak, ilgili olay her gerçekleştiğinde çalışır. Tercihleriniz için talimatları, her zaman gerçekleşmesi gereken bir adım veya asla gerçekleşmemesi gereken bir eylem için ise hook yapısını kullanın.

Claude Code'un belirli bir kabuk komutunu çalıştırmasını nasıl engellerim?

.tool_input.command üzerinden komutu okuyan, stderr'e bir neden yazan ve 2 koduyla çıkan bir Bash eşleştiricisine sahip bir PreToolUse hook'u kaydedin. Claude Code çağrıyı iptal eder ve modelinize nedeninizi gösterir; bu işlem izin modu denetiminden önce gerçekleştiği için, engelleme bypassPermissions modunda bile geçerli kalır. Bir komut dizisi üzerinde desen eşleştirme yapmak bir güvenlik sınırı değil, bir koruma önlemidir; çünkü aynı komut, desenin gözden kaçırabileceği bir biçimde yazılabilir. Bu nedenle, bu yöntemi izin kuralları ve yetkisiz bir hesap ile destekleyin.

Hook'um geçerli JSON çıktısı veriyor ancak hiçbir şey olmuyor. Neden?

En yaygın neden kabuk profilinizdir. args alanı olmayan bir hook, sh -c üzerinden çalışır ve bazı profiller her kabuk açılışında bir başlık yazdırır; bu da JSON verinizden önce stdout'a düşer. Çıktı artık { ile başlamadığı için, Claude Code tüm içeriği düz metin olarak ele alır ve kararı görmezden gelir; 0 çıkış koduyla tamamlandığında ise transkriptte hiçbir şey raporlanmaz. Profilinizdeki tüm echo çıktılarını etkileşimli kabuk testi ile korumaya alın, ardından claude --debug-file /tmp/claude.log üzerinden hata ayıklama günlüğünü okuyarak düzeltmeyi doğrulayın.

Paylaşımlı bir sunucuda Claude Code hook'larını çalıştırmak güvenli midir?

Hook'lar, Claude Code'u başlatan kullanıcı olarak ve o kullanıcının dosya izinleriyle çalışır; dolayısıyla bir hook, ilgili hesabın yapabileceği her şeyi yapabilir. İki alışkanlık riskin çoğunu kapsar: aracı, kısıtlı bir sudo politikasına sahip özel ve yetkisiz bir hesapla çalıştırın ve çalışma alanı güven onayını kabul etmeden önce herhangi bir deponun hooks bloğunu okuyun; çünkü proje hook'ları .claude/settings.json içinde gelir. Hiçbirinin çalışmasını istemediğiniz durumlarda ayar dosyanızda "disableAllHooks": true değerini ayarlayın.