Untuk panduan quickstart dengan contoh, lihat Memulai dengan hooks Claude Code.

Konfigurasi

Hooks Claude Code dikonfigurasi dalam file pengaturan Anda:

  • ~/.claude/settings.json - Pengaturan pengguna
  • .claude/settings.json - Pengaturan proyek
  • .claude/settings.local.json - Pengaturan proyek lokal (tidak di-commit)
  • Pengaturan kebijakan yang dikelola enterprise

Struktur

Hooks diorganisir berdasarkan matcher, di mana setiap matcher dapat memiliki beberapa hooks:

{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here"
          }
        ]
      }
    ]
  }
}
  • matcher: Pola untuk mencocokkan nama tool, case-sensitive (hanya berlaku untuk PreToolUse dan PostToolUse)
    • String sederhana cocok persis: Write hanya cocok dengan tool Write
    • Mendukung regex: Edit|Write atau Notebook.*
    • Gunakan * untuk mencocokkan semua tool. Anda juga dapat menggunakan string kosong ("") atau membiarkan matcher kosong.
  • hooks: Array perintah untuk dieksekusi ketika pola cocok
    • type: Saat ini hanya "command" yang didukung
    • command: Perintah bash untuk dieksekusi (dapat menggunakan variabel lingkungan $CLAUDE_PROJECT_DIR)
    • timeout: (Opsional) Berapa lama perintah harus berjalan, dalam detik, sebelum membatalkan perintah spesifik tersebut.

Untuk event seperti UserPromptSubmit, Notification, Stop, dan SubagentStop yang tidak menggunakan matcher, Anda dapat menghilangkan field matcher:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/prompt-validator.py"
          }
        ]
      }
    ]
  }
}

Script Hook Spesifik Proyek

Anda dapat menggunakan variabel lingkungan CLAUDE_PROJECT_DIR (hanya tersedia ketika Claude Code menjalankan perintah hook) untuk mereferensikan script yang disimpan dalam proyek Anda, memastikan mereka bekerja terlepas dari direktori saat ini Claude:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-style.sh"
          }
        ]
      }
    ]
  }
}

Event Hook

PreToolUse

Berjalan setelah Claude membuat parameter tool dan sebelum memproses panggilan tool.

Matcher umum:

  • Task - Tugas subagent (lihat dokumentasi subagent)
  • Bash - Perintah shell
  • Glob - Pencocokan pola file
  • Grep - Pencarian konten
  • Read - Pembacaan file
  • Edit, MultiEdit - Pengeditan file
  • Write - Penulisan file
  • WebFetch, WebSearch - Operasi web

PostToolUse

Berjalan segera setelah tool selesai dengan sukses.

Mengenali nilai matcher yang sama seperti PreToolUse.

Notification

Berjalan ketika Claude Code mengirim notifikasi. Notifikasi dikirim ketika:

  1. Claude memerlukan izin Anda untuk menggunakan tool. Contoh: “Claude needs your permission to use Bash”
  2. Input prompt telah idle selama setidaknya 60 detik. “Claude is waiting for your input”

UserPromptSubmit

Berjalan ketika pengguna mengirimkan prompt, sebelum Claude memprosesnya. Ini memungkinkan Anda untuk menambahkan konteks tambahan berdasarkan prompt/percakapan, memvalidasi prompt, atau memblokir jenis prompt tertentu.

Stop

Berjalan ketika agen utama Claude Code telah selesai merespons. Tidak berjalan jika penghentian terjadi karena interupsi pengguna.

SubagentStop

Berjalan ketika sub agent Claude Code (panggilan tool Task) telah selesai merespons.

PreCompact

Berjalan sebelum Claude Code akan menjalankan operasi compact.

Matcher:

  • manual - Dipanggil dari /compact
  • auto - Dipanggil dari auto-compact (karena context window penuh)

Input Hook

Hook menerima data JSON melalui stdin yang berisi informasi sesi dan data spesifik event:

{
  // Field umum
  session_id: string
  transcript_path: string  // Path ke JSON percakapan
  cwd: string              // Direktori kerja saat ini ketika hook dipanggil

  // Field spesifik event
  hook_event_name: string
  ...
}

Input PreToolUse

Skema yang tepat untuk tool_input tergantung pada tool.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  }
}

Input PostToolUse

Skema yang tepat untuk tool_input dan tool_response tergantung pada tool.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  },
  "tool_response": {
    "filePath": "/path/to/file.txt",
    "success": true
  }
}

Input Notification

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Notification",
  "message": "Task completed successfully"
}

Input UserPromptSubmit

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "Write a function to calculate the factorial of a number"
}

Input Stop dan SubagentStop

stop_hook_active adalah true ketika Claude Code sudah melanjutkan sebagai hasil dari stop hook. Periksa nilai ini atau proses transkrip untuk mencegah Claude Code berjalan tanpa batas.

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "hook_event_name": "Stop",
  "stop_hook_active": true
}

Input PreCompact

Untuk manual, custom_instructions berasal dari apa yang pengguna masukkan ke /compact. Untuk auto, custom_instructions kosong.

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "hook_event_name": "PreCompact",
  "trigger": "manual",
  "custom_instructions": ""
}

Output Hook

Ada dua cara untuk hook mengembalikan output kembali ke Claude Code. Output mengkomunikasikan apakah akan memblokir dan feedback apa pun yang harus ditampilkan kepada Claude dan pengguna.

Sederhana: Exit Code

Hook mengkomunikasikan status melalui exit code, stdout, dan stderr:

  • Exit code 0: Sukses. stdout ditampilkan kepada pengguna dalam mode transkrip (CTRL-R), kecuali untuk UserPromptSubmit, di mana stdout ditambahkan ke konteks.
  • Exit code 2: Error yang memblokir. stderr diberikan kembali ke Claude untuk diproses secara otomatis. Lihat perilaku per-hook-event di bawah.
  • Exit code lainnya: Error yang tidak memblokir. stderr ditampilkan kepada pengguna dan eksekusi berlanjut.

Pengingat: Claude Code tidak melihat stdout jika exit code adalah 0, kecuali untuk hook UserPromptSubmit di mana stdout diinjeksi sebagai konteks.

Perilaku Exit Code 2

Event HookPerilaku
PreToolUseMemblokir panggilan tool, menampilkan stderr ke Claude
PostToolUseMenampilkan stderr ke Claude (tool sudah berjalan)
NotificationN/A, menampilkan stderr hanya ke pengguna
UserPromptSubmitMemblokir pemrosesan prompt, menghapus prompt, menampilkan stderr hanya ke pengguna
StopMemblokir penghentian, menampilkan stderr ke Claude
SubagentStopMemblokir penghentian, menampilkan stderr ke subagent Claude
PreCompactN/A, menampilkan stderr hanya ke pengguna

Lanjutan: Output JSON

Hook dapat mengembalikan JSON terstruktur dalam stdout untuk kontrol yang lebih canggih:

Field JSON Umum

Semua jenis hook dapat menyertakan field opsional ini:

{
  "continue": true, // Apakah Claude harus melanjutkan setelah eksekusi hook (default: true)
  "stopReason": "string" // Pesan yang ditampilkan ketika continue adalah false
  "suppressOutput": true, // Sembunyikan stdout dari mode transkrip (default: false)
}

Jika continue adalah false, Claude berhenti memproses setelah hook berjalan.

  • Untuk PreToolUse, ini berbeda dari "permissionDecision": "deny", yang hanya memblokir panggilan tool spesifik dan memberikan feedback otomatis ke Claude.
  • Untuk PostToolUse, ini berbeda dari "decision": "block", yang memberikan feedback otomatis ke Claude.
  • Untuk UserPromptSubmit, ini mencegah prompt diproses.
  • Untuk Stop dan SubagentStop, ini mengambil prioritas atas output "decision": "block" apa pun.
  • Dalam semua kasus, "continue" = false mengambil prioritas atas output "decision": "block" apa pun.

stopReason menyertai continue dengan alasan yang ditampilkan kepada pengguna, tidak ditampilkan kepada Claude.

Kontrol Keputusan PreToolUse

Hook PreToolUse dapat mengontrol apakah panggilan tool berlanjut.

  • "allow" melewati sistem izin. permissionDecisionReason ditampilkan kepada pengguna tetapi tidak kepada Claude. (Nilai "approve" yang deprecated + reason memiliki perilaku yang sama.)
  • "deny" mencegah panggilan tool dieksekusi. permissionDecisionReason ditampilkan kepada Claude. (Nilai "block" + reason memiliki perilaku yang sama.)
  • "ask" meminta pengguna untuk mengonfirmasi panggilan tool di UI. permissionDecisionReason ditampilkan kepada pengguna tetapi tidak kepada Claude.
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow" | "deny" | "ask",
    "permissionDecisionReason": "My reason here (shown to user)"
  },
  "decision": "approve" | "block" | undefined, // Deprecated untuk PreToolUse tetapi masih didukung
  "reason": "Explanation for decision" // Deprecated untuk PreToolUse tetapi masih didukung
}

Kontrol Keputusan PostToolUse

Hook PostToolUse dapat mengontrol apakah panggilan tool berlanjut.

  • "block" secara otomatis meminta Claude dengan reason.
  • undefined tidak melakukan apa-apa. reason diabaikan.
{
  "decision": "block" | undefined,
  "reason": "Explanation for decision"
}

Kontrol Keputusan UserPromptSubmit

Hook UserPromptSubmit dapat mengontrol apakah prompt pengguna diproses.

  • "block" mencegah prompt diproses. Prompt yang dikirimkan dihapus dari konteks. "reason" ditampilkan kepada pengguna tetapi tidak ditambahkan ke konteks.
  • undefined memungkinkan prompt berlanjut secara normal. "reason" diabaikan.
  • "hookSpecificOutput.additionalContext" menambahkan string ke konteks jika tidak diblokir.
{
  "decision": "block" | undefined,
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "My additional context here"
  }
}

Kontrol Keputusan Stop/SubagentStop

Hook Stop dan SubagentStop dapat mengontrol apakah Claude harus melanjutkan.

  • "block" mencegah Claude berhenti. Anda harus mengisi reason untuk Claude mengetahui cara melanjutkan.
  • undefined memungkinkan Claude berhenti. reason diabaikan.
{
  "decision": "block" | undefined,
  "reason": "Must be provided when Claude is blocked from stopping"
}

Contoh Exit Code: Validasi Perintah Bash

#!/usr/bin/env python3
import json
import re
import sys

# Definisikan aturan validasi sebagai daftar tuple (pola regex, pesan)
VALIDATION_RULES = [
    (
        r"\bgrep\b(?!.*\|)",
        "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
    ),
    (
        r"\bfind\s+\S+\s+-name\b",
        "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
    ),
]


def validate_command(command: str) -> list[str]:
    issues = []
    for pattern, message in VALIDATION_RULES:
        if re.search(pattern, command):
            issues.append(message)
    return issues


try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
command = tool_input.get("command", "")

if tool_name != "Bash" or not command:
    sys.exit(1)

# Validasi perintah
issues = validate_command(command)

if issues:
    for message in issues:
        print(f"• {message}", file=sys.stderr)
    # Exit code 2 memblokir panggilan tool dan menampilkan stderr ke Claude
    sys.exit(2)

Contoh Output JSON: UserPromptSubmit untuk Menambah Konteks dan Validasi

Untuk hook UserPromptSubmit, Anda dapat menginjeksi konteks menggunakan salah satu metode:

  • Exit code 0 dengan stdout: Claude melihat konteks (kasus khusus untuk UserPromptSubmit)
  • Output JSON: Memberikan kontrol lebih atas perilaku
#!/usr/bin/env python3
import json
import sys
import re
import datetime

# Muat input dari stdin
try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

prompt = input_data.get("prompt", "")

# Periksa pola sensitif
sensitive_patterns = [
    (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
]

for pattern, message in sensitive_patterns:
    if re.search(pattern, prompt):
        # Gunakan output JSON untuk memblokir dengan alasan spesifik
        output = {
            "decision": "block",
            "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."
        }
        print(json.dumps(output))
        sys.exit(0)

# Tambahkan waktu saat ini ke konteks
context = f"Current time: {datetime.datetime.now()}"
print(context)

"""
Berikut ini juga setara:
print(json.dumps({
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": context,
  },
}))
"""

# Izinkan prompt berlanjut dengan konteks tambahan
sys.exit(0)

Contoh Output JSON: PreToolUse dengan Persetujuan

#!/usr/bin/env python3
import json
import sys

# Muat input dari stdin
try:
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})

# Contoh: Auto-approve pembacaan file untuk file dokumentasi
if tool_name == "Read":
    file_path = tool_input.get("file_path", "")
    if file_path.endswith((".md", ".mdx", ".txt", ".json")):
        # Gunakan output JSON untuk auto-approve panggilan tool
        output = {
            "decision": "approve",
            "reason": "Documentation file auto-approved",
            "suppressOutput": True  # Jangan tampilkan dalam mode transkrip
        }
        print(json.dumps(output))
        sys.exit(0)

# Untuk kasus lain, biarkan alur izin normal berlanjut
sys.exit(0)

Bekerja dengan Tool MCP

Hook Claude Code bekerja dengan mulus dengan tool Model Context Protocol (MCP). Ketika server MCP menyediakan tool, mereka muncul dengan pola penamaan khusus yang dapat Anda cocokkan dalam hook Anda.

Penamaan Tool MCP

Tool MCP mengikuti pola mcp__<server>__<tool>, misalnya:

  • mcp__memory__create_entities - Tool create entities server Memory
  • mcp__filesystem__read_file - Tool read file server Filesystem
  • mcp__github__search_repositories - Tool search server GitHub

Mengkonfigurasi Hook untuk Tool MCP

Anda dapat menargetkan tool MCP spesifik atau seluruh server MCP:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
          }
        ]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [
          {
            "type": "command",
            "command": "/home/user/scripts/validate-mcp-write.py"
          }
        ]
      }
    ]
  }
}

Contoh

Untuk contoh praktis termasuk format kode, notifikasi, dan perlindungan file, lihat Contoh Lainnya dalam panduan memulai.

Pertimbangan Keamanan

Disclaimer

GUNAKAN DENGAN RISIKO SENDIRI: Hook Claude Code mengeksekusi perintah shell arbitrer pada sistem Anda secara otomatis. Dengan menggunakan hook, Anda mengakui bahwa:

  • Anda sepenuhnya bertanggung jawab atas perintah yang Anda konfigurasi
  • Hook dapat memodifikasi, menghapus, atau mengakses file apa pun yang dapat diakses akun pengguna Anda
  • Hook yang berbahaya atau ditulis dengan buruk dapat menyebabkan kehilangan data atau kerusakan sistem
  • Anthropic tidak memberikan jaminan dan tidak bertanggung jawab atas kerusakan apa pun yang diakibatkan oleh penggunaan hook
  • Anda harus menguji hook secara menyeluruh dalam lingkungan yang aman sebelum penggunaan produksi

Selalu tinjau dan pahami perintah hook apa pun sebelum menambahkannya ke konfigurasi Anda.

Praktik Terbaik Keamanan

Berikut adalah beberapa praktik kunci untuk menulis hook yang lebih aman:

  1. Validasi dan sanitasi input - Jangan pernah mempercayai data input secara membabi buta
  2. Selalu kutip variabel shell - Gunakan "$VAR" bukan $VAR
  3. Blokir path traversal - Periksa .. dalam path file
  4. Gunakan path absolut - Tentukan path lengkap untuk script (gunakan $CLAUDE_PROJECT_DIR untuk path proyek)
  5. Lewati file sensitif - Hindari .env, .git/, kunci, dll.

Keamanan Konfigurasi

Edit langsung pada hook dalam file pengaturan tidak berlaku segera. Claude Code:

  1. Mengambil snapshot hook saat startup
  2. Menggunakan snapshot ini sepanjang sesi
  3. Memperingatkan jika hook dimodifikasi secara eksternal
  4. Memerlukan tinjauan dalam menu /hooks agar perubahan berlaku

Ini mencegah modifikasi hook berbahaya mempengaruhi sesi Anda saat ini.

Detail Eksekusi Hook

  • Timeout: Batas eksekusi 60 detik secara default, dapat dikonfigurasi per perintah.
    • Timeout untuk perintah individual tidak mempengaruhi perintah lainnya.
  • Paralelisasi: Semua hook yang cocok berjalan secara paralel
  • Lingkungan: Berjalan dalam direktori saat ini dengan lingkungan Claude Code
    • Variabel lingkungan CLAUDE_PROJECT_DIR tersedia dan berisi path absolut ke direktori root proyek
  • Input: JSON melalui stdin
  • Output:
    • PreToolUse/PostToolUse/Stop: Progress ditampilkan dalam transkrip (Ctrl-R)
    • Notification: Dicatat hanya ke debug (--debug)

Debugging

Pemecahan Masalah Dasar

Jika hook Anda tidak bekerja:

  1. Periksa konfigurasi - Jalankan /hooks untuk melihat apakah hook Anda terdaftar
  2. Verifikasi sintaks - Pastikan pengaturan JSON Anda valid
  3. Uji perintah - Jalankan perintah hook secara manual terlebih dahulu
  4. Periksa izin - Pastikan script dapat dieksekusi
  5. Tinjau log - Gunakan claude --debug untuk melihat detail eksekusi hook

Masalah umum:

  • Tanda kutip tidak di-escape - Gunakan \" di dalam string JSON
  • Matcher salah - Periksa nama tool cocok persis (case-sensitive)
  • Perintah tidak ditemukan - Gunakan path lengkap untuk script

Debugging Lanjutan

Untuk masalah hook yang kompleks:

  1. Inspeksi eksekusi hook - Gunakan claude --debug untuk melihat eksekusi hook yang detail
  2. Validasi skema JSON - Uji input/output hook dengan tool eksternal
  3. Periksa variabel lingkungan - Verifikasi lingkungan Claude Code benar
  4. Uji kasus edge - Coba hook dengan path file atau input yang tidak biasa
  5. Monitor sumber daya sistem - Periksa kelelahan sumber daya selama eksekusi hook
  6. Gunakan logging terstruktur - Implementasikan logging dalam script hook Anda

Contoh Output Debug

Gunakan claude --debug untuk melihat detail eksekusi hook:

[DEBUG] Executing hooks for PostToolUse:Write
[DEBUG] Getting matching hook commands for PostToolUse with query: Write
[DEBUG] Found 1 hook matchers in settings
[DEBUG] Matched 1 hooks for query "Write"
[DEBUG] Found 1 hook commands to execute
[DEBUG] Executing hook command: <Your command> with timeout 60000ms
[DEBUG] Hook command completed with status 0: <Your stdout>

Pesan progress muncul dalam mode transkrip (Ctrl-R) menampilkan:

  • Hook mana yang berjalan
  • Perintah yang dieksekusi
  • Status sukses/gagal
  • Pesan output atau error