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 tools. 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 Khusus 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 subagents)
  • 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 dengan 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 Claude Code utama telah selesai merespons. Tidak berjalan jika penghentian terjadi karena interupsi pengguna.

SubagentStop

Berjalan ketika subagent 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 jendela konteks penuh)

SessionStart

Berjalan ketika Claude Code memulai sesi baru atau melanjutkan sesi yang ada (yang saat ini memang memulai sesi baru di balik layar). Berguna untuk memuat konteks pengembangan seperti isu yang ada atau perubahan terbaru pada codebase Anda.

Matcher:

  • startup - Dipanggil dari startup
  • resume - Dipanggil dari --resume, --continue, atau /resume
  • clear - Dipanggil dari /clear

Input Hook

Hooks menerima data JSON melalui stdin yang berisi informasi sesi dan data khusus event:

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

  // Field khusus 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": ""
}

Input SessionStart

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

Output Hook

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

Sederhana: Exit Code

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

  • Exit code 0: Sukses. stdout ditampilkan kepada pengguna dalam mode transkrip (CTRL-R), kecuali untuk UserPromptSubmit dan SessionStart, di mana stdout ditambahkan ke konteks.
  • Exit code 2: Error pemblokiran. stderr diberikan kembali ke Claude untuk diproses secara otomatis. Lihat perilaku per-hook-event di bawah.
  • Exit code lainnya: Error non-pemblokiran. 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
SessionStartN/A, menampilkan stderr hanya ke pengguna

Lanjutan: Output JSON

Hooks 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 hooks berjalan.

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

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

Kontrol Keputusan PreToolUse

Hooks PreToolUse dapat mengontrol apakah panggilan tool berlanjut.

  • "allow" melewati sistem izin. permissionDecisionReason ditampilkan kepada pengguna tetapi tidak ke Claude. (Nilai "approve" yang deprecated + reason memiliki perilaku yang sama.)
  • "deny" mencegah panggilan tool dieksekusi. permissionDecisionReason ditampilkan ke Claude. (Nilai "block" + reason memiliki perilaku yang sama.)
  • "ask" meminta pengguna untuk mengonfirmasi panggilan tool di UI. permissionDecisionReason ditampilkan kepada pengguna tetapi tidak ke 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

Hooks 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

Hooks 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

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

Kontrol Keputusan SessionStart

Hooks SessionStart memungkinkan Anda memuat konteks di awal sesi.

  • "hookSpecificOutput.additionalContext" menambahkan string ke konteks.
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "My additional context here"
  }
}

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 hooks 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

Hooks 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 hooks 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 Hooks untuk Tool MCP

Anda dapat menargetkan tool MCP spesifik atau seluruh server MCP:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "m cp__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 pemformatan kode, notifikasi, dan perlindungan file, lihat Contoh Lainnya dalam panduan memulai.

Pertimbangan Keamanan

Disclaimer

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

  • Anda sepenuhnya bertanggung jawab atas perintah yang Anda konfigurasi
  • Hooks dapat memodifikasi, menghapus, atau mengakses file apa pun yang dapat diakses akun pengguna Anda
  • Hooks 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 dari penggunaan hook
  • Anda harus menguji hooks 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 hooks 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 ke hooks dalam file pengaturan tidak berlaku segera. Claude Code:

  1. Menangkap snapshot hooks saat startup
  2. Menggunakan snapshot ini sepanjang sesi
  3. Memperingatkan jika hooks 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 hooks 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 ke debug saja (--debug)

Debugging

Pemecahan Masalah Dasar

Jika hooks Anda tidak bekerja:

  1. Periksa konfigurasi - Jalankan /hooks untuk melihat apakah hook Anda terdaftar
  2. Verifikasi sintaks - Pastikan pengaturan JSON Anda valid
  3. Tes 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 - Tes input/output hook dengan tool eksternal
  3. Periksa variabel lingkungan - Verifikasi lingkungan Claude Code benar
  4. Tes kasus edge - Coba hooks 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