Penjelasan lengkap mekanisme Claude Code Hooks: lima jenis event, kontrol exit code, dan empat konfigurasi nyata untuk membuat tindakan AI dapat diaudit, dicegat, dan diotomatisasi.
Setiap kali Claude Code membaca file, menulis kode, atau menjalankan perintah, ada sistem event yang bekerja di latar belakang. Hooks adalah antarmuka untuk terhubung ke sistem itu — kamu bisa menyuntikkan logika sendiri kapan saja: menjalankan linter secara otomatis, mencatat operasi, memblokir tindakan berbahaya, atau memicu perintah shell apa pun.
Artikel ini membahas mekanisme Hooks, konfigurasi, dan penggunaan praktisnya secara lengkap.
Hooks adalah perintah shell yang dikonfigurasi di settings.json dan dijalankan otomatis oleh Claude Code saat event tertentu terjadi.
Analogi paling tepat: git hooks. Git bisa memicu skrip sebelum dan sesudah operasi seperti commit dan push. Claude Code Hooks bekerja persis sama — perbedaannya adalah titik pemicunya adalah pemanggilan alat oleh AI.
Mengapa ini penting?
Semakin kuat Claude Code, semakin kamu butuh lapisan kontrol yang deterministik. Hooks memberikan:
- Jaminan eksekusi yang tidak bergantung pada prompt (Claude bisa mengabaikan instruksi, tapi hook selalu berjalan)
- Catatan operasi yang dapat diaudit
- Pemeriksaan kualitas otomatis
| Tipe | Pemicu | Kegunaan Umum |
|---|---|---|
PreToolUse |
Sebelum pemanggilan alat | Memblokir operasi berbahaya, mencatat niat |
PostToolUse |
Sesudah pemanggilan alat | Auto-lint, menjalankan tes |
PreCompact |
Sebelum kompresi konteks | Menyimpan snapshot status saat ini |
Notification |
Saat Claude mengirim notifikasi | Alert desktop, pesan Slack |
Stop |
Saat Claude selesai merespons | Merangkum log, memicu alur berikutnya |
Yang paling sering digunakan adalah PreToolUse dan PostToolUse, untuk mencegat dan memproses pemanggilan alat.
Hooks ditulis di ~/.claude/settings.json (global) atau .claude/settings.json di root proyek (level proyek):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm run lint --silent"
}
]
}
]
}
}
Tiga field utama:
matcher: Regex yang cocok dengan nama alat, menentukan pemanggilan alat mana yang memicu hook ini. "Write|Edit" aktif saat menulis atau mengedit file. Biarkan kosong atau gunakan ".*" untuk mencocokkan semua alat.type: Saat ini hanya "command".command: Perintah shell apa pun.Nama alat yang perlu kamu ketahui untuk menulis matcher:
| Nama Alat | Operasi |
|---|---|
Write |
Menulis file baru |
Edit |
Mengedit file |
Bash |
Menjalankan perintah shell |
Read |
Membaca file |
Glob |
Pencarian file |
Grep |
Pencarian konten |
TodoWrite |
Memperbarui daftar tugas |
Saat dijalankan, data dikirim sebagai JSON melalui stdin:
{
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.rb",
"content": "..."
},
"tool_response": "..."
}
PreToolUse menerima tool_input (argumen sebelum pemanggilan). PostToolUse juga menerima tool_response (nilai kembalian alat).
Contoh hook dengan logika kondisional:
#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path')
# Jalankan rubocop hanya untuk file .rb
if [[ "$file" == *.rb ]]; then
rubocop "$file" --autocorrect-all --no-color
fi
Kode keluar hook mengontrol perilaku Claude Code selanjutnya:
| Kode Keluar | Arti |
|---|---|
0 |
Berhasil, lanjutkan eksekusi |
2 |
Blokir: batalkan pemanggilan alat saat ini, kirim output stderr ke Claude |
| Non-zero lainnya | Catat error, tapi lanjutkan |
Kode keluar 2 paling berguna — memungkinkan kamu menulis logika intersepsi di PreToolUse untuk mencegah Claude melakukan operasi dan menjelaskan alasannya.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.rb ]] && rubocop -A \"$file\" --no-color -q || true'"
}
]
}
]
}
}
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'cmd=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); if echo \"$cmd\" | grep -qE \"rm.*/(migrations|seeds)\"; then echo \"Menghapus direktori migrations atau seeds tidak diizinkan\" >&2; exit 2; fi'"
}
]
}
]
}
}
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|Bash",
"hooks": [
{
"type": "command",
"command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') $CLAUDE_TOOL_NAME: $(echo $CLAUDE_TOOL_INPUT | jq -c .)\" >> ~/.claude/audit.log"
}
]
}
]
}
}
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude telah selesai\" with title \"Claude Code\"'"
}
]
}
]
}
}
Hooks bisa ditempatkan di dua lokasi:
Global (~/.claude/settings.json): Aturan yang berlaku untuk semua proyek — log, notifikasi.
Level proyek (.claude/settings.json): Pemeriksaan khusus proyek. Hooks level proyek dan global digabung dan keduanya dijalankan.
Untuk proyek tim, commit settings.json level proyek ke git agar semua anggota menjalankan hooks yang sama.
Langkah troubleshooting ketika hook tidak berjalan:
write bukan Writeecho "hook triggered" >> /tmp/hook.log untuk konfirmasiNilai inti Hooks adalah menghubungkan perilaku AI yang tidak dapat diprediksi dengan standar rekayasa yang deterministik. Claude mungkin lupa menjalankan tes, tapi PostToolUse hook tidak. Claude mungkin menghapus file secara tidak sengaja, tapi interceptor PreToolUse tidak akan membiarkannya.
Mulai dengan satu hook: auto-lint setelah menulis file. Setelah berjalan lancar, tambahkan yang lain.