SpyBara
Go Premium

hooks.md 2026-10-01 23:59 UTC to 2026-10-02 06:02 UTC

This page contains 884 additions and 873 deletions.

2026
Fri 2 07:00

Referensi hooks

Referensi untuk event hook Claude Code, skema konfigurasi, format JSON input/output, kode keluar, hooks asinkron, hooks HTTP, prompt hooks, dan MCP tool hooks.

Hooks adalah perintah shell yang ditentukan pengguna, endpoint HTTP, panggilan MCP tool, prompt LLM, atau subagent yang dijalankan secara otomatis pada titik-titik tertentu dalam siklus hidup Claude Code. Claude Code memicu event hook yang sama di mana pun ia berjalan: sesi di terminal, ekstensi IDE, aplikasi Desktop, dan Claude Code di web. Gunakan referensi ini untuk mencari skema event, opsi konfigurasi, format JSON input/output, dan fitur lanjutan seperti async hooks, HTTP hooks, dan MCP tool hooks.

Plugin juga dapat mendaftarkan hook sebagai fungsi JavaScript yang dipanggil oleh Claude Code di dalam prosesnya sendiri, yang dapat menggambar di antarmuka sekaligus bertindak atas event. Plugin yang melakukan hal tersebut adalah sebuah mod, dan hook fungsi tersebut dibahas di Bereaksi terhadap event, bukan di sini. Hook di halaman ini tetap berfungsi berdampingan dengan mod.

Siklus hidup hook

Claude Code menjalankan hooks pada titik-titik tertentu selama sesi. Ketika event dijalankan dan matcher cocok, Claude Code meneruskan konteks JSON tentang event ke handler hook Anda. Untuk command hooks, input tiba di stdin. Untuk HTTP hooks, input tiba sebagai badan permintaan POST. Handler Anda kemudian dapat memeriksa input, mengambil tindakan, dan secara opsional mengembalikan keputusan.

Events jatuh ke dalam tiga cadence:

  • per sesi: SessionStart dan SessionEnd
  • per turn: UserPromptSubmit, Stop, dan StopFailure
  • pada setiap pemanggilan tool di dalam loop agentic: PreToolUse dan PostToolUse, kecuali panggilan EndConversation, yang melewati keduanya
Diagram siklus hidup hook menunjukkan Setup opsional yang mengalir ke SessionStart, kemudian loop per-turn yang berisi UserPromptSubmit, UserPromptExpansion untuk slash commands, loop agentic bersarang (PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart/Stop, TaskCreated, TaskCompleted), dan Stop atau StopFailure, diikuti TeammateIdle, PreCompact, PostCompact, dan SessionEnd, dengan Elicitation dan ElicitationResult bersarang di dalam eksekusi MCP tool, PermissionDenied sebagai cabang samping dari PermissionRequest untuk penolakan mode otomatis, WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, FileChanged, dan DirectoryAdded sebagai event asinkron mandiri, PreModelSwitch sebagai event sekuensial mandiri yang berjalan sebelum perubahan model yang diminta, PostModelSwitch sebagai event asinkron mandiri yang berjalan setelah model sesi berubah, dan MessageDisplay sebagai event display-only yang berjalan saat teks pesan asisten streaming
<img src="https://mintcdn.com/claude-code/x7pO8l4XcvAXCoVc/images/hooks-lifecycle-dark.svg?fit=max&auto=format&n=x7pO8l4XcvAXCoVc&q=85&s=c9b3d88487335f58cce0b52e2f9e7531" className="hidden dark:block" alt="Diagram siklus hidup hook menunjukkan Setup opsional yang mengalir ke SessionStart, kemudian loop per-turn yang berisi UserPromptSubmit, UserPromptExpansion untuk slash commands, loop agentic bersarang (PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart/Stop, TaskCreated, TaskCompleted), dan Stop atau StopFailure, diikuti TeammateIdle, PreCompact, PostCompact, dan SessionEnd, dengan Elicitation dan ElicitationResult bersarang di dalam eksekusi MCP tool, PermissionDenied sebagai cabang samping dari PermissionRequest untuk penolakan mode otomatis, WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, FileChanged, dan DirectoryAdded sebagai event asinkron mandiri, PreModelSwitch sebagai event sekuensial mandiri yang berjalan sebelum perubahan model yang diminta, PostModelSwitch sebagai event asinkron mandiri yang berjalan setelah model sesi berubah, dan MessageDisplay sebagai event display-only yang berjalan saat teks pesan asisten streaming" width="520" height="1336" data-path="images/hooks-lifecycle-dark.svg" />

Tabel di bawah merangkum kapan setiap event dijalankan. Bagian Hook events mendokumentasikan skema input lengkap dan opsi kontrol keputusan untuk masing-masing.

Event Kapan event ini dipicu
SessionStart Ketika sesi dimulai atau dilanjutkan
Setup Ketika Anda memulai Claude Code dengan --init-only, atau dengan --init atau --maintenance dalam mode -p. Untuk persiapan satu kali dalam CI atau skrip
UserPromptSubmit Ketika Anda mengirimkan prompt, sebelum Claude memprosesnya
UserPromptExpansion Ketika perintah yang diketik pengguna berkembang menjadi prompt, sebelum mencapai Claude. Dapat memblokir ekspansi
PreToolUse Sebelum panggilan alat dieksekusi. Dapat memblokir
PermissionRequest Ketika panggilan alat memerlukan keputusan izin
PermissionDenied Ketika mode otomatis menolak panggilan alat, termasuk penolakan tanpa putusan classifier. Gunakan JSON hookSpecificOutput.retry: true untuk memberitahu model bahwa mungkin dapat mencoba ulang panggilan alat yang ditolak. Claude Code mengabaikan retry ketika classifier tidak menghasilkan putusan
PostToolUse Setelah panggilan alat berhasil
PostToolUseFailure Setelah panggilan alat gagal
PostToolBatch Setelah batch lengkap panggilan alat paralel terselesaikan, sebelum panggilan model berikutnya
Notification Ketika Claude Code mengirimkan notifikasi
MessageDisplay Saat teks pesan asisten ditampilkan
SubagentStart Ketika subagent dimulai
SubagentStop Ketika subagent selesai
TaskCreated Ketika tugas sedang dibuat melalui TaskCreate
TaskCompleted Ketika tugas sedang ditandai sebagai selesai
Stop Ketika Claude selesai merespons
StopFailure Ketika giliran berakhir karena kesalahan API
TeammateIdle Ketika rekan tim agent team akan menjadi idle
InstructionsLoaded Ketika file CLAUDE.md atau .claude/rules/*.md dimuat ke dalam konteks. Dipicu saat awal sesi dan ketika file dimuat dengan malas selama sesi
ConfigChange Ketika file konfigurasi berubah selama sesi
CwdChanged Ketika direktori kerja berubah, misalnya ketika Claude mengeksekusi perintah cd. Berguna untuk manajemen lingkungan reaktif dengan alat seperti direnv
DirectoryAdded Ketika direktori kerja ditambahkan di tengah sesi melalui /add-dir atau permintaan kontrol SDK register_repo_root
FileChanged Ketika file yang dipantau berubah di disk. Bidang matcher menentukan nama file mana yang dipantau
WorktreeCreate Ketika worktree sedang dibuat melalui --worktree, isolation: "worktree", atau untuk sesi latar belakang. Menggantikan perilaku git default
WorktreeRemove Ketika worktree sedang dihapus saat keluar sesi, ketika subagent selesai, atau ketika Anda menghapus sesi latar belakang
PreCompact Sebelum pemadatan konteks
PostCompact Setelah pemadatan konteks selesai
PreModelSwitch Sebelum Claude Code menerapkan pergantian model yang Anda atau klien minta. Dapat memblokir pergantian
PostModelSwitch Setelah model sesi berubah, termasuk perubahan yang Claude Code lakukan sendiri, seperti memulihkan model ketika Anda melanjutkan sesi
Elicitation Ketika server MCP meminta input pengguna selama panggilan alat
ElicitationResult Setelah pengguna merespons elicitation MCP, sebelum respons dikirim kembali ke server
SessionEnd Ketika sesi berakhir

Bagaimana hook diselesaikan

Untuk melihat bagaimana event, matcher, dan handler cocok bersama, pertimbangkan hook PreToolUse ini yang memblokir perintah shell yang merusak.

matcher mempersempit ke pemanggilan tool Bash dan kondisi if mempersempit lebih lanjut ke subperintah Bash yang cocok dengan rm *, jadi block-rm.sh hanya spawn ketika kedua filter cocok:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}

Skrip membaca input JSON dari stdin, mengekstrak perintah, dan mengembalikan permissionDecision dari "deny" jika berisi rm -rf. Simpan ke .claude/hooks/block-rm.sh di proyek Anda dan buat dapat dieksekusi dengan chmod +x .claude/hooks/block-rm.sh sehingga Claude Code dapat menjalankannya:

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0  # no decision; normal permission flow applies
fi

Skrip ini, seperti contoh Bash lainnya di halaman ini yang mengurai input JSON, menggunakan jq, jadi instal jq dan pastikan itu ada di PATH Anda sebelum mencobanya.

Sekarang anggaplah Claude Code memutuskan untuk menjalankan Bash "rm -rf /tmp/build" terhadap konfigurasi macOS/Linux. Inilah yang terjadi:

Diagram resolusi hook: PreToolUse dijalankan, matcher memeriksa kecocokan Bash, kemudian kondisi if memeriksa kecocokan Bash(rm *). Jika keduanya cocok, perintah hook dijalankan dan mengembalikan permissionDecision deny, jadi pemanggilan tool diblokir dan Claude Code melanjutkan. Jika salah satu pemeriksaan gagal cocok, hook dilewati dan pemanggilan tool diizinkan untuk melanjutkan. Diagram resolusi hook: PreToolUse dijalankan, matcher memeriksa kecocokan Bash, kemudian kondisi if memeriksa kecocokan Bash(rm *). Jika keduanya cocok, perintah hook dijalankan dan mengembalikan permissionDecision deny, jadi pemanggilan tool diblokir dan Claude Code melanjutkan. Jika salah satu pemeriksaan gagal cocok, hook dilewati dan pemanggilan tool diizinkan untuk melanjutkan.
1

Event dijalankan

Event PreToolUse dijalankan. Claude Code mengirimkan input tool sebagai JSON di stdin ke hook:

{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
2

Matcher memeriksa

Matcher "Bash" cocok dengan nama tool, jadi grup hook ini diaktifkan. Jika Anda menghilangkan matcher atau menggunakan "*", grup diaktifkan pada setiap kemunculan event.

3

Kondisi if memeriksa

Kondisi if "Bash(rm *)" cocok karena rm -rf /tmp/build adalah subperintah yang cocok dengan rm *, jadi handler ini spawn. Jika perintah telah npm test, pemeriksaan if akan gagal dan block-rm.sh tidak akan pernah dijalankan, menghindari overhead spawn proses. Bidang if bersifat opsional; tanpanya, setiap handler dalam grup yang cocok dijalankan.

4

Handler hook dijalankan

Skrip memeriksa perintah lengkap dan menemukan rm -rf, jadi itu mencetak keputusan ke stdout:

{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}

Jika perintah telah menjadi varian rm yang lebih aman seperti rm file.txt, skrip akan mencapai exit 0 sebagai gantinya. Kode keluar 0 tanpa output berarti hook tidak memiliki keputusan untuk dilaporkan, jadi pemanggilan tool berlanjut melalui alur izin normal. Hook dapat menolak pemanggilan, tetapi tetap diam tidak menyetujuinya.

5

Claude Code bertindak atas hasil

Claude Code membaca keputusan JSON, memblokir pemanggilan tool, dan menunjukkan Claude alasannya.

Bagian Configuration di bawah mendokumentasikan skema lengkap, dan setiap bagian hook event mendokumentasikan input apa yang diterima perintah Anda dan output apa yang dapat dikembalikan.

Konfigurasi

Hooks didefinisikan dalam file pengaturan JSON. Konfigurasi memiliki tiga tingkat nesting:

  1. Pilih hook event untuk merespons, seperti PreToolUse atau Stop
  2. Tambahkan matcher group untuk memfilter kapan dijalankan, seperti "hanya untuk tool Bash"
  3. Tentukan satu atau lebih hook handlers untuk dijalankan saat cocok

Lihat Bagaimana hook diselesaikan di atas untuk panduan lengkap dengan contoh beranotasi.

Lokasi hook

Tempat Anda mendefinisikan hook menentukan cakupannya:

Lokasi Cakupan Dapat Dibagikan
~/.claude/settings.json Semua proyek Anda Tidak, lokal ke mesin Anda
.claude/settings.json Proyek tunggal Ya, dapat dikomit ke repo
.claude/settings.local.json Proyek tunggal Tidak, gitignored saat Claude Code menyimpan pengaturan ke dalamnya
Pengaturan kebijakan terkelola Seluruh organisasi Ya, dikendalikan admin
Plugin hooks/hooks.json Ketika plugin diaktifkan Ya, dibundel dengan plugin
Skill frontmatter Sisa sesi setelah skill dipanggil. Lihat Hooks dalam skills dan agents Ya, didefinisikan dalam file skill
Subagent frontmatter Saat subagent itu berjalan Ya, didefinisikan dalam file subagent

Sesi cloud di Claude Code di web tidak membaca ~/.claude/settings.json lokal Anda. Dalam lingkungan self-hosted, Claude Code juga menjalankan hooks yang operator semai dari ~/.claude/ host runner, dan menjalankan hooks dalam file pengaturan terkelola image runner saat file itu termasuk dalam sumber terkelola yang Claude Code terapkan, yang secara default berarti hanya ketika pengaturan yang dikelola server maupun kebijakan Claude Code yang dikirimkan MDM tidak menyediakan tingkat terkelola. Lihat apa yang terbawa dari setup Anda untuk file pengaturan dan plugin mana, dan dengan demikian hooks mana, yang mencapai sesi cloud.

Untuk detail tentang resolusi file pengaturan, lihat settings.

Hooks dari file pengaturan, pengaturan kebijakan terkelola, dan plugin juga berjalan di dalam subagents. Ketika subagent memanggil tool, tool events seperti PreToolUse dan PostToolUse menjalankan hooks yang dikonfigurasi sama seperti dalam percakapan utama, dan input membawa bidang input umum agent_id dan agent_type yang mengidentifikasi subagent.

Administrator dapat menggunakan allowManagedHooksOnly dalam pengaturan terkelola untuk membatasi hooks mana yang berjalan:

  • Hooks pengguna, proyek, lokal, dan plugin Anda diblokir. Hooks dari plugins yang dipaksa-aktifkan dalam pengaturan terkelola enabledPlugins dikecualikan
  • Claude Code juga mempersempit pengaturan statusLine, fileSuggestion, dan subagentStatusLine Anda ke pengaturan terkelola
  • Claude Code juga menonaktifkan plugins dengan sumber command, termasuk plugins yang dipaksa-aktifkan dalam pengaturan terkelola enabledPlugins, kecuali disableCommandPluginSources secara eksplisit diatur ke false. Sumber command memerlukan Claude Code v2.1.229 atau lebih baru
  • Claude Code juga memblokir perintah headersHelper marketplace kecuali disableCommandPluginSources secara eksplisit diatur ke false, kecuali untuk marketplace yang pengaturan terkelola sendiri deklarasikan

Lihat apa yang berjalan di bawah allowManagedHooksOnly.

Hook entries bergabung di seluruh tingkat pengaturan daripada menggantikan satu sama lain: pengaturan pengguna, proyek, dan lokal menambahkan hooks mereka sendiri tanpa menghapus yang terkelola, dan pengaturan disableAllHooks tidak dapat menonaktifkan hooks terkelola dari luar pengaturan terkelola.

HTTP hook allowlists berlaku untuk hooks dari setiap sumber, termasuk pengaturan kebijakan terkelola:

  • allowedHttpHookUrls: ketika didefinisikan di tingkat pengaturan apa pun, Claude Code menjalankan HTTP hook handler hanya jika URL-nya cocok dengan allowlist yang digabungkan
  • httpHookAllowedEnvVars: ketika didefinisikan, Claude Code menginterpolasi hanya variabel lingkungan pada daftar itu ke dalam header hook

Pola matcher

Bidang matcher memfilter kapan hooks dijalankan. Bagaimana matcher dievaluasi tergantung pada karakter yang dikandungnya:

Nilai matcher Dievaluasi sebagai Contoh
"*", "", atau dihilangkan Cocokkan semua dijalankan pada setiap kemunculan event
Hanya huruf, digit, _, -, spasi, ,, dan | String yang tepat, atau daftar string yang tepat dipisahkan | atau , dengan whitespace opsional di sekitarnya Bash cocok hanya dengan tool Bash; Edit|Write dan Edit, Write masing-masing cocok dengan salah satu tool dengan tepat; code-reviewer cocok hanya dengan tipe agent itu
Berisi karakter lain apa pun Ekspresi reguler JavaScript, tidak berlabuh ^Notebook cocok dengan tool apa pun yang dimulai dengan Notebook; mcp__memory__.* cocok dengan setiap tool dari server memory

Matcher pada jalur ekspresi reguler diuji dengan RegExp.prototype.test JavaScript, yang berhasil pada kecocokan di mana pun dalam nilai. Edit.* cocok dengan Edit dan NotebookEdit; bungkus pola dalam ^ dan $, seperti ^Edit$, ketika Anda memerlukan kecocokan seluruh string.

FileChanged dan StopFailure menggunakan set exact-match yang lebih sempit dari huruf, digit, _, dan | saja. Tanda hubung, spasi, atau koma dalam matcher untuk dua event itu membuat tetap pada jalur ekspresi reguler, dan hanya | yang memisahkan alternatif. Setiap event lain dengan dukungan matcher dalam tabel yang mengikuti menerima | atau ,.

Event FileChanged tidak mengikuti aturan ini saat membangun daftar watch-nya. Lihat FileChanged.

Setiap tipe event cocok pada bidang yang berbeda:

Event Apa yang difilter matcher Contoh nilai matcher
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied nama tool Bash, Edit|Write, mcp__.*
SessionStart bagaimana sesi dimulai startup, resume, clear, compact, fork
Setup flag CLI mana yang memicu setup init, maintenance
SessionEnd mengapa sesi berakhir clear, resume, logout, prompt_input_exit, other
Notification tipe notifikasi permission_prompt, idle_prompt, auth_success, elicitation_dialog, elicitation_url_dialog, elicitation_complete, elicitation_response, agent_needs_input, agent_completed, quota_auto_resume_fired, quota_auto_resume_stale, quota_auto_resume_disabled
SubagentStart tipe agent general-purpose, Explore, Plan, nama agent kustom, atau nama dengan cakupan plugin seperti ^my-plugin:reviewer$
PreCompact, PostCompact apa yang memicu compaction manual, auto
PreModelSwitch, PostModelSwitch nama kanonik model yang sesi beralih ke, seperti dijelaskan di bawah PreModelSwitch claude-opus-5, claude-opus-4-6|claude-opus-5, .*opus.*
SubagentStop tipe agent nilai yang sama seperti SubagentStart
ConfigChange sumber konfigurasi user_settings, project_settings, local_settings, policy_settings, skills
CwdChanged tidak ada dukungan matcher selalu dijalankan pada setiap kemunculan
DirectoryAdded bagaimana direktori ditambahkan slash_command, register_repo_root
FileChanged nama file literal untuk ditonton (lihat FileChanged) .envrc|.env
StopFailure tipe kesalahan rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, account_on_hold, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, cloud_credential_error, unknown
InstructionsLoaded alasan load session_start, nested_traversal, path_glob_match, include, compact
UserPromptExpansion nama command nama skill atau command Anda
Elicitation nama server MCP nama server MCP yang dikonfigurasi Anda
ElicitationResult nama server MCP nilai yang sama seperti Elicitation
UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, MessageDisplay tidak ada dukungan matcher selalu dijalankan pada setiap kemunculan

Mencocokkan StopFailure pada cloud_credential_error memerlukan Claude Code v2.1.267 atau lebih baru, versi pertama yang melaporkan kegagalan pemuatan kredensial di bawah nilai itu daripada server_error atau unknown.

Untuk sebagian besar events, Claude Code mengevaluasi matcher terhadap bidang dari JSON input yang dikirimkan ke hook Anda di stdin. Untuk tool events, bidang itu adalah tool_name. Untuk PreModelSwitch dan PostModelSwitch, Claude Code mengevaluasi matcher terhadap nama kanonik yang diturunkan dari to_model, seperti dijelaskan di bawah PreModelSwitch. Setiap bagian hook event mencantumkan set lengkap nilai matcher dan skema input untuk event itu.

Contoh ini menjalankan skrip linting hanya ketika Claude menulis atau mengedit file:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/lint-check.sh"
          }
        ]
      }
    ]
  }
}

Jika Anda menambahkan bidang matcher ke event tanpa dukungan matcher, itu diabaikan secara diam-diam.

Untuk tool events, Anda dapat memfilter lebih sempit dengan menetapkan bidang if pada handler hook individual. if menggunakan sintaks aturan izin untuk mencocokkan terhadap nama tool dan argumen bersama-sama, jadi "Bash(git *)" dijalankan ketika subperintah apa pun dari input Bash cocok dengan git * dan "Edit(*.ts)" dijalankan hanya untuk file TypeScript.

Cocokkan MCP tools

Tool server MCP muncul sebagai tool reguler dalam tool events (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied), jadi Anda dapat mencocokkannya dengan cara yang sama seperti Anda mencocokkan nama tool lainnya.

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

  • mcp__memory__create_entities: tool create entities dari Memory server
  • mcp__filesystem__read_file: tool read file dari Filesystem server
  • mcp__github__search_repositories: tool search dari GitHub server

Untuk mencocokkan setiap tool dari server, tambahkan .* ke awalan server. .* diperlukan: matcher seperti mcp__memory atau mcp__brave-search hanya berisi karakter exact-match, jadi dibandingkan sebagai string yang tepat dan tidak cocok dengan tool apa pun.

  • mcp__memory__.* cocok dengan semua tools dari server memory
  • mcp__brave-search__.* cocok dengan semua tools dari server yang namanya berisi tanda hubung
  • mcp__.*__write.* cocok dengan tool apa pun yang namanya dimulai dengan write dari server apa pun

Tools dari plugin-bundled MCP server menggunakan segmen server yang dibatasi yang mencakup nama plugin: mcp__plugin_<plugin-name>_<server-name>__<tool>. Matcher yang ditulis terhadap kunci server bare tidak pernah dijalankan untuk tools ini. Untuk plugin bernama my-plugin yang membundel server di bawah kunci db, tool query muncul sebagai mcp__plugin_my-plugin_db__query, jadi matcher untuk setiap tool dari server itu adalah mcp__plugin_my-plugin_db__.*. Gunakan nama tool yang dibatasi yang sama dalam bidang if handler. Lihat Plugin-provided MCP servers untuk bagaimana nama yang dibatasi dibangun.

Contoh ini mencatat semua operasi memory server dan memvalidasi operasi write dari server MCP apa pun:

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

Bidang hook handler

Setiap objek dalam array hooks inner adalah hook handler: perintah shell, endpoint HTTP, tool MCP, prompt LLM, atau agent yang dijalankan saat matcher cocok. Ada lima tipe:

  • Command hooks (type: "command"): jalankan perintah shell. Skrip Anda menerima JSON input event di stdin dan mengkomunikasikan hasil kembali melalui kode keluar dan stdout.
  • HTTP hooks (type: "http"): kirimkan JSON input event sebagai permintaan HTTP POST ke URL. Endpoint mengkomunikasikan hasil kembali melalui badan respons menggunakan format JSON output yang sama seperti command hooks.
  • MCP tool hooks (type: "mcp_tool"): panggil tool pada server MCP yang dikonfigurasi. Output teks tool diperlakukan seperti command-hook stdout.
  • Prompt hooks (type: "prompt"): kirimkan prompt ke model Claude untuk evaluasi single-turn. Model mengembalikan keputusannya sebagai JSON. Lihat Prompt-based hooks.
  • Agent hooks (type: "agent"): spawn subagent yang dapat menggunakan tools seperti Read, Grep, dan Glob untuk memverifikasi kondisi sebelum mengembalikan keputusan. Agent hooks adalah eksperimental dan mungkin berubah. Lihat Agent-based hooks.

Semua matching hooks berjalan secara paralel. Jika Anda mendefinisikan handler yang sama di lebih dari satu file pengaturan, itu berjalan sekali. Salinan handler yang sama dari plugin atau skill tetap terpisah.

Handlers berjalan di direktori saat ini dengan lingkungan Claude Code. Jika direktori saat ini tidak lagi ada, misalnya worktree atau direktori temp yang shell lain hapus di tengah-sesi, Claude Code menjalankan command hooks dari yang pertama dari ini yang masih ada: direktori tempat sesi dimulai, akar proyek, direktori home Anda, atau direktori temp sistem. Claude Code mencatat warning yang menamai direktori fallback dalam debug log.

Variabel lingkungan $CLAUDE_CODE_REMOTE adalah "true" di lingkungan web jarak jauh dan tidak diatur di CLI lokal. Claude Code v2.1.199 dan lebih baru menetapkan $CLAUDE_CODE_BRIDGE_SESSION_ID ke Remote Control session ID saat sesi lokal memiliki koneksi Remote Control yang aktif.

Bidang umum

Bidang-bidang ini berlaku untuk semua tipe hook:

Bidang Diperlukan Deskripsi
type ya "command", "http", "mcp_tool", "prompt", atau "agent"
if tidak Sintaks aturan izin untuk memfilter kapan hook ini dijalankan, seperti "Bash(git *)" atau "Edit(*.ts)". Hook command hanya dijalankan jika pemanggilan tool cocok dengan pola. Lihat tabel Bash matching di bawah untuk bagaimana pola Bash dievaluasi terhadap subperintah, $(), dan backticks. Hanya dievaluasi pada tool events: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, dan PermissionDenied. Pada event lain, hook dengan if yang ditetapkan tidak akan pernah dijalankan. Menggunakan sintaks yang sama seperti aturan izin
timeout tidak Detik sebelum membatalkan. Claude Code tidak memberlakukannya pada command hook yang Anda jalankan dengan async: true. Default: 600 untuk command, http, dan mcp_tool; 30 untuk prompt; 60 untuk agent. Claude Code menurunkan default command, http, dan mcp_tool menjadi 30 pada UserPromptSubmit, PreModelSwitch, dan PostModelSwitch, dan menjadi 10 pada MessageDisplay. Hook SessionEnd berbagi anggaran 1,5 detik; jika pengaturan Anda menetapkan timeout per-hook yang lebih lama, Claude Code menaikkan anggaran untuk mencocokkan, hingga 60 detik
statusMessage tidak Pesan spinner kustom ditampilkan saat hook dijalankan
once tidak Jika true, Claude Code menghapus hook setelah run pertamanya yang berhasil. Run yang gagal, memblokir dengan kode keluar 2, atau timeout meninggalkan hook di tempat, jadi berjalan lagi pada event yang cocok berikutnya. Hanya dihormati untuk hooks yang dideklarasikan dalam skill frontmatter; diabaikan dalam file pengaturan dan agent frontmatter

Bidang if menyimpan tepat satu aturan izin. Tidak ada sintaks &&, ||, atau list untuk menggabungkan aturan; untuk menerapkan beberapa kondisi, tentukan handler hook terpisah untuk masing-masing.

Dalam kondisi if untuk file tool, pola direktori single-segment seperti "Edit(src/**)" cocok hanya dengan direktori src di direktori kerja dan file di bawahnya. Untuk mencocokkan direktori bernama src di kedalaman apa pun, tulis "Edit(**/src/**)". Sebelum v2.1.214, "Edit(src/**)" cocok dengan direktori bernama src di kedalaman apa pun di bawah direktori kerja.

Untuk pola Bash, apakah hook command Anda dijalankan tergantung pada bentuk pola dan perintah Bash yang Claude panggil. Penugasan VAR=value terkemuka dihapus sebelum pencocokan.

Pola if Perintah Bash Hook dijalankan? Mengapa
Bash(git *) FOO=bar git push ya penugasan terkemuka dihapus; git push cocok
Bash(git *) npm test && git push ya setiap subperintah diperiksa; git push cocok
Bash(rm *) echo $(rm -rf /) ya perintah di dalam $() dan backticks diperiksa; rm -rf / cocok
Bash(rm *) echo $(date) tidak tidak ada subperintah yang cocok dengan rm *
Bash(git push *) echo $(date) ya pola yang menentukan lebih dari nama perintah menjalankan hook bagaimanapun pada $(), backticks, atau $VAR

Ketika Claude Code tidak dapat menentukan perintah mana yang dijalankan input Bash, itu menjalankan hook Anda terlepas dari pola. Karena filter if adalah best-effort, gunakan sistem izin daripada hook untuk memberlakukan allow atau deny yang keras.

Bidang command hook

Selain bidang umum, command hooks menerima bidang-bidang ini:

Bidang Diperlukan Deskripsi
command ya Perintah shell untuk dijalankan. Dengan args, executable untuk spawn secara langsung. Lihat Exec form dan shell form
args tidak Daftar argumen. Ketika ada, command diselesaikan sebagai executable dan di-spawn secara langsung dengan args sebagai vektor argumen, tanpa shell yang terlibat. Lihat Exec form dan shell form
async tidak Jika true, dijalankan di latar belakang tanpa memblokir. Lihat Run hooks in the background
asyncRewake tidak Jika true, dijalankan di latar belakang dan membangunkan Claude pada kode keluar 2. Hook stderr, atau stdout jika stderr kosong, ditampilkan ke Claude sebagai system reminder sehingga dapat bereaksi terhadap kegagalan latar belakang yang berjalan lama
shell tidak Shell untuk digunakan untuk hook ini. Menerima "bash" atau "powershell". Default ke "bash", atau ke "powershell" di Windows ketika Git Bash tidak diinstal. Menetapkan "powershell" menjalankan perintah melalui PowerShell di Windows. Tidak memerlukan CLAUDE_CODE_USE_POWERSHELL_TOOL karena hooks spawn PowerShell secara langsung. Diabaikan ketika args diatur
Exec form dan shell form

Hook command dijalankan sebagai exec form ketika args diatur, dan shell form ketika args dihilangkan. Atur args setiap kali hook mereferensikan path placeholder, karena setiap elemen dilewatkan sebagai satu argumen tanpa quoting. Hilangkan args ketika Anda memerlukan fitur shell seperti pipes atau &&, atau ketika tidak ada kekhawatiran yang berlaku.

Exec form dijalankan ketika args ada. Claude Code menyelesaikan command sebagai executable di PATH dan spawn-nya secara langsung dengan args sebagai vektor argumen. Tidak ada shell, jadi setiap elemen args adalah satu argumen persis seperti yang ditulis, dan path placeholders seperti ${CLAUDE_PLUGIN_ROOT} disubstitusi ke dalam command dan ke dalam setiap elemen args sebagai string biasa. Karakter khusus seperti apostrophe, $, dan backticks melewati verbatim karena tidak ada shell untuk menginterpretasinya. Tidak ada tokenisasi shell yang terjadi di platform apa pun.

Shell form dijalankan ketika args tidak ada. String command dilewatkan ke shell: sh -c di macOS dan Linux, Git Bash di Windows, atau PowerShell ketika Git Bash tidak diinstal. Atur bidang shell untuk memilih secara eksplisit. Shell melakukan tokenisasi string, memperluas variabel, dan menginterpretasi pipes, &&, redirects, dan globs.

Contoh ini menjalankan skrip Node yang dibundel dengan plugin. Exec form melewatkan path skrip yang diselesaikan sebagai satu argumen tanpa quoting:

{
  "type": "command",
  "command": "node",
  "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/format.js", "--fix"]
}

Shell form yang setara memerlukan quoting untuk menangani paths dengan spasi atau karakter khusus:

{
  "type": "command",
  "command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/format.js --fix"
}

Kedua form mendukung path placeholders yang sama, dan keduanya mengekspornya sebagai variabel lingkungan CLAUDE_PROJECT_DIR, CLAUDE_PLUGIN_ROOT, dan CLAUDE_PLUGIN_DATA pada proses yang di-spawn, jadi skrip dapat membaca process.env.CLAUDE_PLUGIN_ROOT terlepas dari bagaimana itu diluncurkan.

Plugin hooks juga mensubstitusi nilai ${user_config.*}, dalam exec form saja: nilai disubstitusi ke dalam command dan ke dalam setiap elemen args sebagai string biasa, jadi tidak ada shell yang mem-parse ulangnya.

Hook plugin bentuk shell yang command-nya mereferensikan ${user_config.*} gagal dengan error daripada menjalankan. Untuk menggunakan nilai opsi dari hook bentuk shell, baca variabel lingkungan $CLAUDE_PLUGIN_OPTION_<KEY>, seperti $CLAUDE_PLUGIN_OPTION_WEBHOOK_URL untuk opsi webhook_url, atau atur args untuk beralih hook ke exec form. Sebelum v2.1.207, hook plugin bentuk shell juga mensubstitusi ${user_config.*}.

Bidang HTTP hook

Selain bidang umum, HTTP hooks menerima bidang-bidang ini:

Bidang Diperlukan Deskripsi
url ya URL untuk mengirimkan permintaan POST ke
headers tidak Header HTTP tambahan sebagai pasangan kunci-nilai. Nilai mendukung interpolasi variabel lingkungan menggunakan sintaks $VAR_NAME atau ${VAR_NAME}. Hanya variabel yang tercantum dalam allowedEnvVars yang diselesaikan
allowedEnvVars tidak Daftar nama variabel lingkungan yang dapat diinterpolasi ke nilai header. Referensi ke variabel yang tidak tercantum diganti dengan string kosong. Diperlukan untuk interpolasi variabel env apa pun untuk bekerja

Claude Code mengirimkan JSON input hook sebagai badan permintaan POST dengan Content-Type: application/json. Badan respons menggunakan format JSON output yang sama seperti command hooks.

Penanganan kesalahan berbeda dari command hooks; lihat HTTP response handling.

Contoh ini mengirimkan event PreToolUse ke layanan validasi lokal, mengautentikasi dengan token dari variabel lingkungan MY_TOKEN:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:8080/hooks/pre-tool-use",
            "timeout": 30,
            "headers": {
              "Authorization": "Bearer $MY_TOKEN"
            },
            "allowedEnvVars": ["MY_TOKEN"]
          }
        ]
      }
    ]
  }
}

Bidang MCP tool hook

Selain bidang umum, MCP tool hooks menerima bidang-bidang ini:

Bidang Diperlukan Deskripsi
server ya Nama server MCP yang dikonfigurasi. Untuk plugin-bundled server, ini adalah nama yang dibatasi plugin:<plugin-name>:<server-name>, seperti plugin:my-plugin:db, bukan kunci server bare
tool ya Nama tool untuk dipanggil di server itu
input tidak Argumen yang dilewatkan ke tool. Nilai string mendukung substitusi ${path} dari JSON input hook, seperti "${tool_input.file_path}"

Contoh ini memanggil tool security_scan pada server MCP my_server setelah setiap Write atau Edit, melewatkan path file yang diedit:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "mcp_tool",
            "server": "my_server",
            "tool": "security_scan",
            "input": { "file_path": "${tool_input.file_path}" }
          }
        ]
      }
    ]
  }
}
Bagaimana hasil tool dibaca

Claude Code membaca konten teks tool dengan cara yang sama seperti membaca command-hook stdout, mengikuti aturan parsing di bawah exit code 0. Jika tool mengembalikan isError: true, hook menghasilkan kesalahan non-blocking dan eksekusi berlanjut.

Ketika server masih dalam proses terhubung

Pada event di mana hook dapat memblokir atau mengubah hasil, seperti PreToolUse atau Stop, Claude Code menunggu server yang sedang terhubung sebelum memanggil tool, paling lama MCP_TIMEOUT dan dalam batas timeout milik hook itu sendiri. Pada event observasional, seperti Notification atau SessionEnd, Claude Code tidak menunggu.

Server yang menampilkan status cached terhubung ketika hook memanggil tool-nya. Jika server belum terhubung pada saat itu, hook menghasilkan kesalahan non-blocking dan eksekusi berlanjut. Hook tidak pernah memulai alur OAuth, jadi autentikasi server dari /mcp terlebih dahulu.

Event yang dijalankan sebelum server MCP tersedia

SessionStart saat peluncuran, termasuk dengan --continue atau --resume, dan setiap event Setup dijalankan sebelum server MCP sesi tersedia untuk hook. Claude Code melewatkan hook mcp_tool mereka tanpa memanggil tool, dan debug log mencatat mcp_tool hooks are not available for the 'SessionStart' hook event (no MCP client context), atau pesan yang sama yang menyebut Setup. Ketika SessionStart dijalankan lagi nanti dalam sesi, setelah /clear atau compaction, hook mcp_tool-nya berjalan. Untuk apa pun yang dibutuhkan sesi saat peluncuran, gunakan hook type: "command" pada SessionStart sebagai gantinya.

Bidang prompt dan agent hook

Selain bidang umum, prompt dan agent hooks menerima bidang-bidang ini:

Bidang Diperlukan Deskripsi
prompt ya Teks prompt untuk dikirim ke model. Gunakan $ARGUMENTS sebagai placeholder untuk JSON input hook. Escape dengan backslash untuk menyertakan teks literal: \$1.00 dirender sebagai $1.00
model tidak Model untuk digunakan untuk evaluasi. Default ke model yang digunakan Claude Code untuk fungsionalitas latar belakang

Referensi skrip berdasarkan path

Gunakan placeholders ini untuk mereferensikan skrip hook relatif terhadap akar proyek atau plugin, terlepas dari direktori kerja saat hook dijalankan:

  • ${CLAUDE_PROJECT_DIR}: akar proyek tempat sesi dimulai. Claude Code juga menetapkan variabel ini dalam lingkungan stdio MCP servers dan plugin LSP servers.
  • ${CLAUDE_PLUGIN_ROOT}: direktori instalasi plugin, untuk skrip yang dibundel dengan plugin. Lihat plugin environment variables untuk bagaimana path berperilaku di seluruh pembaruan.
  • ${CLAUDE_PLUGIN_DATA}: direktori data persisten plugin, untuk dependensi dan status yang harus bertahan pembaruan plugin.

Lebih suka exec form untuk hook apa pun yang mereferensikan path placeholder. Dalam shell form, bungkus setiap placeholder dalam tanda kutip ganda.

Contoh ini menggunakan ${CLAUDE_PROJECT_DIR} untuk menjalankan pemeriksa gaya dari direktori .claude/hooks/ proyek setelah pemanggilan tool Write atau Edit apa pun:

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

Hooks dalam skills dan agents

Selain file pengaturan dan plugin, hooks dapat didefinisikan langsung dalam skills dan subagents menggunakan frontmatter, dalam format konfigurasi yang sama seperti hooks berbasis pengaturan. Berapa lama Claude Code menyimpannya terdaftar tergantung pada komponen:

  • Subagent hooks: Claude Code menjalankannya hanya saat subagent itu berjalan dan menghapusnya saat selesai. Claude Code mengkonversi hook Stop di sini menjadi SubagentStop, event yang dijalankan saat subagent selesai.
  • Skill hooks: Claude Code mendaftarkannya ketika Anda atau Claude memanggil skill dan terus menjalankannya untuk sisa sesi, pada turns setelah turn skill sendiri juga. Untuk membuat Claude Code menghapus hook setelah run pertamanya yang berhasil, atur once: true padanya.

Skill ini mendefinisikan hook PreToolUse yang menjalankan skrip validasi keamanan sebelum setiap perintah Bash:

---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/security-check.sh"
---

Subagents menggunakan format yang sama dalam frontmatter YAML mereka.

Frontmatter hooks dalam skill proyek mengikuti workspace trust rule yang sama seperti hooks dalam file pengaturan. Claude Code mendaftarkannya ketika Anda atau Claude memanggil skill, termasuk dalam run -p dalam folder yang belum Anda percayai.

Frontmatter hooks dalam subagent proyek berjalan hanya setelah Anda menerima workspace trust dialog untuk folder file agent berasal. Sesi -p tidak dihitung sebagai menerimanya. Apa yang berjalan sebelum Anda mempercayai folder membandingkan ini dengan rule file pengaturan, dan halaman subagents mencantumkan cakupan mana yang dikecualikan. Sebelum v2.1.218, hooks ini dapat berjalan dari folder yang belum Anda percayai.

Menu `/hooks`

Ketik /hooks di Claude Code untuk membuka browser read-only untuk hooks yang telah Anda konfigurasi. Daftar memberi label pada setiap hook dengan asalnya, seperti pengaturan pengguna, pengaturan proyek, pengaturan lokal, plugin, atau sesi saat ini.

Pilih hook untuk melihat teks lengkap dari apa yang dijalankannya dan di mana hook itu didefinisikan, seperti path file pengaturannya atau nama plugin-nya.

Untuk menelusuri semua hook event, termasuk yang tidak memiliki hooks yang dikonfigurasi, pilih All events di akhir daftar.

Nonaktifkan atau hapus hooks

Untuk menghapus hook yang didefinisikan dalam file pengaturan, hapus entrinya dari file tersebut.

Untuk menonaktifkan semua hooks sementara tanpa menghapusnya, atur "disableAllHooks": true dalam file pengaturan Anda. Claude Code membaca nilai yang tersisa setelah settings precedence diterapkan, jadi "disableAllHooks": false dalam .claude/settings.json proyek menimpa true dalam pengaturan pengguna Anda. Untuk mematikan hooks untuk satu run apa pun yang dikatakan pengaturan proyek, lewatkan --settings '{"disableAllHooks": true}', yang mengambil prioritas atas pengaturan proyek dan lokal. Tidak ada cara untuk menonaktifkan hook individual sambil menyimpannya dalam konfigurasi.

Pengaturan disableAllHooks menghormati hierarki pengaturan terkelola. Jika administrator telah mengonfigurasi hooks melalui pengaturan kebijakan terkelola, disableAllHooks yang diatur dalam pengaturan pengguna, proyek, atau lokal tidak dapat menonaktifkan hooks terkelola tersebut. Hanya disableAllHooks yang diatur pada tingkat pengaturan terkelola yang dapat menonaktifkan hooks terkelola. Untuk jangkauan lengkap setiap tingkat, lihat disableAllHooks.

Pengeditan langsung ke hooks dalam file pengaturan biasanya diambil secara otomatis oleh file watcher.

Input dan output hook

Hook perintah menerima data JSON melalui stdin dan menyampaikan hasil melalui exit code, stdout, dan stderr. Hook HTTP menerima JSON yang sama sebagai body permintaan POST dan menyampaikan hasil melalui body respons HTTP. Bagian ini membahas field dan perilaku yang umum untuk semua event. Bagian setiap event di bawah Event hook mencakup skema input spesifik dan opsi kontrol keputusannya.

Di macOS dan Linux, hook perintah berjalan dalam sesinya sendiri tanpa terminal pengendali. Proses hook dan proses turunannya tidak dapat membuka /dev/tty atau mengirim escape sequence secara langsung ke antarmuka Claude Code. Windows tidak memiliki /dev/tty.

Untuk menampilkan pesan kepada pengguna di platform apa pun, kembalikan systemMessage dalam output JSON. Beberapa event membuangnya atau mengirimkannya ke tempat lain, dan bagian setiap event menyebutkannya. Untuk memicu notifikasi desktop, mengatur judul jendela, atau membunyikan bel, kembalikan terminalSequence sebagai gantinya.

Field input umum

Event hook menerima field berikut sebagai JSON, selain field khusus event yang didokumentasikan di setiap bagian event hook. Untuk hook perintah, JSON ini tiba melalui stdin. Untuk hook HTTP, JSON ini tiba sebagai body permintaan POST.

Field Deskripsi
session_id Pengidentifikasi sesi saat ini
prompt_id UUID yang mengidentifikasi prompt pengguna yang sedang diproses. Cocok dengan atribut prompt.id pada event OpenTelemetry, sehingga Anda dapat mengorelasikan output hook dengan telemetri untuk satu prompt. Tidak ada sampai input pengguna pertama. Memerlukan Claude Code v2.1.196 atau lebih baru
transcript_path Path ke JSON percakapan. File transkrip ditulis secara asinkron dan mungkin tertinggal dari percakapan di memori, sehingga mungkin belum menyertakan pesan terbaru dari giliran saat ini ketika hook dijalankan. Hook yang memerlukan teks akhir asisten dari giliran saat ini sebaiknya menggunakan last_assistant_message pada Stop dan SubagentStop alih-alih membaca transkrip
cwd Direktori kerja saat ini ketika hook dipanggil
scratchpad_dir Path ke direktori scratchpad sesi, tempat Claude menyimpan file kerja sementara. Tidak ada ketika sesi tidak memiliki scratchpad atau direktori temp tidak tersedia. Memerlukan Claude Code v2.1.257 atau lebih baru
permission_mode Mode izin saat ini: "default", "plan", "acceptEdits", "auto", "dontAsk", atau "bypassPermissions". Mode berlabel Manual tiba sebagai "default", tidak pernah sebagai "manual", sehingga skrip yang mencocokkan "default" tetap berfungsi. Tidak semua event menerima field ini. Periksa contoh JSON di setiap bagian event hook
effort Objek dengan field level yang berisi tingkat effort yang berlaku saat hook berjalan: "low", "medium", "high", "xhigh", atau "max". Jika Anda mengatur tingkat yang tidak didukung model aktif, level melaporkan tingkat yang sebenarnya dijalankan Claude Code; Menyesuaikan tingkat effort menjelaskan cara tingkat tersebut dipilih. Objek ini cocok dengan field effort pada baris status. Ada untuk event yang dijalankan dalam konteks penggunaan tool, seperti PreToolUse, PostToolUse, Stop, dan SubagentStop, ketika model saat ini mendukung parameter effort. Tingkat ini juga tersedia untuk perintah hook dan tool Bash sebagai environment variable $CLAUDE_EFFORT.
hook_event_name Nama event yang dijalankan

Saat berjalan dengan --agent atau di dalam subagent, dua field tambahan disertakan:

Field Deskripsi
agent_id Pengidentifikasi unik untuk subagent. Hanya ada ketika hook dijalankan di dalam pemanggilan subagent. Gunakan ini untuk membedakan pemanggilan hook subagent dari pemanggilan thread utama.
agent_type Nama agent (misalnya, "Explore" atau "security-reviewer"). Ada ketika sesi menggunakan --agent atau hook dijalankan di dalam subagent. Untuk subagent, tipe subagent diutamakan daripada nilai --agent sesi. Lihat SubagentStart untuk nilai yang dilaporkan subagent kustom dan plugin serta cara menulis matcher untuk nama yang dibatasi pada plugin.

Hanya hook SessionStart yang dapat menerima field model, dan Claude Code tidak selalu menyertakannya. Hook PreModelSwitch dan PostModelSwitch menerima from_model dan to_model sebagai gantinya, jadi gunakan hook PostModelSwitch untuk mengikuti model saat model berubah selama sesi.

Tidak ada environment variable $CLAUDE_MODEL. Hook dapat membaca $ANTHROPIC_MODEL jika Anda mengaturnya di shell Anda, tetapi nilai tersebut tidak berubah ketika Anda mengganti model dengan /model selama sesi.

Proses hook mewarisi environment induk, kecuali variabel exporter OTEL_* yang dihapus Claude Code dari setiap subproses yang dijalankannya dan, ketika CLAUDE_CODE_SUBPROCESS_ENV_SCRUB diatur ke 1, variabel-variabel yang dihapusnya.

Misalnya, hook PreToolUse untuk perintah Bash menerima ini di stdin:

{
  "session_id": "abc123",
  "prompt_id": "550e8400-e29b-41d4-a716-446655440000",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/home/user/my-project",
  "scratchpad_dir": "/tmp/claude-1000/-home-user-my-project/abc123/scratchpad",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite",
    "timeout": 120000,
    "run_in_background": false
  },
  "tool_use_id": "toolu_01ABC123..."
}

Field tool_name, tool_input, dan tool_use_id bersifat khusus event. Setiap bagian event hook mendokumentasikan field tambahan untuk event tersebut.

Output exit code

Exit code dari perintah hook Anda memberi tahu Claude Code apakah tindakan harus dilanjutkan, diblokir, atau diabaikan. Exit code tidak bekerja sendiri. Claude Code membaca field output JSON dari stdout pada setiap exit code, bukan hanya 0, dan untuk event yang menggunakan model keputusan standar, objek yang berhasil di-parse dan lolos validasi skema berlaku bersamaan dengan kode tersebut. Pemblokiran oleh exit 2 adalah satu-satunya hasil yang tidak dapat ditimpa oleh JSON.

Dua tabel memuat pengecualian per event: Perilaku exit code 2 per event menjelaskan apa yang dilakukan exit code untuk setiap event, dan Kontrol keputusan menjelaskan field keputusan mana yang dihormati setiap event. Field universal seperti systemMessage berfungsi di sebagian besar event dan tercantum dalam tabel output JSON.

Exit code 0

Exit 0 berarti berhasil, dan merupakan exit code yang dimaksudkan ketika Anda mencetak JSON untuk kontrol terstruktur.

Untuk sebagian besar event, Claude Code menulis stdout ke log debug dan tidak menampilkannya di transkrip. Pengecualiannya adalah UserPromptSubmit, UserPromptExpansion, SessionStart, dan PostModelSwitch, di mana Claude Code menambahkan stdout teks biasa sebagai konteks yang dapat dilihat dan ditindaklanjuti oleh Claude.

Apakah Claude Code membaca stdout Anda sebagai output JSON atau sebagai teks biasa bergantung pada bagaimana stdout diawali dan diakhiri, dengan mengabaikan whitespace di sekitarnya:

  • Diawali dengan { dan diakhiri dengan }: Claude Code mem-parse-nya sebagai JSON. Ketika output terdiri dari dua baris atau lebih yang masing-masing dapat di-parse sebagai JSON secara terpisah, dan tidak ada baris yang merupakan objek output JSON yang mengatur suatu field, Claude Code memperlakukan seluruh output sebagai teks biasa. Ketika salah satu baris tersebut mengatur suatu field, seluruh output dianggap gagal di-parse, seperti dijelaskan di bawah.
  • Diawali dengan { tetapi tidak diakhiri dengan }: Claude Code memperlakukannya sebagai teks biasa.
  • Diawali dengan hal lain: Claude Code memperlakukannya sebagai teks biasa, termasuk array JSON atau string JSON yang diberi tanda kutip.

Untuk event yang menggunakan model keputusan standar, exit 0 dengan objek yang berhasil di-parse tetapi gagal validasi skema adalah error non-blocking: tindakan dilanjutkan, dan transkrip menampilkan pemberitahuan <hook name> hook error dengan pesan validasi. Hal yang sama terjadi pada exit code apa pun selain 2, sementara exit 2 tetap memblokir.

Untuk event yang menggunakan model keputusan standar, ketika Claude Code mencoba mem-parse stdout Anda sebagai JSON dan gagal, Claude Code melaporkan error non-blocking pada setiap exit code selain 2. Transkrip menampilkan pemberitahuan <hook name> hook error dengan pesan parse. Pada event yang menambahkan stdout teks biasa sebagai konteks, Claude Code tidak menambahkan teks tersebut. Sebelum v2.1.248, Claude Code memperlakukan stdout tersebut sebagai teks biasa.

Stderr dari hook yang keluar dengan 0 hanya masuk ke log debug, tidak pernah ke transkrip, dan Claude tidak pernah melihatnya. Untuk membacanya sendiri, aktifkan logging debug. Untuk menyampaikan peringatan kepada Claude dari hook PostToolUse atau PostToolUseFailure, gunakan exit 2 sehingga Claude melihat stderr meskipun tool sudah berjalan.

Exit code 2

Exit 2 berarti error blocking. Pada event yang dapat memblokir, exit 2 memblokir terlepas dari apakah Anda mencetak JSON atau tidak: bahkan permissionDecision JSON bernilai "allow" tidak dapat menimpanya. Claude Code tetap membaca output JSON yang valid di stdout. Pada Elicitation dan ElicitationResult, hookSpecificOutput dari hook yang keluar dengan exit 2 diabaikan.

Pesan pemblokiran adalah alasan dari keputusan pemblokiran dalam JSON Anda jika ada, dan teks stderr Anda jika tidak. Apa yang dilakukan pemblokiran bervariasi menurut event: PreToolUse memblokir panggilan tool, UserPromptSubmit menolak prompt, dan seterusnya. Perilaku exit code 2 per event mencantumkan efek untuk setiap event, dan bagian setiap event menjelaskan ke mana pesan tersebut dikirim.

Hook yang keluar dengan 2 sambil mencetak JSON yang gagal validasi skema output JSON tetap memblokir: Claude Code menggunakan stderr sebagai alasan pemblokiran dan mencatat kegagalan validasi di log debug. Sebelum v2.1.214, Claude Code memperlakukan kombinasi tersebut sebagai error non-blocking dan tindakan dilanjutkan.

Skrip ini memblokir perintah rm dengan keluar menggunakan 2 dan menyerahkan setiap perintah lain ke alur izin normal:

#!/bin/bash
# Reads JSON input from stdin, checks the command
input=$(cat)
command=$(jq -r '.tool_input.command' <<<"$input")

if [[ "$command" == rm* ]]; then
  echo "Blocked: rm commands are not allowed" >&2
  exit 2  # Blocking error: tool call is prevented
fi

exit 0  # No decision: the normal permission flow applies

Exit code lainnya

Exit code lainnya tidak memblokir dengan sendirinya untuk sebagian besar event hook. Apa yang terjadi bergantung pada stdout Anda:

  • Dengan objek yang berhasil di-parse dan lolos validasi skema, untuk event yang menggunakan model keputusan standar, Claude Code mengabaikan exit code dan hanya JSON yang menentukan hasilnya:
    • Setiap field yang didukung event dihormati, termasuk permissionDecision, additionalContext, updatedInput, dan systemMessage, dan hook tidak dilaporkan sebagai error.
    • Kontrol keputusan mencantumkan field keputusan per event; field universal seperti systemMessage mengikuti tabel output JSON.
  • Dengan objek yang berhasil di-parse tetapi gagal validasi skema, untuk event yang menggunakan model keputusan standar, hasilnya adalah error non-blocking yang sama seperti pada exit 0: tindakan dilanjutkan, dan pemberitahuan <hook name> hook error memuat pesan validasi.
  • Dengan stdout yang dicoba di-parse sebagai JSON oleh Claude Code tetapi gagal, Claude Code melaporkan error non-blocking yang sama seperti pada exit 0 untuk event yang menggunakan model keputusan standar. Tindakan dilanjutkan, dan pemberitahuan memuat pesan parse.
  • Dengan stdout yang diperlakukan sebagai teks biasa oleh Claude Code, atau dengan stdout kosong, hasilnya adalah error non-blocking untuk sebagian besar event hook: tindakan dilanjutkan, dan transkrip menampilkan pemberitahuan <hook name> hook error diikuti baris pertama stderr, dengan awalan Failed with non-blocking status code:. Untuk menangkap stderr lengkap, aktifkan logging debug.

Event di luar model keputusan standar memiliki barisnya sendiri dalam tabel per event: WorktreeCreate menggagalkan pembuatan pada exit bukan nol apa pun, apa pun isi JSON Anda, dan event yang membuang output hook sepenuhnya, seperti StopFailure, mengabaikan JSON Anda pada setiap exit code, kecuali field efek samping seperti terminalSequence, yang tetap dijalankan.

Hook yang tidak dapat dimulai masuk ke kategori non-blocking yang sama. Ketika path skrip tidak ada atau tidak dapat dieksekusi, shell keluar dengan kode seperti 127 dan Anda melihat pemberitahuan yang sama dengan pesan interpreter, misalnya Failed with non-blocking status code: /bin/sh: /path/to/hook.sh: No such file or directory. Untuk sebagian besar event hook, tindakan dilanjutkan. Saat Anda menyiapkan hook kebijakan, perhatikan pemberitahuan ini pada eksekusi pertamanya: path yang salah ketik di settings.json membuat gerbang tersebut nonaktif tanpa pemberitahuan.

Timeout

Selain hook perintah yang Anda jalankan dengan async: true, Claude Code membatalkan hook command, http, atau mcp_tool yang mencapai timeout-nya, membuang output hook, sehingga pada sebagian besar event hook yang mengalami timeout tidak memberikan keputusan.

Pada PreModelSwitch, hook yang dibatalkan karena timeout memblokir pergantian model. Pada PreToolUse, kedua keluarga hook berbeda:

  • Hook command, http, atau mcp_tool yang mengalami timeout tidak memblokir panggilan tool. Panggilan berlanjut melalui alur izin normal, jadi jangan mengandalkan hook yang macet untuk berfungsi sebagai gerbang.
  • Callback hook Agent SDK yang melebihi timeout-nya memblokir panggilan tool.

Perilaku exit code 2 per event

Exit code 2 adalah cara hook memberi sinyal "berhenti, jangan lakukan ini." Efeknya bergantung pada event, karena beberapa event mewakili tindakan yang dapat diblokir (seperti panggilan tool yang belum terjadi) dan yang lain mewakili hal-hal yang sudah terjadi atau tidak dapat dicegah.

Event hook Dapat memblokir? Apa yang terjadi pada exit 2
PreToolUse Ya Memblokir panggilan tool
PermissionRequest Tidak Exit code 2 tidak dihormati untuk event ini dan alur izin berlanjut tanpa perubahan. Tolak melalui objek decision sebagai gantinya
UserPromptSubmit Ya Memblokir prompt, sehingga prompt tidak pernah sampai ke Claude. Lihat Apa yang ditinggalkan prompt yang diblokir
UserPromptExpansion Ya Memblokir ekspansi
Stop Ya Mencegah Claude berhenti, melanjutkan percakapan
SubagentStop Ya Mencegah subagent berhenti
TeammateIdle Ya Mencegah rekan tim menjadi idle, sehingga tetap bekerja
TaskCreated Ya Membatalkan pembuatan tugas
TaskCompleted Ya Mencegah tugas ditandai sebagai selesai
ConfigChange Ya Memblokir perubahan konfigurasi agar tidak berlaku (kecuali policy_settings)
StopFailure Tidak Output dan exit code diabaikan, kecuali terminalSequence
PostToolUse Tidak Menampilkan stderr kepada Claude; tool sudah berjalan
PostToolUseFailure Tidak Menampilkan stderr kepada Claude; tool sudah gagal
PostToolBatch Ya Menghentikan agentic loop sebelum panggilan model berikutnya
PermissionDenied Tidak Exit code dan stderr diabaikan karena penolakan sudah terjadi. Gunakan JSON hookSpecificOutput.retry: true untuk memberi tahu model bahwa model boleh mencoba ulang; Claude Code mengabaikan retry: true untuk penolakan tanpa putusan
Notification Tidak Exit code dan stderr diabaikan
SubagentStart Tidak Menampilkan stderr hanya kepada pengguna
SessionStart Tidak Menampilkan stderr hanya kepada pengguna
Setup Tidak Exit code dan stderr diabaikan
SessionEnd Tidak Menampilkan stderr hanya kepada pengguna
CwdChanged Tidak Menampilkan stderr hanya kepada pengguna
DirectoryAdded Tidak Stderr masuk ke log debug; direktori sudah ditambahkan
FileChanged Tidak Menampilkan stderr hanya kepada pengguna
PreCompact Ya Memblokir compaction
PostCompact Tidak Menampilkan stderr hanya kepada pengguna
PreModelSwitch Ya Memblokir pergantian model dan menampilkan stderr kepada pengguna
PostModelSwitch Tidak Menampilkan stderr hanya kepada pengguna; model sudah berganti
Elicitation Ya Menolak elicitation
ElicitationResult Ya Memblokir respons (tindakan menjadi decline)
WorktreeCreate Ya Exit code bukan nol apa pun menyebabkan pembuatan worktree gagal
WorktreeRemove Ya Exit code bukan nol apa pun menyebabkan penghapusan worktree gagal jika direktori masih ada setelahnya. Lihat WorktreeRemove untuk apa yang terjadi pada direktori
InstructionsLoaded Tidak Exit code diabaikan
MessageDisplay Tidak Teks asli ditampilkan

Untuk SessionStart, SubagentStart, dan PostModelSwitch, Claude Code menampilkan stderr exit code 2 di transkrip sebagai pemberitahuan <hook name> hook error, dengan cara yang sama seperti menampilkan error non-blocking. Claude tidak melihatnya, dan sesi atau subagent berlanjut. Untuk SubagentStart, pemberitahuan muncul di transkrip subagent itu sendiri, bukan di percakapan induk.

Penanganan respons HTTP

Hook HTTP menggunakan kode status HTTP dan body respons alih-alih exit code dan stdout. Hasil di bawah ini berlaku untuk sebagian besar event; event dengan kontrak kegagalannya sendiri dalam tabel per event, seperti WorktreeCreate, juga menerapkan kontrak tersebut pada hook HTTP yang gagal:

  • 2xx dengan body kosong: berhasil, setara dengan exit code 0 tanpa output
  • 2xx dengan body objek JSON: di-parse menggunakan skema output JSON yang sama seperti hook perintah. Body yang gagal validasi skema adalah error non-blocking
  • 2xx dengan body lain, seperti teks biasa: error non-blocking, ditangani sama seperti status non-2xx. Claude Code tidak menambahkan teks tersebut ke konteks Claude
  • Status non-2xx: error non-blocking, eksekusi berlanjut
  • Kegagalan koneksi: error non-blocking, eksekusi berlanjut
  • Timeout: hook dibatalkan, seperti dijelaskan di bagian Timeout

Tidak seperti hook perintah, hook HTTP tidak dapat memberi sinyal error blocking hanya melalui kode status. Untuk memblokir panggilan tool atau menolak izin, kembalikan respons 2xx dengan body JSON yang berisi field keputusan yang sesuai.

Output JSON

Exit code hanya memungkinkan Anda memblokir atau tetap diam, tetapi output JSON memberi Anda kontrol yang lebih terperinci. Alih-alih keluar dengan kode 2 untuk memblokir, keluar dengan 0 dan cetak objek JSON ke stdout. Claude Code membaca field tertentu dari JSON tersebut untuk mengontrol perilaku, termasuk kontrol keputusan untuk memblokir, mengizinkan, atau mengeskalasi ke pengguna.

Stdout hook Anda hanya boleh berisi objek JSON. Jika profil shell Anda mencetak teks saat startup, hal itu dapat mengganggu parsing JSON. Lihat JSON hook tidak berpengaruh dalam panduan pemecahan masalah.

String additionalContext, systemMessage, dan initialUserMessage dari hook, serta stdout biasanya, dibatasi hingga 10.000 karakter:

  • Cakupan: Claude Code mengukur setiap string secara terpisah, bahkan ketika beberapa hook berjalan untuk event yang sama. Untuk output JSON, setiap field diukur secara terpisah; stdout biasa diukur secara keseluruhan.
  • Melebihi batas: Claude Code menyimpan output ke file di direktori sesi dan menggantinya dengan path file serta pratinjau hingga 2.000 karakter pertama. Hasil Bash valid yang besar ditangani dengan cara yang sama, seperti dijelaskan di bagian Batas output. Tidak seperti batas atas Bash tersebut, batas ini tidak memiliki pengaturan atau environment variable untuk menaikkannya.
  • Membaca file: Claude Code tidak meminta Claude untuk membaca file tersebut, jadi pastikan apa pun yang harus selalu dilihat Claude tetap berada dalam batas.

Objek JSON mendukung tiga jenis field:

  • Field universal seperti continue tercantum dalam tabel di bawah. Setiap event menerimanya, tetapi beberapa event membuangnya atau mengirimkan systemMessage ke tempat selain transkrip. Bagian setiap event menyebutkannya. terminalSequence juga berfungsi pada event tersebut, dengan pengecualian yang tercantum di bagian Memancarkan notifikasi terminal.
  • decision dan reason tingkat atas digunakan oleh beberapa event untuk memblokir atau memberikan umpan balik.
  • hookSpecificOutput adalah objek bersarang untuk event yang memerlukan kontrol lebih kaya. Objek ini memerlukan field hookEventName yang diatur ke nama event.
Field Default Deskripsi
continue true Jika false, Claude berhenti memproses sepenuhnya setelah hook berjalan. Diutamakan daripada field keputusan khusus event mana pun
stopReason tidak ada Pesan yang ditampilkan kepada pengguna ketika continue bernilai false. Pesan ini tetap berada di percakapan, sehingga Claude melihatnya jika percakapan berlanjut
suppressOutput false Tidak berpengaruh: Claude Code menerima field ini tetapi tidak menindaklanjutinya. Stdout hook yang berhasil tidak pernah ditampilkan di transkrip dan dicatat di log debug
systemMessage tidak ada Pesan peringatan yang ditampilkan kepada pengguna. Dalam output Agent SDK dan --output-format stream-json, pesan ini dapat tiba sebagai SDKInformationalMessage
terminalSequence tidak ada Escape sequence terminal yang akan dipancarkan Claude Code atas nama Anda, seperti notifikasi desktop, judul jendela, atau bel. Dibatasi pada OSC 0/1/2/9/99/777 dan BEL. Jika nilai berisi apa pun di luar allowlist, field diabaikan. Gunakan ini alih-alih menulis ke /dev/tty, yang tidak tersedia untuk hook

Untuk menghentikan Claude sepenuhnya:

{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }

Untuk hook PreToolUse dan PostToolUse, penghentian berlaku bahkan ketika panggilan tool gagal atau selesai saat Claude masih melakukan streaming respons.

Memancarkan notifikasi terminal

Hook berjalan tanpa terminal pengendali, sehingga menulis escape sequence langsung ke /dev/tty akan gagal. Sebagai gantinya, kembalikan escape sequence di field terminalSequence dan Claude Code memancarkannya untuk Anda melalui jalur penulisan terminalnya sendiri. Cara ini bebas race condition, berfungsi di dalam tmux dan GNU screen, dan berfungsi di Windows yang tidak memiliki /dev/tty.

Field ini menerima string berisi satu atau lebih escape sequence yang ada di allowlist:

  • OSC 0, 1, 2: judul jendela dan ikon
  • OSC 9: notifikasi iTerm2, ConEmu, Windows Terminal, dan WezTerm, termasuk progres taskbar 9;4
  • OSC 99: notifikasi Kitty
  • OSC 777: notifikasi urxvt, Ghostty, dan Warp
  • BEL tunggal

Sequence dapat diakhiri dengan BEL atau dengan ST. Apa pun di luar allowlist, termasuk sequence kursor dan warna CSI, sequence palet OSC, hyperlink OSC 8, penulisan clipboard OSC 52, dan OSC 1337, ditolak dan field diabaikan.

Claude Code menulis sequence tersebut sendiri ketika memproses output hook Anda, sehingga field ini berfungsi pada event yang membuang systemMessage dan continue, seperti Notification dan StopFailure. Field ini memiliki dua batasan:

  • Claude Code menulis sequence hanya dalam sesi interaktif, dan hanya selama antarmukanya ada di layar. Dalam mode non-interaktif dengan flag -p dan di Agent SDK, Claude Code mengabaikan field ini.
  • Hook perintah WorktreeCreate tidak dapat mengembalikan JSON, karena Claude Code membaca stdout-nya sebagai path worktree. Hook HTTP WorktreeCreate mengembalikan JSON dan dapat menyertakan field ini.

Contoh di bawah memicu notifikasi desktop dari hook Notification. Escape sequence dibuat dengan escape oktal printf sehingga byte kontrol tidak pernah muncul di baris perintah shell, dan jq -n --arg membuat output JSON sehingga tanda kutip, backslash, dan baris baru dalam pesan notifikasi di-escape dengan benar:

#!/bin/bash
# Notification hook: ping the desktop when Claude Code needs attention.
input=$(cat)
title="Claude Code"
body=$(jq -r '.message // "Needs your attention"' <<<"$input")
seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'

Bentuk { "terminalSequence": "..." } sama dari shell atau bahasa apa pun.

Menambahkan konteks untuk Claude

Field additionalContext meneruskan string dari hook Anda ke context window Claude. Claude Code membungkus string tersebut dalam system reminder dan menyisipkannya ke dalam percakapan pada titik di mana hook dijalankan. Claude membaca reminder tersebut pada permintaan model berikutnya, tetapi reminder tidak muncul sebagai pesan chat di antarmuka.

Kembalikan additionalContext di dalam hookSpecificOutput bersama nama event:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
  }
}

Di mana reminder muncul bergantung pada event:

Ketika beberapa hook mengembalikan additionalContext untuk event yang sama, Claude menerima semua nilainya.

Jika suatu nilai melebihi 10.000 karakter, Claude Code menulis teks tersebut ke file di direktori sesi dan sebagai gantinya meneruskan path file kepada Claude beserta pratinjau hingga 2.000 karakter pertama. Claude dapat membaca file tersebut, tetapi Claude Code tidak memintanya untuk melakukannya.

Gunakan additionalContext untuk informasi yang perlu diketahui Claude tentang status environment Anda saat ini atau operasi yang baru saja dijalankan:

  • Status environment: branch saat ini, target deployment, atau feature flag yang aktif
  • Aturan proyek bersyarat: perintah pengujian mana yang berlaku untuk file yang baru saja diedit, direktori mana yang read-only di worktree ini
  • Data eksternal: issue terbuka yang ditugaskan kepada Anda, hasil CI terbaru, konten yang diambil dari layanan internal

Untuk instruksi yang tidak pernah berubah, utamakan CLAUDE.md. File ini dimuat tanpa menjalankan skrip dan merupakan tempat standar untuk konvensi proyek yang statis.

Tulis teks sebagai pernyataan faktual, bukan instruksi sistem yang bersifat imperatif. Frasa seperti "The deployment target is production" atau "This repo uses bun test" dibaca sebagai informasi proyek. Teks yang dibingkai sebagai perintah sistem di luar jalur dapat memicu pertahanan prompt injection Claude, yang menyebabkan Claude menampilkan teks tersebut kepada Anda alih-alih memperlakukannya sebagai konteks.

Claude Code menyimpan teks yang disisipkan di transkrip sesi. Untuk event di tengah sesi seperti PostToolUse atau UserPromptSubmit, ketika Anda melanjutkan dengan --continue atau --resume, Claude Code memutar ulang teks yang tersimpan alih-alih menjalankan ulang hook untuk giliran sebelumnya, sehingga nilai seperti timestamp atau SHA commit menjadi usang. Hook SessionStart berjalan lagi saat dilanjutkan dengan source diatur ke "resume", atau "fork" jika Anda menambahkan --fork-session, sehingga hook tersebut dapat memperbarui konteksnya.

Kontrol keputusan

Tidak setiap event mendukung pemblokiran atau pengendalian perilaku melalui JSON. Event yang mendukungnya masing-masing menggunakan kumpulan field yang berbeda untuk menyatakan keputusan tersebut. Gunakan tabel ini sebagai referensi cepat sebelum menulis hook:

Event Pola keputusan Field utama
UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact decision tingkat atas decision: "block", reason. Stop dan SubagentStop juga menerima hookSpecificOutput.additionalContext untuk umpan balik non-error yang melanjutkan percakapan
TeammateIdle, TaskCompleted Exit code atau continue: false Exit code 2 memblokir tindakan dengan umpan balik stderr. JSON {"continue": false, "stopReason": "..."} juga menghentikan rekan tim sepenuhnya, sesuai dengan perilaku hook Stop; TaskCompleted mengabaikannya ketika tool TaskUpdate memicu event tersebut
TaskCreated Exit code atau decision tingkat atas Exit code 2 atau decision: "block" membatalkan tugas dan mengembalikan pesan kepada Claude. continue: false diabaikan
PreToolUse hookSpecificOutput permissionDecision (allow/deny/ask/defer), permissionDecisionReason
PreModelSwitch hookSpecificOutput atau decision tingkat atas permissionDecision (allow/deny/ask), permissionDecisionReason. decision: "block" juga membatalkan pergantian
PermissionRequest hookSpecificOutput decision.behavior (allow/deny)
PermissionDenied hookSpecificOutput retry: true memberi tahu model bahwa model boleh mencoba ulang panggilan tool yang ditolak; Claude Code mengabaikannya untuk penolakan tanpa putusan
WorktreeCreate pengembalian path Hook perintah mencetak path di stdout; hook HTTP mengembalikan hookSpecificOutput.worktreePath. Kegagalan hook atau path yang tidak ada menggagalkan pembuatan
WorktreeRemove Exit code Exit code bukan nol apa pun membuat penghapusan gagal jika direktori masih ada setelahnya. Output JSON dibuang
Elicitation hookSpecificOutput action (accept/decline/cancel), content (nilai field formulir untuk accept)
ElicitationResult hookSpecificOutput action (accept/decline/cancel), content (override nilai field formulir)
MessageDisplay hookSpecificOutput displayContent menggantikan teks yang ditampilkan di layar. Hanya tampilan: transkrip dan apa yang dilihat Claude tetap mempertahankan teks asli
SessionStart, SubagentStart, PostModelSwitch Hanya konteks hookSpecificOutput.additionalContext menambahkan konteks untuk Claude. SessionStart juga menerima initialUserMessage, watchPaths, sessionTitle, dan reloadSkills. Tidak ada pemblokiran atau kontrol keputusan
Setup, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, DirectoryAdded, FileChanged Tidak ada Tidak ada kontrol keputusan. Digunakan untuk efek samping seperti logging atau pembersihan

Beberapa event juga dapat menulis ulang konten alih-alih hanya mengizinkan atau memblokirnya:

Untuk kasus penggunaan redaksi atau transformasi, lakukan intersepsi di PreToolUse untuk input tool keluar dan di PostToolUse untuk hasil tool masuk.

Berikut contoh setiap pola dalam praktik:

Satu-satunya nilai untuk decision adalah "block". Untuk mengizinkan tindakan dilanjutkan, hilangkan decision dari JSON Anda, atau keluar dengan 0 tanpa JSON sama sekali:

{
"decision": "block",
"reason": "Test suite must pass before proceeding"
}

Untuk contoh lanjutan termasuk validasi perintah Bash, pemfilteran prompt, dan skrip persetujuan otomatis, lihat Apa yang dapat Anda otomatisasi dalam panduan dan implementasi referensi validator perintah Bash.

Event hook

Setiap event berkaitan dengan satu titik dalam siklus hidup Claude Code tempat hook dapat berjalan. Bagian-bagian di bawah ini diurutkan sesuai siklus hidup tersebut: mulai dari penyiapan sesi, melalui agentic loop, hingga akhir sesi. Setiap bagian menjelaskan kapan event dipicu, matcher apa yang didukungnya, input JSON yang diterimanya, dan cara mengontrol perilaku melalui output.

SessionStart

Berjalan saat Claude Code memulai sesi baru atau melanjutkan sesi yang sudah ada. Berguna untuk memuat konteks pengembangan seperti issue yang ada atau perubahan terbaru pada codebase Anda, atau untuk menyiapkan environment variable. Untuk konteks statis yang tidak memerlukan skrip, gunakan CLAUDE.md sebagai gantinya.

SessionStart berjalan pada setiap sesi, jadi pastikan hook ini tetap cepat. Hanya hook type: "command" dan type: "mcp_tool" yang didukung. Lihat kolom hook tool MCP untuk mengetahui kapan hook mcp_tool berjalan.

Nilai matcher berkaitan dengan cara sesi dimulai:

Matcher Kapan dipicu
startup Sesi baru
resume --resume, --continue, atau /resume
clear /clear
compact Compaction otomatis atau manual
fork Sesi baru yang di-fork dari sesi yang sudah ada: --fork-session dengan --resume atau --continue, salinan latar belakang /fork, /branch, atau percakapan yang Anda pindahkan ke latar belakang

Sebelum v2.1.214, sesi hasil fork melaporkan source "resume".

Saat Anda memulai sesi interaktif, melanjutkan percakapan saat peluncuran dengan --continue atau --resume, atau menjalankan /clear, hook SessionStart berjalan di latar belakang. Anda dapat langsung mengetik, dan percakapan yang Anda lanjutkan muncul tanpa menunggu hook. Respons pertama Claude tetap menunggu hook selesai, sehingga konteksnya sampai ke Claude.

Saat Anda berpindah percakapan dengan /resume di dalam sesi, perpindahan tersebut justru menunggu hook selesai. Jika Anda menjalankan /clear atau berpindah ke percakapan lain saat hook latar belakang masih berjalan, apa pun yang dikembalikannya tidak diterapkan ke sesi.

Penantian yang sama berlaku saat peluncuran, termasuk sesi yang dilanjutkan: prompt yang Anda kirim saat hook SessionStart masih berjalan tidak sampai ke Claude hingga hook tersebut selesai.

Selama salah satu penantian tersebut, tekan Esc untuk mengambil kembali prompt ke input tanpa mengirimkannya. Hook tetap berjalan.

Input SessionStart

Selain kolom input umum, hook SessionStart menerima source dan secara opsional model, agent_type, dan session_title:

Kolom Deskripsi
source Cara sesi dimulai: "startup" untuk sesi baru, "resume" untuk sesi yang dilanjutkan, "clear" setelah /clear, "compact" setelah compaction, atau "fork" untuk sesi baru yang di-fork dari sesi yang sudah ada
model Pengidentifikasi model yang aktif. Kolom ini dapat dihilangkan, misalnya setelah /clear atau saat sesi dipulihkan melalui pemulihan percakapan, jadi periksa keberadaan kolom ini sebelum membacanya
agent_type Nama agent, ada saat Anda memulai Claude Code dengan claude --agent <name>
session_title Judul kustom sesi, ada saat judul tersebut diatur, misalnya dengan --name, /rename, output sessionTitle dari hook, atau renameSession() dari Agent SDK. Hook yang mengeluarkan sessionTitle dapat memeriksa kolom ini terlebih dahulu agar tidak menimpa judul kustom yang sudah ada

Sesi yang belum Anda beri nama tetap dapat memiliki judul yang dihasilkan. Judul tersebut bukan judul kustom dan tidak muncul di session_title.

Saat source bernilai "resume" atau "fork" dan transkrip berisi setidaknya satu respons dari Claude, hook SessionStart juga menerima empat kolom di bawah ini. Hook Anda dapat menggunakannya untuk melaporkan biaya melanjutkan percakapan yang sudah basi sebelum permintaan pertama, misalnya dalam systemMessage. Kolom-kolom ini memerlukan Claude Code v2.1.251 atau yang lebih baru.

Kolom Deskripsi
seconds_since_last_response Detik waktu nyata sejak respons terakhir dalam transkrip yang dilanjutkan
context_tokens Token yang dikirim ulang oleh permintaan pertama sesi yang dilanjutkan sebagai prompt-nya
prompt_cache_likely_expired true saat respons terakhir lebih lama dari masa hidup prompt cache sesi atau compaction setelahnya menggantikan percakapan yang di-cache
estimated_cache_write_usd Perkiraan biaya dalam dolar AS untuk menulis context_tokens ke prompt cache pada model sesi, tidak termasuk respons

Contoh ini menunjukkan input untuk sesi yang dilanjutkan 90 menit setelah respons terakhirnya:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionStart",
  "source": "resume",
  "model": "claude-opus-5",
  "seconds_since_last_response": 5400,
  "context_tokens": 182340,
  "prompt_cache_likely_expired": true,
  "estimated_cache_write_usd": 1.1396
}

Kontrol keputusan SessionStart

Claude Code menambahkan stdout yang diperlakukannya sebagai teks biasa ke konteks Claude. Selain kolom output JSON yang tersedia untuk semua hook, Anda dapat mengembalikan kolom khusus event berikut:

Kolom Deskripsi
additionalContext String yang ditambahkan ke konteks Claude di awal percakapan, sebelum prompt pertama. Lihat Menambahkan konteks untuk Claude untuk cara teks dikirimkan dan apa yang perlu dimasukkan ke dalamnya
initialUserMessage String yang digunakan sebagai pesan pengguna pertama dalam sesi. Berlaku dalam mode non-interaktif dengan flag -p, tempat string ini menjadi giliran pertama meskipun tidak ada prompt yang diberikan. Jika prompt diberikan, prompt tersebut menyusul sebagai giliran berikutnya. Tidak seperti additionalContext, yang dilampirkan ke giliran yang sudah ada, kolom ini membuat giliran tersebut
sessionTitle Mengatur judul sesi, dengan efek yang sama seperti /rename. Gunakan untuk memberi nama sesi secara otomatis dari folder peluncuran, branch git, atau nama worktree. Berlaku saat source bernilai "startup", "resume", atau "fork"; diabaikan pada "clear" dan "compact"
watchPaths Array path absolut yang dipantau untuk event FileChanged selama sesi ini
reloadSkills Boolean. Saat true, Claude Code memindai ulang direktori skill dan perintah setelah hook SessionStart selesai, sehingga skill yang diinstal oleh hook tersedia dalam sesi yang sama, mulai dari prompt pertama
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2",
    "sessionTitle": "auth-refactor"
  }
}

Karena stdout biasa sudah sampai ke Claude untuk event ini, hook yang hanya memuat konteks dapat langsung mencetak ke stdout tanpa membangun JSON. Gunakan bentuk JSON saat Anda perlu menggabungkan konteks dengan kolom lain seperti sessionTitle.

Gunakan reloadSkills saat hook SessionStart menginstal atau memperbarui skill. Penemuan skill biasanya berjalan sebelum hook SessionStart selesai, sehingga file yang ditulis hook ke ~/.claude/skills/ atau .claude/skills/ jika tidak demikian hanya akan muncul di sesi berikutnya. Contoh ini menyinkronkan repositori skill bersama dan meminta pemindaian ulang:

#!/bin/bash

git -C ~/.claude/skills/team-skills pull --quiet 2>/dev/null || \
  git clone --quiet https://git.example.com/your-org/team-skills.git ~/.claude/skills/team-skills

echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "reloadSkills": true}}'

URL repositori tersebut adalah placeholder; ganti dengan repositori skill Anda sendiri. Dengan placeholder tersebut, clone gagal dan mencetak pesan fatal: ke stderr. Stderr dari hook SessionStart yang keluar dengan 0 hanya bersifat informasional, sehingga permintaan reloadSkills tetap berlaku.

Mempertahankan environment variable

Hook SessionStart memiliki akses ke environment variable CLAUDE_ENV_FILE, yang menyediakan path file tempat Anda dapat mempertahankan environment variable untuk perintah Bash berikutnya.

Untuk mengatur environment variable satu per satu, tulis pernyataan export ke CLAUDE_ENV_FILE. Gunakan append (>>) untuk mempertahankan variabel yang diatur oleh hook lain:

#!/bin/bash

if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
  echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
  echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi

exit 0

Untuk menangkap semua perubahan lingkungan dari perintah penyiapan, bandingkan variabel yang diekspor sebelum dan sesudahnya:

#!/bin/bash

ENV_BEFORE=$(export -p | sort)

# Run your setup commands that modify the environment
source ~/.nvm/nvm.sh
nvm use 20

if [ -n "$CLAUDE_ENV_FILE" ]; then
  ENV_AFTER=$(export -p | sort)
  comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi

exit 0

Setup

Hanya dipicu saat Anda meluncurkan Claude Code dengan --init-only, atau dengan --init atau --maintenance dalam mode non-interaktif dengan flag -p. Event ini tidak dipicu pada startup normal. Gunakan untuk instalasi dependensi satu kali atau pembersihan terjadwal yang Anda picu secara eksplisit dari CI atau skrip, terpisah dari startup sesi normal. Untuk inisialisasi per sesi, gunakan SessionStart sebagai gantinya.

Nilai matcher berkaitan dengan flag CLI yang memicu hook:

Matcher Kapan dipicu
init claude --init-only atau claude -p --init
maintenance claude -p --maintenance

Saat Anda menjalankan claude --init-only, Claude Code menjalankan hook Setup dan hook SessionStart dengan matcher startup, lalu keluar tanpa memulai percakapan.

Saat Anda memulai atau melanjutkan percakapan dengan -p, Anda juga perlu memberikan prompt, sebagai argumen atau melalui pipe ke stdin. Anda dapat melewatkan prompt saat hook SessionStart menyediakan initialUserMessage atau saat Anda melanjutkan sesi dengan panggilan tool yang ditunda.

Jika berhasil, --init-only tidak mencetak apa pun ke terminal. Untuk memastikan hook berjalan, mulai dengan claude --debug-file <path> --init-only, ganti <path> dengan lokasi file log, lalu periksa log untuk entri hook Setup dan SessionStart.

Karena Setup tidak dipicu pada setiap peluncuran, plugin yang memerlukan dependensi terinstal tidak dapat hanya mengandalkan Setup. Pola praktisnya adalah memeriksa dependensi saat penggunaan pertama dan menginstalnya jika tidak ada, misalnya hook atau skill yang memeriksa ${CLAUDE_PLUGIN_DATA}/node_modules dan menjalankan npm install jika tidak ada. Lihat direktori data persisten untuk lokasi menyimpan dependensi yang diinstal. Jika Anda mendistribusikan plugin melalui marketplace, Anda mungkin tidak memerlukan pola ini: Claude Code menginstal dependensi paket Node.js yang memenuhi syarat secara otomatis saat melakukan cache pada plugin.

Input Setup

Selain kolom input umum, hook Setup menerima kolom trigger yang bernilai "init" atau "maintenance":

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Setup",
  "trigger": "init"
}

Kontrol keputusan Setup

Hook Setup tidak dapat memblokir; eksekusi berlanjut pada exit code apa pun. Pada setiap exit code, Claude Code membuang kolom output JSON hook Setup, seperti systemMessage, continue, dan hookSpecificOutput.additionalContext. Dengan -p, stdout, stderr, dan exit code hook Setup muncul dalam output run hanya sebagai event hook_response saat Anda meluncurkan dengan --output-format stream-json --verbose.

Hook Setup memiliki akses ke CLAUDE_ENV_FILE. Variabel yang ditulis ke file tersebut dipertahankan untuk perintah Bash berikutnya dalam sesi, sama seperti pada hook SessionStart. Hanya hook type: "command" yang berjalan pada Setup. Hook type: "mcp_tool" pada Setup selalu dilewati, seperti dijelaskan di kolom hook tool MCP.

InstructionsLoaded

Dipicu saat file CLAUDE.md atau .claude/rules/*.md dimuat ke dalam konteks. Event ini dipicu di awal sesi untuk file yang dimuat secara eager dan dipicu lagi nanti saat file dimuat secara lazy, misalnya saat Claude mengakses subdirektori yang berisi CLAUDE.md bersarang atau saat aturan kondisional dengan frontmatter paths: cocok. Hook ini tidak mendukung pemblokiran atau kontrol keputusan. Hook ini berjalan secara asinkron untuk tujuan observabilitas.

Event ini tidak dipicu saat Claude membaca AGENTS.md secara langsung melalui pengaturan Project instructions. Event ini dipicu saat CLAUDE.md mengimpor AGENTS.md Anda, dengan load_reason diatur ke include seperti untuk file impor lainnya, dan saat CLAUDE.md adalah symlink ke file tersebut, sebagai pemuatan CLAUDE.md biasa.

Matcher dijalankan terhadap load_reason. Misalnya, gunakan "matcher": "session_start" agar hanya dipicu untuk file yang dimuat di awal sesi, atau "matcher": "path_glob_match|nested_traversal" agar hanya dipicu untuk pemuatan lazy.

Input InstructionsLoaded

Selain kolom input umum, hook InstructionsLoaded menerima kolom-kolom berikut:

Kolom Deskripsi
file_path Path absolut ke file instruksi yang dimuat
memory_type Cakupan file: "User", "Project", "Local", atau "Managed"
load_reason Alasan file dimuat: "session_start", "nested_traversal", "path_glob_match", "include", atau "compact". Nilai "compact" dipicu saat file instruksi dimuat ulang setelah event compaction
globs Pola glob path dari frontmatter paths: file, jika ada. Hanya ada untuk pemuatan path_glob_match
trigger_file_path Path ke file yang aksesnya memicu pemuatan ini, untuk pemuatan lazy
parent_file_path Path ke file instruksi induk yang menyertakan file ini, untuk pemuatan include
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "InstructionsLoaded",
  "file_path": "/Users/my-project/CLAUDE.md",
  "memory_type": "Project",
  "load_reason": "session_start"
}

Kontrol keputusan InstructionsLoaded

Hook InstructionsLoaded tidak memiliki kontrol keputusan. Hook ini tidak dapat memblokir atau memodifikasi pemuatan instruksi. Claude Code membuang kolom output JSON hook ini, seperti systemMessage dan continue. Gunakan event ini untuk log audit, pelacakan kepatuhan, atau observabilitas.

UserPromptSubmit

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

Hook UserPromptSubmit memiliki timeout default 30 detik untuk jenis command, http, dan mcp_tool, lebih singkat daripada default 600 detik untuk jenis-jenis tersebut pada sebagian besar event lain. Karena hook ini berjalan sebelum setiap prompt dan memblokir pemrosesan model hingga selesai, hook yang macet akan menghentikan sesi. Jika hook Anda memerlukan waktu lebih lama, atur kolom timeout dalam entri hook.

Selain hook perintah yang Anda jalankan dengan async: true, hook perintah, HTTP, atau tool MCP UserPromptSubmit yang mencapai timeout-nya akan dibatalkan dan output-nya, termasuk additionalContext apa pun, dibuang. Prompt tetap sampai ke Claude tanpa konteks tersebut. Transkrip menampilkan pemberitahuan yang menyebutkan nama hook, timeout yang terpicu, dan bahwa output-nya dibuang.

Hook callback Agent SDK pada UserPromptSubmit yang mencapai timeout-nya memblokir prompt dengan pesan yang menyebutkan nama hook dan timeout-nya, karena callback di sana dapat berfungsi sebagai gerbang kebijakan yang tidak boleh gagal secara terbuka. Sesi tetap berlanjut. Sebelum v2.1.208, timeout callback pada event tersebut mengakhiri giliran dengan error eksekusi.

Input UserPromptSubmit

Selain kolom input umum, hook UserPromptSubmit menerima kolom prompt yang berisi teks yang dikirimkan pengguna. Konten tempelan yang diciutkan menjadi placeholder [Pasted text #N] tiba dalam keadaan diperluas di tempatnya. Dalam sesi tempat Claude Code menandai teks tempelan untuk Claude, konten yang diperluas tersebut berada di antara baris <pasted_content id="…"> dan baris </pasted_content id="…">, jadi perhitungkan baris-baris tersebut jika hook Anda mengurai prompt.

Hook UserPromptSubmit juga menerima session_title saat sesi memiliki judul kustom, dengan arti yang sama seperti kolom session_title SessionStart.

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

Kontrol keputusan UserPromptSubmit

Hook UserPromptSubmit dapat mengontrol apakah prompt pengguna diproses dan menambahkan konteks. Semua kolom output JSON tersedia.

Ada dua cara untuk menambahkan konteks ke percakapan pada exit code 0:

  • Stdout teks biasa: Claude Code menambahkan stdout yang diperlakukannya sebagai teks biasa ke konteks Claude
  • JSON dengan additionalContext: gunakan format JSON di bawah ini untuk kontrol lebih. Kolom additionalContext ditambahkan sebagai konteks

Kedua saluran tersebut tidak menghasilkan entri transkrip yang terlihat. Stdout biasa dan nilai additionalContext masing-masing disisipkan sebagai system reminder yang diawali dengan nama hook; Claude membaca keduanya. Untuk memastikan pengirimannya, periksa log debug.

Untuk memblokir prompt, kembalikan objek JSON dengan decision diatur ke "block":

Kolom Deskripsi
decision "block" menghentikan prompt sebelum sampai ke Claude. Hilangkan agar prompt dapat berlanjut
reason Ditampilkan kepada pengguna saat decision bernilai "block". Tidak ditambahkan ke konteks
additionalContext String yang ditambahkan ke konteks Claude bersama prompt yang dikirimkan. Lihat Menambahkan konteks untuk Claude
sessionTitle Mengatur judul sesi. Gunakan untuk memberi nama sesi secara otomatis berdasarkan konten prompt
suppressOriginalPrompt Jika true saat hook memblokir prompt, teks prompt tidak disertakan dalam pesan blokir. Lihat Apa yang ditinggalkan oleh prompt yang diblokir

Hook yang memblokir dengan keluar dengan kode 2 diarahkan dengan cara yang sama seperti reason: pesan blokir menampilkan teks stderr kepada pengguna, dan teks tersebut tidak ditambahkan ke konteks.

{
  "decision": "block",
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "My additional context here",
    "sessionTitle": "My session title",
    "suppressOriginalPrompt": true
  }
}

Apa yang ditinggalkan oleh prompt yang diblokir

Prompt yang diblokir tidak pernah sampai ke Claude, tetapi teksnya tidak dihapus dari semua tempat. Secara default, pesan blokir yang ditampilkan kepada pengguna diakhiri dengan Original prompt: diikuti teks yang dikirimkan, dan Claude Code menulis pesan tersebut ke file transkrip sesi di disk. Agar teks tidak disertakan dalam pesan, cetak JSON dengan "suppressOriginalPrompt": true di dalam hookSpecificOutput. Ini berfungsi baik saat hook memblokir dengan decision: "block" maupun dengan keluar dengan kode 2. Hook exit-2 yang tidak mencetak JSON selalu menyertakan teks prompt dalam pesan blokirnya.

suppressOriginalPrompt hanya mengubah pesan blokir. Teks yang dikirimkan masih dapat muncul di file lokal seperti transkrip sesi dan riwayat prompt Anda, jadi hook pemblokiran bukan cara untuk menjauhkan rahasia dari disk. Untuk membatasi atau menghapus file-file tersebut, lihat Penyimpanan plaintext dan Menghapus data lokal.

UserPromptExpansion

Berjalan saat perintah yang diketik pengguna diperluas menjadi prompt sebelum sampai ke Claude. Gunakan ini untuk memblokir perintah tertentu agar tidak dipanggil secara langsung, menyisipkan konteks untuk skill tertentu, atau mencatat perintah mana yang dipanggil pengguna. Misalnya, hook yang cocok dengan deploy dapat memblokir /deploy kecuali ada file persetujuan, atau hook yang cocok dengan skill review dapat menambahkan daftar periksa review tim sebagai additionalContext.

Event ini mencakup jalur yang tidak dicakup PreToolUse: hook PreToolUse yang cocok dengan tool Skill hanya dipicu saat Claude memanggil tool tersebut, tetapi mengetik /skillname secara langsung melewati PreToolUse. UserPromptExpansion dipicu pada jalur langsung tersebut.

Dicocokkan dengan command_name. Biarkan matcher kosong agar dipicu pada setiap perintah bertipe prompt.

Input UserPromptExpansion

Selain kolom input umum, hook UserPromptExpansion menerima expansion_type, command_name, command_args, command_source, dan string prompt asli. Kolom expansion_type bernilai slash_command untuk skill dan perintah kustom, atau mcp_prompt untuk prompt server MCP.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../00893aaf.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "UserPromptExpansion",
  "expansion_type": "slash_command",
  "command_name": "example-skill",
  "command_args": "arg1 arg2",
  "command_source": "plugin",
  "prompt": "/example-skill arg1 arg2"
}

Kontrol keputusan UserPromptExpansion

Hook UserPromptExpansion dapat memblokir perluasan atau menambahkan konteks. Semua kolom output JSON tersedia.

Kolom Deskripsi
decision "block" mencegah perintah diperluas. Hilangkan agar dapat berlanjut
reason Ditampilkan kepada pengguna saat decision bernilai "block"
additionalContext String yang ditambahkan ke konteks Claude bersama prompt yang diperluas. Lihat Menambahkan konteks untuk Claude

Hook yang memblokir dengan keluar dengan kode 2 diarahkan dengan cara yang sama seperti reason: pesan blokir menampilkan teks stderr kepada pengguna.

{
  "decision": "block",
  "reason": "This slash command is not available",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptExpansion",
    "additionalContext": "Additional context for this expansion"
  }
}

MessageDisplay

Berjalan saat pesan asisten di-stream ke layar. Claude Code menampilkan pesan secara bertahap: setiap kali sekelompok baris yang baru selesai siap dirender, hook berjalan sekali dengan baris-baris tersebut dan Claude Code merender teks pengganti dari hook di tempatnya. Pesan yang panjang menghasilkan beberapa panggilan; pesan yang pendek mungkin hanya menghasilkan satu.

Gunakan MessageDisplay untuk:

  • menghapus markdown untuk tampilan minimal
  • mengubah teks yang ditampilkan aplikasi Agent SDK kepada penggunanya
  • menyamarkan kunci API atau hostname internal dari respons Claude

Claude Code menahan setiap kelompok hingga hook Anda mengembalikan hasil, jadi pastikan hook tetap cepat. Jika hook gagal atau mengalami timeout, Claude Code menampilkan teks asli. Timeout default untuk event ini adalah 10 detik; jika hook Anda memerlukan waktu lebih lama, atur kolom timeout dalam entri hook.

MessageDisplay hanya untuk tampilan: teks pengganti hanya mengubah apa yang dirender di layar. Transkrip dan apa yang dilihat Claude tetap menggunakan teks asli, sehingga Claude tidak pernah melihat teks pengganti, dan mode verbose menampilkan teks asli. Hook hanya menerima teks pesan asisten, sehingga hasil tool dan teks yang Anda ketik dirender tanpa perubahan.

MessageDisplay tidak mendukung matcher dan dipicu untuk setiap pesan asisten yang men-stream teks; pesan tanpa teks, seperti respons yang hanya berisi panggilan tool, tidak memicunya.

Dalam run non-interaktif, termasuk kueri Agent SDK dan claude -p, MessageDisplay berjalan sekali per pesan asisten, bukan sekali per kelompok baris. Panggilan tunggal tersebut tiba setelah pesan selesai dan membawa teks pesan lengkap: index bernilai 0, final bernilai true, dan delta berisi seluruh pesan. Hook yang mengumpulkan teks delta untuk setiap pesan menerima total teks yang sama dalam kedua mode.

Input MessageDisplay

Selain kolom input umum, hook MessageDisplay menerima pengidentifikasi untuk giliran dan pesan, posisi panggilan ini dalam pesan, dan teks baru dalam delta. Batas kelompok bergantung pada cara teks di-stream, jadi gunakan index dan final untuk melacak kemajuan dalam sebuah pesan alih-alih mengharapkan baris dikelompokkan dengan cara tertentu.

Kolom Deskripsi
turn_id UUID giliran saat ini
message_id UUID pesan asisten yang sedang ditampilkan. Stabil di setiap kelompok dari pesan yang sama. Ini bukan id API msg_…, sehingga tidak dapat dikorelasikan dengan id pesan transkrip
index Indeks berbasis nol dari kelompok ini dalam pesan
final true pada kelompok terakhir pesan. Setiap pesan memiliki tepat satu kelompok terakhir
delta Baris-baris yang baru selesai sejak kelompok sebelumnya, termasuk baris baru penutup. Selalu berupa baris utuh, kecuali kelompok terakhir yang dapat berakhir di tengah baris. Dalam run interaktif, delta kelompok terakhir kosong saat pesan berakhir dengan baris baru, jadi perlakukan final, bukan delta yang tidak kosong, sebagai sinyal akhir pesan. Dalam run Agent SDK dan claude -p, panggilan tunggal membawa seluruh pesan
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "MessageDisplay",
  "turn_id": "0c9e6a2f-7d41-4f4e-9a15-3f4f7c2b8d10",
  "message_id": "5b2a9c8e-1f63-4d8a-b7c4-9e0d2a6f1c3b",
  "index": 0,
  "final": false,
  "delta": "Here is the plan:\n"
}

Output MessageDisplay

Selain kolom output JSON yang tersedia untuk semua hook, hook MessageDisplay dapat mengembalikan displayContent untuk menggantikan delta di layar:

Kolom Deskripsi
displayContent Teks yang ditampilkan sebagai pengganti delta. Hilangkan untuk menampilkan teks asli

Hook MessageDisplay tidak memiliki kontrol keputusan. Hook ini tidak dapat memblokir pesan atau mengubah apa yang disimpan dalam transkrip atau dikirim ke Claude. Claude Code menindaklanjuti displayContent dari output JSON-nya dan membuang systemMessage dan continue.

Contoh ini menghapus format markdown dari respons Claude untuk tampilan teks biasa. Skrip membaca setiap kelompok dari stdin, menghapus penanda tebal dan backtick kode inline dari delta, lalu mengembalikan hasilnya sebagai displayContent.

Daftarkan hook perintah untuk event ini di file pengaturan Anda:

{
"hooks": {
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.sh",
"args": []
}
]
}
]
}
}

Simpan skrip ini ke .claude/hooks/plain-display.sh di proyek Anda dan jadikan dapat dieksekusi dengan chmod +x:

#!/bin/bash
jq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'

Kelompok tanpa markdown diteruskan tanpa perubahan. Jika skrip gagal, misalnya karena jq tidak ada, Claude Code menampilkan teks asli dan mencatat kegagalan tersebut hanya di output debug, bukan di sesi.

PreToolUse

Berjalan setelah Claude membuat parameter tool dan sebelum memproses panggilan tool. Dicocokkan dengan nama tool apa pun kecuali EndConversation: tool bawaan seperti Bash, PowerShell, Edit, Write, Read, Glob, Grep, Agent, Workflow, WebFetch, WebSearch, AskUserQuestion, dan ExitPlanMode, serta nama tool MCP apa pun.

Untuk menjalankan hook saat file tertentu berubah di disk, apa pun yang menulisnya, gunakan FileChanged alih-alih mencocokkan tool pengeditan file berdasarkan nama. Tidak seperti PreToolUse, Claude Code menjalankan hook FileChanged setelah perubahan terjadi, dan hook tersebut tidak memiliki kontrol keputusan, sehingga tidak dapat memblokir penulisan.

Gunakan kontrol keputusan PreToolUse untuk mengizinkan, menolak, meminta konfirmasi, atau menunda panggilan tool.

Hook callback Agent SDK pada PreToolUse yang melebihi timeout-nya memblokir panggilan tool, dan Claude menerima hasil error yang menyebutkan timeout tersebut. Deny eksplisit yang dikembalikan oleh hook lain tetap diutamakan.

Input PreToolUse

Selain kolom input umum, hook PreToolUse menerima tool_name, tool_input, dan tool_use_id.

Untuk tool MCP, input juga membawa mcp_server, sebuah objek dengan name server dan source yang menyatakan dari mana definisi server berasal. Nilai source mencakup plugin, sdk, dan cakupan konfigurasi seperti user dan project. McpServerProvenance dalam referensi Agent SDK mencantumkan semuanya dan menjelaskan cara memperlakukan nilai yang tidak Anda kenali. Dasarkan keputusan kepercayaan pada source, bukan pada name atau prefiks nama tool mcp__<server>__. Kolom mcp_server memerlukan Claude Code v2.1.274 atau yang lebih baru.

Untuk tool file Write, Edit, dan Read, tool_input.file_path selalu absolut:

  • Claude Code memperluas ~ dan path relatif sebelum hook berjalan, sehingga hook yang mencocokkan path tidak dapat dilewati melalui ~ atau penulisan relatif dari path yang sama
  • Di Windows, path tiba dengan pemisah backslash, bahkan saat hook Anda berjalan di Git Bash tempat $PWD terlihat seperti /c/project
  • Perbandingan yang ditulis dengan garis miring maju, seperti pemeriksaan /src/, tidak pernah cocok dengan path backslash, dan panggilan tool berlanjut seolah-olah hook tidak memiliki apa pun untuk diblokir
  • Normalkan pemisah sebelum membandingkan: FILE_PATH="${FILE_PATH//\\//}" di Bash, atau file_path.replace("\\", "/") di Python, lalu cocokkan segmen path seperti /src/ alih-alih menambatkan dengan ^, karena path bersifat absolut

Panggilan Write di Windows mengirimkan:

{
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "C:\\project\\src\\index.ts",
    "content": "..."
  },
  ...
}

Kolom tool_input bergantung pada tool-nya:

Bash

Mengeksekusi perintah shell.

Kolom Tipe Contoh Deskripsi
command string "npm test" Perintah shell yang akan dieksekusi
description string "Run test suite" Deskripsi opsional tentang apa yang dilakukan perintah
timeout number 120000 Timeout opsional dalam milidetik. Nilai di atas maksimum dikurangi ke nilai maksimum alih-alih ditolak
run_in_background boolean false Apakah perintah dijalankan di latar belakang

Saat perintah Bash mengubah file di repositori Git, Claude Code dapat merekam apa yang berubah. Claude Code merekam perubahan di setiap mode izin saat pengaturan bashEditDiffEnabled mengaktifkan perekaman; entri pengaturan tersebut menyebutkan file mana yang dapat mengaturnya. Jika tidak, Claude Code hanya merekamnya dalam auto mode dan mode bypassPermissions, dan hanya saat Claude Code mengarahkan Claude untuk mengedit file melalui Bash. Atur bashEditDiffEnabled ke false untuk mematikan perekaman. Perintah latar belakang dan perintah read-only tidak membawa diff.

Hook PostToolUse Anda kemudian menerima file yang berubah dalam tool_response.bashEditDiff. Daftar tersebut mencakup apa yang berubah di bawah repositori selama perintah berjalan. File yang diabaikan Git dan file di submodule tidak dicantumkan. Memerlukan Claude Code v2.1.269 atau yang lebih baru.

changedFiles dan files mencantumkan apa yang diubah oleh perintah; kolom-kolom lainnya menyatakan seberapa lengkap dan seberapa andal daftar tersebut.

Kolom Tipe Contoh Deskripsi
changedFiles array ["/path/to/src/app.ts"] Path absolut file-file yang diubah oleh perintah, paling banyak 200. Ada setiap kali files berisi diff atau moreFiles di atas nol
files array [{"filePath": "/path/to/src/app.ts", "hunks": [...]}] Diff dari hingga 5 file yang berubah, untuk ditampilkan. created atau deleted bernilai true untuk file yang ditambahkan atau dihapus oleh perintah
moreFiles number 2 Jumlah file yang berubah tanpa diff di files
unavailable boolean true Diatur saat diff tidak lengkap atau tidak dapat diambil
skipped boolean true Diatur untuk perintah Git yang memindahkan working tree, seperti git checkout atau git stash, sehingga Claude Code tidak mengambil diff
shared boolean true Diatur saat panggilan tool Bash lain, seperti milik subagent, berjalan di repositori yang sama pada saat yang sama, sehingga beberapa perubahan yang tercantum mungkin berasal dari perintah tersebut
PowerShell

Mengeksekusi perintah PowerShell. Lihat tool PowerShell untuk ketersediaan berdasarkan platform.

Kolom-kolomnya sama dengan tool Bash, dengan string perintah di command:

Kolom Tipe Contoh Deskripsi
command string "Get-ChildItem -Recurse" Perintah PowerShell yang akan dieksekusi
description string "List files recursively" Deskripsi opsional tentang apa yang dilakukan perintah
timeout number 120000 Timeout opsional dalam milidetik
run_in_background boolean false Apakah perintah dijalankan di latar belakang

Cocokkan Bash|PowerShell dalam hook yang memeriksa perintah shell, sehingga hook tersebut mencakup kedua tool:

  • Di Windows, di mana pun tool PowerShell diaktifkan, Claude memperlakukan PowerShell sebagai shell utama dan mengarahkan perintah shell melaluinya.
  • Di Windows tanpa Git Bash, tool ini diaktifkan secara otomatis dan Claude Code sama sekali tidak mendaftarkan tool Bash.
  • Hook yang hanya mencocokkan Bash tidak pernah dipicu di sana.
Write

Membuat atau menimpa file.

Kolom Tipe Contoh Deskripsi
file_path string "/path/to/file.txt" Path absolut ke file yang akan ditulis
content string "file content" Konten yang akan ditulis ke file
Edit

Mengganti string dalam file yang sudah ada.

Kolom Tipe Contoh Deskripsi
file_path string "/path/to/file.txt" Path absolut ke file yang akan diedit
old_string string "original text" Teks yang akan dicari dan diganti
new_string string "replacement text" Teks pengganti
replace_all boolean false Apakah semua kemunculan diganti
Read

Membaca isi file.

Kolom Tipe Contoh Deskripsi
file_path string "/path/to/file.txt" Path absolut ke file yang akan dibaca
offset number 10 Nomor baris opsional untuk mulai membaca
limit number 50 Jumlah baris opsional yang akan dibaca
Glob

Menemukan file yang cocok dengan pola glob.

Kolom Tipe Contoh Deskripsi
pattern string "**/*.ts" Pola glob untuk mencocokkan file
path string "/path/to/dir" Direktori opsional untuk pencarian. Default-nya adalah direktori kerja saat ini
Grep

Mencari isi file dengan ekspresi reguler.

Kolom Tipe Contoh Deskripsi
pattern string "TODO.*fix" Pola ekspresi reguler yang akan dicari
path string "/path/to/dir" File atau direktori opsional untuk pencarian
glob string "*.ts" Pola glob opsional untuk memfilter file
output_mode string "content" "content", "files_with_matches", atau "count". Default-nya adalah "files_with_matches"
-i boolean true Pencarian tanpa membedakan huruf besar/kecil
multiline boolean false Mengaktifkan pencocokan multibaris
WebFetch

Mengambil dan memproses konten web.

Kolom Tipe Contoh Deskripsi
url string "https://example.com/api" URL untuk mengambil konten
prompt string "Extract the API endpoints" Prompt yang dijalankan pada konten yang diambil
WebSearch

Mencari di web.

Kolom Tipe Contoh Deskripsi
query string "react hooks best practices" Kueri pencarian
allowed_domains array ["docs.example.com"] Opsional: hanya sertakan hasil dari domain-domain ini
blocked_domains array ["spam.example.com"] Opsional: kecualikan hasil dari domain-domain ini
Agent

Membuat subagent.

Kolom Tipe Contoh Deskripsi
prompt string "Find all API endpoints" Tugas yang akan dilakukan agent
description string "Find API endpoints" Deskripsi singkat tugas
subagent_type string "Explore" Jenis agent khusus yang akan digunakan
model string "sonnet" Alias model opsional untuk menimpa default

Saat panggilan Agent di latar depan selesai, hook PostToolUse Anda menerima hasil dan telemetri run subagent dalam tool_response. Baca kolom-kolom ini untuk memeriksa run; untuk rekapitulasi token dan biaya di seluruh subagent, gunakan penghitung token dan biaya yang difilter ke query_source "subagent", karena totalTokens dan usage hanya mencakup permintaan terakhir:

Kolom Tipe Contoh Deskripsi
status string "completed" "completed" untuk subagent latar depan, "async_launched" untuk subagent latar belakang. Mulai v2.1.198, subagent berjalan di latar belakang secara default, sehingga run_in_background yang dihilangkan juga menghasilkan "async_launched"
agentId string "a4d2c8f1e0b3a297" Pengidentifikasi untuk run subagent
content array [{"type": "text", "text": "Found 12 endpoints..."}] Blok teks terakhir subagent, atau, untuk subagent yang laporannya melalui SubagentHandback, catatan singkat tentang penyerahan tersebut sebagai gantinya
resolvedModel string "claude-sonnet-4-5" Model yang digunakan subagent saat memulai, yang dapat berbeda dari model yang diminta
modelsUsed array ["claude-sonnet-4-5", "claude-haiku-4-5"] Model yang digunakan secara berurutan, dengan pengulangan berturut-turut diciutkan; diatur hanya saat model ditukar di tengah run. Memerlukan Claude Code v2.1.212 atau yang lebih baru
totalTokens number 12450 Jumlah token dari permintaan API terakhir subagent: token input, output, dan cache digabungkan. Ini bukan total untuk seluruh run
totalDurationMs number 48211 Durasi waktu nyata run subagent
totalToolUseCount number 7 Jumlah panggilan tool yang dilakukan subagent
usage object {"input_tokens": 8320, ...} Rincian token per jenis dari permintaan API terakhir: input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens

Pada Claude Code v2.1.271 atau yang lebih baru, subagent yang berjalan dengan tool SubagentHandback, yang disediakan Claude Code dalam auto mode, mengirimkan laporannya melalui tool tersebut alih-alih mengembalikannya sebagai teks. Kolom content dari hasil completed-nya kemudian membawa catatan singkat tentang penyerahan tersebut, bukan laporan itu sendiri. Untuk membaca laporan, cocokkan hook PreToolUse atau PostToolUse pada SubagentHandback dan baca tool_input.message.

Untuk subagent latar belakang, tool mengembalikan hasil saat tugas berpindah ke latar belakang, sehingga tool_response tidak membawa kolom penggunaan: peluncuran latar belakang langsung mengembalikan hasil, dan tugas latar depan yang dipindahkan Claude Code ke latar belakang di tengah run mengembalikan hasil pada transisi tersebut. Respons ini memiliki status: "async_launched", agentId, description, prompt, outputFile, dan resolvedModel.

Pada respons completed, resolvedModel menyebutkan model yang digunakan subagent saat memulai, yang dapat berbeda dari nilai model di tool_input, misalnya saat availableModels atau override lain berlaku. Pada respons async_launched, resolvedModel menyebutkan model yang digunakan saat agent berpindah ke latar belakang, sehingga pertukaran yang terjadi sebelum pemindahan ke latar belakang tercermin di sana. modelsUsed dan perilaku resolvedModel pada saat pemindahan ke latar belakang memerlukan Claude Code v2.1.212 atau yang lebih baru.

AskUserQuestion

Mengajukan satu hingga empat pertanyaan pilihan ganda kepada pengguna.

Kolom Tipe Contoh Deskripsi
questions array [{"question": "Which framework?", "header": "Framework", "options": [{"label": "React"}], "multiSelect": false}] Pertanyaan yang akan ditampilkan, masing-masing dengan string question, header singkat, array options, dan flag multiSelect opsional
answers object {"Which framework?": "React"} Opsional. Memetakan teks pertanyaan ke label opsi yang dipilih. Jawaban pilihan ganda menggabungkan label dengan koma. Claude tidak mengatur kolom ini; berikan melalui updatedInput untuk menjawab secara terprogram
ExitPlanMode

Menyajikan rencana dan meminta pengguna menyetujuinya sebelum Claude meninggalkan plan mode. Claude menulis rencana ke file di disk sebelum memanggil tool, sehingga tool_input literal dari model biasanya kosong. Claude Code menyisipkan konten rencana dan path file sebelum meneruskan input ke hook.

Kolom Tipe Contoh Deskripsi
plan string "## Refactor auth\n1. Extract..." Konten rencana dalam Markdown. Disisipkan dari file rencana di disk
planFilePath string "/Users/.../plans/refactor-auth.md" Path ke file rencana. Disisipkan
allowedPrompts array [{"tool": "Bash", "prompt": "run tests"}] Deprecated. Claude Code menerima kolom ini tetapi mengabaikannya. Sebelum v2.1.205, kolom ini membawa izin berbasis prompt yang diminta Claude untuk mengimplementasikan rencana

Di PostToolUse, tool_response adalah objek dengan kolom plan dan filePath yang berisi rencana yang disetujui, ditambah flag status internal. Baca tool_response.plan untuk konten rencana alih-alih membaca ulang file dari disk.

Kontrol keputusan PreToolUse

Hook PreToolUse dapat mengontrol apakah panggilan tool berlanjut. Tidak seperti hook lain yang menggunakan kolom decision tingkat atas, PreToolUse mengembalikan keputusannya di dalam objek hookSpecificOutput. Ini memberikan kontrol yang lebih kaya: empat hasil (allow, deny, ask, atau defer) ditambah kemampuan untuk memodifikasi input tool sebelum eksekusi.

Kolom Deskripsi
permissionDecision "allow" melewati permintaan izin, kecuali untuk tindakan yang tidak disetujui otomatis oleh mode apa pun dan untuk AskUserQuestion dan ExitPlanMode, yang memerlukan updatedInput yang dipasangkan dengannya. "deny" mencegah panggilan tool. "ask" meminta pengguna untuk mengonfirmasi. "defer" keluar dengan baik sehingga tool dapat dilanjutkan nanti. Aturan deny dan ask tetap dievaluasi apa pun yang dikembalikan hook
permissionDecisionReason Untuk "ask", ditampilkan kepada pengguna tetapi tidak kepada Claude. Untuk "deny", ditampilkan kepada Claude. Untuk "allow" dan "defer", hanya ditulis ke log debug
updatedInput Memodifikasi parameter input tool sebelum eksekusi. Menggantikan seluruh objek input, jadi sertakan kolom yang tidak berubah bersama kolom yang dimodifikasi. Claude Code mengevaluasi aturan izin dan kelayakan auto-background perintah Bash terhadap input yang dikembalikan hook Anda, bukan input yang dikirim Claude. Gabungkan dengan "allow" untuk menyetujui otomatis, atau "ask" untuk menampilkan input yang dimodifikasi kepada pengguna. Untuk "defer", diabaikan
additionalContext String yang ditambahkan ke konteks Claude bersama hasil tool. Diabaikan saat permissionDecision bernilai "defer". Lihat Menambahkan konteks untuk Claude

Saat beberapa hook PreToolUse mengembalikan keputusan yang berbeda, urutan prioritasnya adalah deny > defer > ask > allow.

Hook yang memblokir dengan keluar dengan kode 2 diarahkan dengan cara yang sama seperti "deny": Claude melihat pesan stderr sebagai alasan penolakan.

Saat hook mengembalikan "ask", permintaan izin yang ditampilkan kepada pengguna menyertakan label yang mengidentifikasi asal hook: [settings] untuk hook dari file pengaturan mana pun atau dari frontmatter agent, [plugin:<name>] untuk hook plugin, atau [skill] untuk hook dari frontmatter skill. Ini membantu pengguna memahami sumber konfigurasi mana yang meminta konfirmasi.

"ask" dari hook juga memaksa permintaan izin dalam auto mode: pengklasifikasi tetap dapat menolak panggilan tool, tetapi tidak dapat menyetujui panggilan tersebut secara diam-diam. Sebelum v2.1.211, pengklasifikasi dapat menyetujui perintah Bash yang berjalan di luar sandbox tanpa menampilkan permintaan yang diminta hook; pengklasifikasi tetap menerapkan aturan keamanannya sendiri pada perintah tersebut, dan "deny" dari hook selalu dihormati.

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "My reason here",
    "updatedInput": {
      "field_to_modify": "new value"
    },
    "additionalContext": "Current environment: production. Proceed with caution."
  }
}

Dalam mode non-interaktif dengan flag -p, Claude Code menawarkan AskUserQuestion dan ExitPlanMode hanya saat run memiliki host izin untuk menerima permintaan, seperti callback canUseTool Agent SDK. Tool-tool ini memerlukan interaksi pengguna. Mengembalikan permissionDecision: "allow" bersama dengan updatedInput memenuhi persyaratan tersebut: hook membaca input tool dari stdin, mengumpulkan jawaban melalui UI Anda sendiri, dan mengembalikannya dalam updatedInput sehingga tool berjalan tanpa meminta konfirmasi. Mengembalikan "allow" saja tidak cukup untuk tool-tool ini. Untuk AskUserQuestion, kembalikan array questions asli dan tambahkan objek answers yang memetakan teks setiap pertanyaan ke jawaban yang dipilih.

Mulai v2.1.199, tool MCP yang ditandai server-nya dengan _meta["anthropic/requiresUserInteraction"] lebih ketat: hook tidak dapat melewati permintaan persetujuannya dengan "allow", dengan atau tanpa updatedInput, karena Claude Code tidak dapat memastikan bahwa hook telah mengumpulkan interaksi yang dibutuhkan tool tersebut.

Menunda panggilan tool untuk nanti

"defer" ditujukan untuk integrasi yang menjalankan claude -p sebagai subproses dan membaca output JSON-nya, seperti aplikasi Agent SDK atau UI kustom yang dibangun di atas Claude Code. Nilai ini memungkinkan proses pemanggil menjeda Claude pada panggilan tool, mengumpulkan input melalui antarmukanya sendiri, dan melanjutkan dari titik terakhir. Claude Code hanya menghormati nilai ini dalam mode non-interaktif dengan flag -p. Dalam sesi interaktif, Claude Code mencatat peringatan ke log dan mengabaikan hasil hook.

Tool AskUserQuestion adalah kasus yang umum: Claude ingin menanyakan sesuatu kepada pengguna, tetapi tidak ada terminal untuk menjawabnya. Run -p menawarkan AskUserQuestion hanya saat memiliki host izin, seperti tool MCP yang Anda berikan dengan --permission-prompt-tool, jadi mulai run dengan salah satunya. Alur bolak-baliknya bekerja seperti ini:

  1. Claude memanggil AskUserQuestion. Hook PreToolUse dipicu.
  2. Hook mengembalikan permissionDecision: "defer". Tool tidak dieksekusi. Proses keluar dengan stop_reason: "tool_deferred" dan panggilan tool yang tertunda dipertahankan dalam transkrip.
  3. Proses pemanggil membaca deferred_tool_use dari hasil SDK, menampilkan pertanyaan di UI-nya sendiri, dan menunggu jawaban.
  4. Proses pemanggil menjalankan claude -p --resume <session-id> dengan host izin yang sama. Panggilan tool yang sama memicu PreToolUse lagi.
  5. Hook mengembalikan permissionDecision: "allow" dengan jawaban di updatedInput. Tool dieksekusi dan Claude melanjutkan.

Kolom deferred_tool_use membawa id, name, dan input tool. input adalah parameter yang dihasilkan Claude untuk panggilan tool, ditangkap sebelum eksekusi:

{
  "type": "result",
  "subtype": "success",
  "stop_reason": "tool_deferred",
  "session_id": "abc123",
  "deferred_tool_use": {
    "id": "toolu_01abc",
    "name": "AskUserQuestion",
    "input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React"}, {"label": "Vue"}], "multiSelect": false }] }
  }
}

Tidak ada timeout atau batas retry. Sesi tetap ada di disk hingga Anda melanjutkannya, tunduk pada pembersihan retensi cleanupPeriodDays, yang menghapus file sesi setelah 30 hari secara default, mengikuti aturan pembersihan retensi. Jika jawaban belum siap saat Anda melanjutkan, hook dapat mengembalikan "defer" lagi dan proses keluar dengan cara yang sama. Proses pemanggil mengontrol kapan loop dihentikan dengan akhirnya mengembalikan "allow" atau "deny" dari hook.

"defer" hanya berfungsi saat Claude melakukan satu panggilan tool dalam giliran tersebut. Jika Claude melakukan beberapa panggilan tool sekaligus, "defer" diabaikan dengan peringatan dan tool berlanjut melalui alur izin normal. Batasan ini ada karena resume hanya dapat menjalankan ulang satu tool: tidak ada cara untuk menunda satu panggilan dari sekelompok panggilan tanpa membiarkan panggilan lainnya tidak terselesaikan.

Jika tool yang ditunda tidak lagi tersedia saat Anda melanjutkan, proses keluar dengan stop_reason: "tool_deferred_unavailable" dan is_error: true sebelum hook dipicu. Ini terjadi saat server MCP yang menyediakan tool tersebut tidak terhubung untuk sesi yang dilanjutkan. Payload deferred_tool_use tetap disertakan sehingga Anda dapat mengidentifikasi tool mana yang hilang.

PermissionRequest

Berjalan saat Claude Code akan meminta izin Anda untuk menggunakan tool. Dalam sesi yang tidak dapat menampilkan permintaan izin, seperti subagent latar belakang dalam mode non-interaktif, Claude Code tetap menjalankan hook ini, dan jika tidak ada hook yang mengembalikan keputusan, Claude Code menolak panggilan tool tersebut. Gunakan kontrol keputusan PermissionRequest untuk mengizinkan atau menolak atas nama pengguna.

Gunakan event ini saat Anda memerlukan sinyal tepat pada saat Claude meminta izin untuk menggunakan tool. Claude Code menjalankan hook Notification dengan jenis permission_prompt hanya setelah permintaan izin menunggu sekitar enam detik.

Claude Code tidak menjalankan hook PermissionRequest untuk permintaan jaringan dari perintah yang berjalan dalam sandbox. Untuk mendapatkan sinyal untuk permintaan izin tersebut, gunakan jenis notifikasi permission_prompt.

Dicocokkan dengan nama tool, nilai yang sama seperti PreToolUse.

Input PermissionRequest

Hook PermissionRequest menerima kolom tool_name dan tool_input seperti hook PreToolUse, tetapi tanpa tool_use_id. Untuk tool MCP, hook ini juga menerima objek mcp_server. Array permission_suggestions opsional berisi pembaruan izin yang disarankan Claude Code untuk permintaan ini, seperti menambahkan aturan allow atau mengubah mode izin.

Array permission_suggestions bukan daftar persis dari opsi yang Anda lihat, karena setiap dialog izin membangun opsinya sendiri. Beberapa dialog, seperti dialog untuk pengeditan file, sama sekali tidak membaca array tersebut dan menurunkan opsinya dari permintaan itu sendiri. Dialog yang membacanya tetap dapat menahan opsi yang sarannya tetap ada di array, misalnya saat allowManagedPermissionRulesOnly menyembunyikan opsi penyimpanan aturan. Dialog juga dapat menawarkan opsi yang tidak memiliki entri saran, seperti Yes, and switch to auto mode, yang mengubah mode izin secara langsung alih-alih melalui pembaruan izin.

Hook PreToolUse berjalan sebelum setiap panggilan tool, baik memerlukan izin maupun tidak. Hook PermissionRequest hanya berjalan saat Claude Code akan meminta izin Anda, atau saat Claude Code akan menolak secara otomatis panggilan yang tidak dapat menampilkan permintaan izin. Kedua event tersebut tidak dipicu untuk EndConversation.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PermissionRequest",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf node_modules",
    "description": "Remove node_modules directory"
  },
  "permission_suggestions": [
    {
      "type": "addRules",
      "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
      "behavior": "allow",
      "destination": "localSettings"
    }
  ]
}

Kontrol keputusan PermissionRequest

Hook PermissionRequest dapat mengizinkan atau menolak permintaan izin. Selain kolom output JSON yang tersedia untuk semua hook, skrip hook Anda dapat mengembalikan objek decision dengan kolom khusus event berikut:

Kolom Deskripsi
behavior "allow" memberikan izin, "deny" menolaknya. Aturan deny dan ask tetap dievaluasi, sehingga hook yang mengembalikan "allow" tidak menimpa aturan deny yang cocok
updatedInput Hanya untuk "allow": memodifikasi parameter input tool sebelum eksekusi. Menggantikan seluruh objek input, jadi sertakan kolom yang tidak berubah bersama kolom yang dimodifikasi. Input yang dimodifikasi dievaluasi ulang terhadap aturan deny dan ask
updatedPermissions Hanya untuk "allow": array entri pembaruan izin yang akan diterapkan, seperti menambahkan aturan allow atau mengubah mode izin sesi
message Hanya untuk "deny": memberi tahu Claude mengapa izin ditolak
interrupt Hanya untuk "deny": jika true, menghentikan Claude

Hook yang keluar dengan kode 2 tanpa objek decision membiarkan alur izin tidak berubah, dan stderr-nya dibuang. Hanya objek decision yang dapat memberikan atau menolak permintaan.

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": {
        "command": "npm run lint"
      }
    }
  }
}

Entri pembaruan izin

Kolom output updatedPermissions dan kolom input permission_suggestions sama-sama menggunakan array objek entri yang sama. Setiap entri memiliki type yang menentukan kolom-kolom lainnya, dan destination yang mengontrol tempat perubahan ditulis.

type Kolom Efek
addRules rules, behavior, destination Menambahkan aturan izin. rules adalah array objek {toolName, ruleContent?}. Hilangkan ruleContent untuk mencocokkan seluruh tool. behavior bernilai "allow", "deny", atau "ask"
replaceRules rules, behavior, destination Mengganti semua aturan dengan behavior yang diberikan di destination dengan rules yang disediakan
removeRules rules, behavior, destination Menghapus aturan yang cocok dengan behavior yang diberikan
setMode mode, destination Mengubah mode izin. Mode yang valid adalah default, auto, acceptEdits, dontAsk, bypassPermissions, plan, dan manual sebagai alias untuk default. Alias manual memerlukan Claude Code v2.1.200 atau yang lebih baru
addDirectories directories, destination Menambahkan direktori kerja. directories adalah array string path
removeDirectories directories, destination Menghapus direktori kerja

Field destination pada setiap entri menentukan apakah perubahan tetap berada di memori atau disimpan ke file pengaturan.

destination Menulis ke
session hanya di memori, dibuang ketika sesi berakhir
localSettings .claude/settings.local.json
projectSettings .claude/settings.json
userSettings ~/.claude/settings.json

Sebuah hook dapat mengembalikan salah satu permission_suggestions yang diterimanya sebagai output updatedPermissions miliknya sendiri.

PostToolUse

Berjalan segera setelah sebuah tool berhasil diselesaikan.

Mencocokkan berdasarkan nama tool, dengan nilai yang sama seperti PreToolUse.

Lakukan pencocokan yang lebih luas ketika nama tool bukan filter yang tepat:

  • Untuk menjalankan hook setelah tool apa pun berhasil diselesaikan, hilangkan matcher atau atur ke "*". Hook Anda kemudian dapat mengetahui sendiri apa yang berubah, misalnya dengan menjalankan git status --porcelain, yang juga mencantumkan file yang tidak dilacak yang terlewat oleh git diff. Untuk panggilan tool yang gagal, tambahkan hook yang sama di bawah PostToolUseFailure.
  • Untuk menjalankan hook ketika file tertentu berubah di disk, apa pun yang menulisnya, gunakan FileChanged. Claude Code tidak menjalankan hook PostToolUse yang mencocokkan Edit|Write ketika perintah Bash atau proses di luar Claude Code menulis ulang file yang sama.

Input PostToolUse

Hook PostToolUse dipicu setelah sebuah tool berhasil dieksekusi. Input mencakup tool_input, yaitu argumen yang dikirim ke tool, dan tool_response, yaitu hasil yang dikembalikannya. Skema persis untuk keduanya bergantung pada tool. Path tool_input pada tool file datang dalam format yang sama seperti untuk PreToolUse: selalu absolut, dengan pemisah native platform, sehingga menggunakan backslash di Windows. Untuk tool MCP, input juga membawa objek mcp_server.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "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",
    "type": "create"
  },
  "tool_use_id": "toolu_01ABC123...",
  "duration_ms": 12
}
Field Deskripsi
duration_ms Opsional. Waktu eksekusi tool dalam milidetik. Tidak termasuk waktu yang dihabiskan dalam permintaan izin dan hook PreToolUse

Kontrol keputusan PostToolUse

Hook PostToolUse dapat memberikan umpan balik kepada Claude setelah eksekusi tool. Selain field output JSON yang tersedia untuk semua hook, skrip hook Anda dapat mengembalikan field khusus event berikut:

Field Deskripsi
decision "block" menambahkan reason di samping hasil tool. Claude tetap melihat output asli; untuk menggantinya, gunakan updatedToolOutput
reason Penjelasan yang ditampilkan kepada Claude ketika decision adalah "block"
additionalContext String yang ditambahkan ke konteks Claude bersama hasil tool. Lihat Menambahkan konteks untuk Claude
classifierContext Catatan singkat tentang hasil panggilan ini untuk pengklasifikasi auto mode, bukan untuk Claude. Lihat Memberi anotasi pada hasil untuk pengklasifikasi auto mode. Memerlukan Claude Code v2.1.236 atau lebih baru
updatedToolOutput Mengganti output tool dengan nilai yang diberikan sebelum dikirim ke Claude. Nilai harus sesuai dengan bentuk output tool
updatedMCPToolOutput Mengganti output hanya untuk tool MCP. Sebaiknya gunakan updatedToolOutput, yang berfungsi untuk semua tool

Contoh di bawah ini mengganti output dari panggilan Bash. Nilai pengganti sesuai dengan bentuk output tool Bash:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Additional information for Claude",
    "updatedToolOutput": {
      "stdout": "[redacted]",
      "stderr": "",
      "interrupted": false,
      "isImage": false
    }
  }
}

Memberi anotasi pada hasil untuk pengklasifikasi auto mode

Kembalikan classifierContext untuk mengirim catatan singkat tentang hasil panggilan tool ke pengklasifikasi auto mode, bukan ke Claude. Pengklasifikasi tidak pernah menerima hasil tool itu sendiri, sehingga field ini adalah cara yang didukung untuk memberitahunya sesuatu tentang apa yang dikembalikan suatu panggilan sebelum ia meninjau tindakan berikutnya. Field ini memerlukan Claude Code v2.1.236 atau lebih baru.

Contoh di bawah ini memberi tahu pengklasifikasi dari mana output sebuah query berasal:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "classifierContext": "This query ran against the staging database, not production."
  }
}

Seberapa besar bobot yang diberikan pengklasifikasi pada catatan tersebut bergantung pada tempat Anda mengonfigurasi hook:

  • Hook yang dikonfigurasi di Claude Code: untuk hook dari file pengaturan, plugin, skill, dan frontmatter agent, pengklasifikasi memperlakukan catatan tersebut sebagai konteks yang belum diverifikasi dan disediakan oleh aplikasi. Catatan tersebut tidak pernah menetapkan niat pengguna, dan jika catatan itu mengklaim bahwa Anda menyetujui atau meminta sesuatu, pengklasifikasi memeriksa klaim tersebut terhadap pesan Anda sendiri dalam percakapan
  • Callback Agent SDK dalam proses: ketika aplikasi yang menyematkan Claude Code mendaftarkan hook sebagai callback TypeScript SDK dan mengembalikan catatan selama sesi aktif, pengklasifikasi dapat menimbang pernyataan pengguna yang diteruskan dalam catatan sebagai niat pengguna. Pernyataan seperti itu dapat memenuhi persyaratan persetujuan yang akan diterima pengklasifikasi dari pesan yang Anda kirim, tetapi tidak pernah mencabut blokir yang juga tidak dapat dicabut oleh pesan Anda sendiri. Setelah sesi dilanjutkan, Claude Code memperlakukan catatan yang dipulihkan sebagai konteks yang belum diverifikasi. Ketika hook dari kedua kelompok memberi anotasi pada panggilan yang sama, pengklasifikasi memperlakukan catatan gabungan sebagai belum diverifikasi

Claude Code menerapkan batasan berikut saat mengirimkan catatan:

  • Panjang: Claude Code membatasi catatan untuk satu panggilan tool hingga 2.000 karakter dan memotong sisanya. Batas ini dibagi di antara setiap hook yang merespons panggilan tersebut
  • Hanya respons sinkron: Claude Code mengabaikan field ini dalam respons hook yang berjalan di latar belakang, karena respons tersebut tiba setelah Claude Code mencatat hasil tool
  • Panggilan yang tidak dicatat oleh pengklasifikasi: transkrip pengklasifikasi menghilangkan pencarian read-only seperti pembacaan file dan pencarian. Claude Code membuang catatan yang dilampirkan ke salah satu panggilan tersebut
  • Interaksi dengan penulisan ulang: ketika catatan menjelaskan output yang Anda ganti dengan updatedToolOutput, kembalikan kedua field dalam respons hook yang sama. Claude Code membuang catatan tersebut jika penulisan ulang itu ditolak atau penulisan ulang hook lain menggantikannya. Claude Code mengirimkan catatan yang Anda kembalikan tanpa penulisan ulang bahkan ketika hook lain menulis ulang output

PostToolUseFailure

Berjalan ketika sebuah tool yang sudah mulai dieksekusi gagal: tool melempar error, atau tool MCP mengembalikan hasil error. Gunakan ini untuk mencatat kegagalan, mengirim peringatan, atau memberikan umpan balik korektif kepada Claude.

Mencocokkan berdasarkan nama tool, dengan nilai yang sama seperti PreToolUse.

Input PostToolUseFailure

Hook PostToolUseFailure menerima field tool_name dan tool_input yang sama seperti PostToolUse, bersama dengan informasi error sebagai field tingkat atas. Untuk tool MCP, hook juga menerima objek mcp_server. Misalnya, perintah npm test yang gagal mungkin mengirimkan:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUseFailure",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite"
  },
  "tool_use_id": "toolu_01ABC123...",
  "error": "Exit code 1\nError: Cannot find module 'express'",
  "is_interrupt": false,
  "duration_ms": 4187
}
Field Deskripsi
error String yang menjelaskan apa yang salah. Formatnya bergantung pada tool yang gagal
is_interrupt Boolean opsional. True ketika kegagalan mencapai Claude Code sebagai pembatalan, bukan sebagai error yang dilaporkan oleh tool. Membatalkan tool yang sedang berjalan tidak memicu hook ini; hasil tool membawa pesan interupsi sebagai gantinya
duration_ms Opsional. Waktu eksekusi tool dalam milidetik. Tidak termasuk waktu yang dihabiskan dalam permintaan izin dan hook PreToolUse

String error umumnya merupakan teks yang sama yang diterima Claude sebagai hasil tool yang gagal. Formatnya bervariasi menurut tool dan jenis kegagalan. Dasarkan hook Anda pada tool_name, is_interrupt, dan baris pertama Exit code N; perlakukan sisa string sebagai teks tampilan, bukan format yang stabil.

  • Untuk Bash dan PowerShell, perintah yang berjalan dan keluar menghasilkan baris pertama Exit code N, lalu output apa pun yang dihasilkan perintah sebagai satu blok dengan stdout dan stderr yang saling berselang-seling
  • Sebuah payload juga dapat membawa pesan kegagalan polos tanpa baris exit code, ketika Claude Code tidak dapat memulai proses shell itu sendiri
  • Claude Code memotong bagian tengah string panjang di sekitar penanda ... [N characters truncated] ..., dan dapat menyisipkan baris miliknya sendiri, seperti Command timed out after 2m 0s

Kontrol keputusan PostToolUseFailure

Hook PostToolUseFailure dapat memberikan konteks kepada Claude setelah kegagalan tool. Selain field output JSON yang tersedia untuk semua hook, skrip hook Anda dapat mengembalikan field khusus event berikut:

Field Deskripsi
additionalContext String yang ditambahkan ke konteks Claude bersama error. Lihat Menambahkan konteks untuk Claude
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUseFailure",
    "additionalContext": "Additional information about the failure for Claude"
  }
}

PostToolBatch

Berjalan sekali setelah setiap panggilan tool dalam satu batch selesai diproses, sebelum Claude Code mengirim permintaan berikutnya ke model. PostToolUse dipicu sekali per tool, yang berarti ia dipicu secara bersamaan ketika Claude melakukan panggilan tool paralel. PostToolBatch dipicu tepat sekali dengan batch lengkap, sehingga merupakan tempat yang tepat untuk menyuntikkan konteks yang bergantung pada kumpulan tool yang berjalan, bukan pada satu tool tertentu. Tidak ada matcher untuk event ini.

Input PostToolBatch

Selain field input umum, hook PostToolBatch menerima tool_calls, sebuah array yang menjelaskan setiap panggilan tool dalam batch:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolBatch",
  "tool_calls": [
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/accounts.py"},
      "tool_use_id": "toolu_01...",
      "tool_response": "1\tfrom __future__ import annotations\n2\t..."
    },
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/transactions.py"},
      "tool_use_id": "toolu_02...",
      "tool_response": "1\tfrom __future__ import annotations\n2\t..."
    }
  ]
}

tool_response berisi konten yang sama yang diterima model dalam blok tool_result yang sesuai. Nilainya adalah string terserialisasi atau array content-block, persis seperti yang dikeluarkan tool. Untuk Read, itu berarti teks dengan awalan nomor baris, bukan konten file mentah. Respons bisa berukuran besar, jadi parse hanya field yang Anda butuhkan.

Kontrol keputusan PostToolBatch

Hook PostToolBatch dapat menyuntikkan konteks untuk Claude. Selain field output JSON yang tersedia untuk semua hook, skrip hook Anda dapat mengembalikan field khusus event berikut:

Field Deskripsi
additionalContext String konteks yang disuntikkan sekali sebelum panggilan model berikutnya. Lihat Menambahkan konteks untuk Claude untuk detail pengiriman, apa yang perlu dimasukkan, dan bagaimana sesi yang dilanjutkan menangani nilai sebelumnya
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolBatch",
    "additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
  }
}

Mengembalikan decision: "block" atau continue: false menghentikan agentic loop sebelum panggilan model berikutnya. Pesan pemblokiran berasal dari reason atau stopReason JSON, atau dari stderr pada exit 2. Anda melihatnya sebagai peringatan di transkrip, dan pesan itu tetap ada dalam percakapan, sehingga Claude melihatnya ketika percakapan berlanjut.

PermissionDenied

Berjalan ketika auto mode menolak panggilan tool, termasuk ketika ia menolak tanpa putusan pengklasifikasi karena pemeriksaan keamanan yang terpisah dari auto mode menolak permintaan pengklasifikasi itu sendiri atau responsnya tidak dapat di-parse. Hook ini hanya dipicu dalam auto mode: hook ini tidak berjalan ketika Anda menolak dialog izin secara manual, ketika hook PreToolUse memblokir panggilan, atau ketika aturan deny cocok. Gunakan ini untuk mencatat penolakan, menyesuaikan konfigurasi, atau memberi tahu model bahwa ia boleh mencoba ulang panggilan tool.

Mencocokkan berdasarkan nama tool, dengan nilai yang sama seperti PreToolUse.

Input PermissionDenied

Selain field input umum, hook PermissionDenied menerima tool_name, tool_input, tool_use_id, dan reason. Untuk tool MCP, hook juga menerima objek mcp_server.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "auto",
  "hook_event_name": "PermissionDenied",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf /tmp/build",
    "description": "Clean build directory"
  },
  "tool_use_id": "toolu_01ABC123...",
  "reason": "[Irreversible Local Destruction]"
}
Field Deskripsi
reason Alasan penolakan. Untuk putusan pengklasifikasi, di sebagian besar sesi alasan ini menyebutkan aturan yang cocok dalam kurung siku, seperti [Data Exfiltration]; lihat Meninjau penolakan untuk bentuk lainnya. Untuk penolakan tanpa putusan, alasan dimulai dengan Auto mode could not evaluate this action and is blocking it for safety. Untuk penolakan karena model pengklasifikasi tidak tersedia, alasannya adalah teks tetap Classifier unavailable

Kontrol keputusan PermissionDenied

Hook PermissionDenied dapat memberi tahu model bahwa ia boleh mencoba ulang panggilan tool yang ditolak. Kembalikan objek JSON dengan hookSpecificOutput.retry diatur ke true:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionDenied",
    "retry": true
  }
}

Ketika retry bernilai true, Claude Code menambahkan pesan ke percakapan yang memberi tahu model bahwa ia boleh mencoba ulang panggilan tool. Claude Code tidak membatalkan penolakan itu sendiri. Jika hook Anda tidak mengembalikan JSON, atau mengembalikan retry: false, penolakan tetap berlaku dan model menerima pesan penolakan asli.

Claude Code mengabaikan retry: true ketika pengklasifikasi tidak menghasilkan putusan atas tindakan tersebut: responsnya tidak dapat di-parse, atau pemeriksaan keamanan yang terpisah dari auto mode menolak permintaan pengklasifikasi itu sendiri. Untuk penolakan tersebut, Claude Code sudah memberi tahu model dalam pesan penolakan apakah harus mencoba ulang nanti atau melanjutkan.

Notification

Berjalan ketika Claude Code mengirim notifikasi. Mencocokkan berdasarkan jenis notifikasi. Hilangkan matcher untuk menjalankan hook untuk semua jenis notifikasi.

Anda menerima event hook ini bahkan ketika notifikasi desktop dimatikan: pengaturan preferredNotifChannel, termasuk notifications_disabled, hanya mengubah cara Anda diberi peringatan, bukan apakah hook Anda berjalan.

Matcher Kapan dipicu
permission_prompt Claude memerlukan Anda untuk menyetujui penggunaan tool atau permintaan jaringan dari perintah yang di-sandbox, dan permintaan izin telah menunggu sekitar enam detik
idle_prompt Claude selesai merespons sekitar 60 detik yang lalu dan Anda belum mengetik sejak itu
auth_success Autentikasi selesai
elicitation_dialog Server MCP membuka formulir elicitation dan Anda belum mengetik selama sekitar enam detik
elicitation_url_dialog Server MCP meminta Anda membuka URL browser dan Anda belum mengetik selama sekitar enam detik
elicitation_complete Server MCP melaporkan bahwa elicitation mode URL telah selesai
elicitation_response Respons elicitation MCP dikirim kembali ke server
agent_needs_input Sesi latar belakang mulai menunggu input Anda saat agent view terbuka di terminal. Juga dipicu ketika sesi terminal menampilkan kepada Anda pertanyaan penyiapan terminal dari rekan tim agent atau pemberitahuan auto mode tentang biaya permintaan pengklasifikasi dan Anda belum mengetik selama sekitar enam detik
agent_completed Sesi latar belakang selesai atau gagal. Hanya dipicu saat agent view terbuka di terminal
quota_auto_resume_fired Claude Code melanjutkan tugas Anda setelah batas penggunaan claude.ai menjedanya: pada saat reset, atau lebih cepat ketika sesuatu yang Anda lakukan di Claude Code selama menunggu, seperti menambahkan kredit penggunaan, meningkatkan paket Anda, atau beralih model, membuat penggunaan tersedia kembali, dengan pengecualian pengaturan model
quota_auto_resume_stale Batas penggunaan claude.ai direset saat komputer Anda dalam mode tidur selama lebih dari sekitar 30 menit. Claude Code menunggu Anda menekan Enter alih-alih melanjutkan. Setelah tidur yang lebih singkat, Claude Code melanjutkan dan memicu quota_auto_resume_fired sebagai gantinya
quota_auto_resume_disabled Claude Code mengakhiri penantiannya untuk batas penggunaan claude.ai tanpa melanjutkan tugas Anda: autoContinueAtUsageLimit dimatikan atau waktu reset bergeser lebih dari 24 jam selama penantian yang dimulai Claude Code sendiri, tugas yang dilanjutkan terus mencapai batas, atau kelanjutan diblokir sebelum mencapai model. Tidak dipicu ketika Anda menekan Esc atau Ctrl+C, atau memilih Don't continue automatically

Jenis agent_needs_input dan agent_completed memerlukan Claude Code v2.1.198 atau lebih baru.

Jenis quota_auto_resume_fired, quota_auto_resume_stale, dan quota_auto_resume_disabled memerlukan Claude Code v2.1.234 atau lebih baru.

Dalam sesi terminal, permission_prompt untuk permintaan jaringan dari perintah yang di-sandbox memerlukan Claude Code v2.1.246 atau lebih baru.

agent_needs_input untuk pertanyaan penyiapan terminal dari rekan tim memerlukan Claude Code v2.1.248 atau lebih baru.

Claude Code mengatur waktu permission_prompt secara berbeda dalam sesi di mana ia mengirim permintaan izin ke callback canUseTool milik Agent SDK, yang merupakan cara Claude Desktop dan ekstensi VS Code menghosting Claude Code:

  • permission_prompt akan muncul sekitar enam detik setelah Claude meminta izin. Claude Code tidak menundanya saat Anda mengetik.
  • Jika Anda atau hook PermissionRequest menjawab lebih cepat, Claude Code tidak menjalankan permission_prompt.
  • Atur CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS ke 1 untuk mematikan permission_prompt dalam sesi ini.

Sebelum v2.1.233, permission_prompt tidak dipicu dalam sesi ini.

Gunakan matcher terpisah untuk menjalankan handler yang berbeda bergantung pada jenis notifikasi. Konfigurasi ini memicu skrip peringatan khusus izin ketika Claude memerlukan persetujuan izin dan notifikasi yang berbeda ketika Claude dalam keadaan idle:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/permission-alert.sh"
          }
        ]
      },
      {
        "matcher": "idle_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/idle-notification.sh"
          }
        ]
      }
    ]
  }
}

Input Notification

Selain field input umum, hook Notification menerima message dengan teks notifikasi, title opsional, dan notification_type yang menunjukkan jenis mana yang dipicu.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Notification",
  "message": "Claude needs your permission",
  "title": "Permission needed",
  "notification_type": "permission_prompt"
}

Hook Notification tidak dapat memblokir atau memodifikasi notifikasi. Claude Code membuang field systemMessage dan continue miliknya tetapi tetap mengeluarkan terminalSequence, yang menjadi andalan contoh notifikasi desktop. Hook Notification ditujukan untuk efek samping seperti meneruskan notifikasi ke layanan eksternal.

SubagentStart

Berjalan ketika Claude memunculkan subagent dengan tool Agent, ketika Claude melanjutkan subagent, dan setiap kali rekan tim tim agent dalam proses menangani pesan baru. Mendukung matcher untuk memfilter berdasarkan nama jenis agent. Untuk agent bawaan, ini adalah nama agent seperti general-purpose, Explore, atau Plan. Untuk subagent kustom, ini adalah field name dari frontmatter agent, bukan nama file.

Untuk subagent yang disertakan oleh plugin, jenis agent adalah pengidentifikasi dengan cakupan plugin seperti my-plugin:reviewer, bukan nama frontmatter polos. Titik dua menempatkan nama dengan cakupan plugin pada jalur regular expression, jadi jangkarkan matcher dengan ^ dan $ untuk kecocokan persis: ^my-plugin:reviewer$.

Input SubagentStart

Selain field input umum, hook SubagentStart menerima agent_id dengan pengidentifikasi unik untuk subagent dan agent_type dengan nama agent yang difilter oleh matcher.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SubagentStart",
  "agent_id": "agent-abc123",
  "agent_type": "Explore"
}

Hook SubagentStart tidak dapat memblokir pembuatan subagent, tetapi dapat menyuntikkan konteks ke dalam subagent. Selain field output JSON yang tersedia untuk semua hook, Anda dapat mengembalikan:

Field Deskripsi
additionalContext String yang ditambahkan ke konteks subagent di awal percakapannya, sebelum prompt pertamanya. Lihat Menambahkan konteks untuk Claude
{
  "hookSpecificOutput": {
    "hookEventName": "SubagentStart",
    "additionalContext": "Follow security guidelines for this task"
  }
}

Ketika hook berjalan lagi untuk subagent yang sama, Claude Code menyuntikkan konteks yang dikembalikan hanya jika konteks subagent belum menyimpan salinan dari eksekusi sebelumnya. Salinan yang disuntikkan saat peluncuran tetap di tempatnya, sehingga prompt cache subagent tetap utuh. Setelah auto-compaction membuang salinan tersebut, Claude Code menyuntikkan konteks dari eksekusi berikutnya lagi.

SubagentStop

Berjalan ketika subagent Claude Code telah selesai merespons. Mencocokkan berdasarkan jenis agent, dengan nilai yang sama seperti SubagentStart.

Input SubagentStop

Selain field input umum, hook SubagentStop menerima stop_hook_active, agent_id, agent_type, agent_transcript_path, dan last_assistant_message. Field agent_type adalah nilai yang digunakan untuk pemfilteran matcher. transcript_path adalah transkrip sesi utama, sedangkan agent_transcript_path adalah transkrip milik subagent sendiri yang disimpan dalam folder subagents/ bersarang. Field last_assistant_message berisi konten teks dari respons akhir subagent, sehingga hook dapat mengaksesnya tanpa mem-parse file transkrip.

Tidak setiap event SubagentStop berasal dari subagent yang dimunculkan Claude. Claude Code juga menjalankan agent internal untuk beberapa fiturnya sendiri, seperti saran prompt dan pertanyaan sampingan /btw, dan SubagentStop juga dipicu ketika salah satunya selesai. Untuk event tersebut, agent_type adalah nama agent yang dijalankan oleh sesi itu sendiri, seperti yang diatur dengan --agent atau pengaturan agent, dan berupa string kosong ketika sesi berjalan tanpa agent tersebut.

matcher yang menyebutkan jenis agent tidak cocok dengan agent_type yang kosong. Hook yang matcher-nya dihilangkan, "", atau "*", atau berupa regular expression yang cocok dengan string kosong, juga berjalan untuk event dengan agent_type kosong.

Pada Claude Code v2.1.271 atau lebih baru, subagent yang berjalan dengan tool SubagentHandback mengirimkan laporannya melalui tool tersebut sebelum berhenti. Field last_assistant_message kemudian berisi teks penutup subagent, jika ada, yang bukan laporan yang dikirimkan. Laporan tersebut adalah input message dari panggilan itu, yang diterima oleh hook PreToolUse atau PostToolUse yang mencocokkan SubagentHandback sebagai tool_input.message.

Hook SubagentStop juga menerima array background_tasks dan session_crons yang dijelaskan di bawah Input Stop. Kedua array tersebut dicakup ke sesi induk, bukan subagent.

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../abc123.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "SubagentStop",
  "stop_hook_active": false,
  "agent_id": "def456",
  "agent_type": "Explore",
  "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
  "last_assistant_message": "Analysis complete. Found 3 potential issues...",
  "background_tasks": [],
  "session_crons": []
}

Hook SubagentStop menggunakan format kontrol keputusan yang sama seperti hook Stop, termasuk hookSpecificOutput.additionalContext dengan hookEventName diatur ke "SubagentStop", untuk umpan balik non-error yang membuat subagent tetap berjalan. Mengembalikan decision: "block" dengan reason membuat subagent tetap berjalan dan mengirimkan reason ke subagent sebagai instruksi berikutnya. Hook yang memblokir dengan keluar menggunakan kode 2 mengirimkan pesan stderr-nya dengan cara yang sama. Untuk menyuntikkan konteks ke sesi induk setelah subagent kembali, gunakan hook PostToolUse pada tool Agent sebagai gantinya.

TaskCreated

Berjalan ketika sebuah tugas sedang dibuat melalui tool TaskCreate. Gunakan ini untuk menegakkan konvensi penamaan, mewajibkan deskripsi tugas, atau mencegah tugas tertentu dibuat. Dalam sesi tanpa tool Task, event ini tidak dipicu.

Hook TaskCreated tidak mendukung matcher dan dipicu pada setiap kejadian.

Input TaskCreated

Selain field input umum, hook TaskCreated menerima task_id, task_subject, dan secara opsional task_description, teammate_name, dan team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "TaskCreated",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "session-a1b2c3d4"
}
Field Deskripsi
task_id Pengidentifikasi tugas yang sedang dibuat
task_subject Judul tugas
task_description Deskripsi terperinci tugas. Mungkin tidak ada
teammate_name Nama rekan tim yang membuat tugas. Mungkin tidak ada
team_name Deprecated. Nama tim yang diturunkan dari sesi; akan dihapus dalam rilis mendatang

Kontrol keputusan TaskCreated

Hook TaskCreated dapat memblokir pembuatan dengan dua cara. Dengan cara mana pun, Claude Code menghapus tugas dan mengembalikan pesan Anda kepada Claude sebagai error tool. Claude Code mengabaikan continue: false dari event ini dan Claude tetap bekerja.

  • Exit code 2: Claude Code mengembalikan teks stderr sebagai pesan.
  • JSON {"decision": "block", "reason": "..."}: Claude Code mengembalikan reason sebagai pesan.

Contoh ini memblokir tugas yang subjeknya tidak mengikuti format yang diwajibkan:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
  echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
  exit 2
fi

exit 0

TaskCompleted

Berjalan ketika sebuah tugas sedang ditandai sebagai selesai. Ini dipicu dalam dua situasi: ketika agent mana pun secara eksplisit menandai tugas sebagai selesai melalui tool TaskUpdate, atau ketika rekan tim tim agent menyelesaikan gilirannya dengan tugas yang sedang berlangsung. Gunakan ini untuk menegakkan kriteria penyelesaian seperti lolos pengujian atau pemeriksaan lint sebelum tugas dapat ditutup.

Hook TaskCompleted tidak mendukung matcher dan dipicu pada setiap kejadian.

Input TaskCompleted

Selain field input umum, hook TaskCompleted menerima task_id, task_subject, dan secara opsional task_description, teammate_name, dan team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TaskCompleted",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "session-a1b2c3d4"
}
Field Deskripsi
task_id Pengidentifikasi tugas yang sedang diselesaikan
task_subject Judul tugas
task_description Deskripsi terperinci tugas. Mungkin tidak ada
teammate_name Nama rekan tim yang menyelesaikan tugas. Mungkin tidak ada
team_name Deprecated. Nama tim yang diturunkan dari sesi; akan dihapus dalam rilis mendatang

Kontrol keputusan TaskCompleted

Hook TaskCompleted mendukung dua cara untuk mengontrol penyelesaian tugas:

  • Exit code 2: tugas tidak ditandai sebagai selesai dan pesan stderr diumpankan kembali ke model sebagai umpan balik.
  • JSON {"continue": false, "stopReason": "..."}: ketika rekan tim yang menyelesaikan gilirannya memicu event, rekan tim dihentikan sepenuhnya, sesuai dengan perilaku hook Stop. stopReason ditampilkan kepada pengguna. Ketika tool TaskUpdate memicu event, Claude Code mengabaikan continue: false; exit code 2 tetap memblokir penyelesaian.

Contoh ini menjalankan pengujian dan memblokir penyelesaian tugas jika pengujian gagal:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

# Run the test suite
if ! npm test 2>&1; then
  echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
  exit 2
fi

exit 0

Stop

Berjalan ketika agent utama Claude Code telah selesai merespons. Tidak berjalan jika penghentian terjadi karena interupsi pengguna. Error API memicu StopFailure sebagai gantinya.

Input Stop

Selain field input umum, hook Stop menerima stop_hook_active, last_assistant_message, background_tasks, dan session_crons. Field stop_hook_active bernilai true ketika Claude Code sudah melanjutkan sebagai akibat dari hook stop. Periksa nilai ini atau proses transkrip untuk menghindari pemblokiran pada kondisi yang tidak akan pernah terselesaikan. Claude Code menerapkan batas 8 kelanjutan berturut-turut: setelah hook stop melanjutkan giliran delapan kali berturut-turut, Claude Code menimpa blokir berikutnya dan mengakhiri giliran. Untuk menaikkan batas, atur CLAUDE_CODE_STOP_HOOK_BLOCK_CAP.

Field last_assistant_message berisi konten teks dari respons akhir Claude, sehingga hook dapat mengaksesnya tanpa mem-parse file transkrip. Untuk hook yang bertindak pada giliran yang baru saja selesai, seperti hook pembacaan dengan suara keras atau notifikasi, gunakan field ini alih-alih membaca transcript_path: file transkrip tidak dijamin menyertakan pesan akhir pada saat Stop di semua versi.

Array background_tasks dan session_crons memungkinkan hook membedakan "sesi sudah selesai" dari "sesi dijeda menunggu pekerjaan latar belakang untuk membangunkannya kembali". Kedua array hadir ketika registri tugas dapat dijangkau dan kosong ketika tidak ada yang sedang berjalan atau dijadwalkan.

Setiap entri dalam background_tasks menjelaskan satu tugas yang sedang berjalan dan menggunakan field berikut:

Field Deskripsi
id Pengidentifikasi tugas
type Label jenis tugas yang mudah dibaca seperti shell, subagent, monitor, workflow, teammate, cloud session, atau MCP task. Setiap label mengidentifikasi fitur Claude Code mana yang membuat tugas tersebut. Menggunakan diskriminan mentah sebagai cadangan untuk jenis yang tidak dikenali
status Status tugas saat ini
description Deskripsi teks bebas, dibatasi hingga 1000 karakter dengan penanda … [+N chars] di dalam string ketika dipotong
command Baris perintah shell, dibatasi hingga 1000 karakter. Hanya ada untuk tugas shell
agent_type Nama jenis subagent. Hanya ada untuk tugas subagent
server Nama server MCP. Hanya ada untuk tugas monitor dan MCP task
tool Nama tool MCP. Hanya ada untuk tugas monitor dan MCP task
name Nama workflow. Hanya ada untuk tugas workflow

Setiap entri dalam session_crons menjelaskan satu wakeup terjadwal dengan cakupan sesi, yang bersumber dari CronCreate, ScheduleWakeup, dan /loop:

Field Deskripsi
id Pengidentifikasi tugas cron
schedule Ekspresi cron, misalnya 0 9 * * 1-5
recurring false untuk wakeup sekali jalan yang jadwalnya mengodekan satu waktu pemicu, true untuk tugas yang dipicu ulang pada setiap kecocokan
prompt Prompt yang dikirim ketika cron dipicu, dibatasi hingga 1000 karakter dengan penanda … [+N chars] yang sama

Contoh ini menunjukkan input Stop dengan satu tugas shell yang sedang berjalan dan satu cron berulang:

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "Stop",
  "stop_hook_active": true,
  "last_assistant_message": "I've completed the refactoring. Here's a summary...",
  "background_tasks": [
    {
      "id": "task-001",
      "type": "shell",
      "status": "running",
      "description": "tail logs",
      "command": "tail -f /var/log/syslog"
    }
  ],
  "session_crons": [
    {
      "id": "cron-001",
      "schedule": "0 9 * * 1-5",
      "recurring": true,
      "prompt": "check the build"
    }
  ]
}

Kontrol keputusan Stop

Hook Stop dan SubagentStop dapat mengontrol apakah Claude melanjutkan. Selain field output JSON yang tersedia untuk semua hook, skrip hook Anda dapat mengembalikan field khusus event berikut:

Field Deskripsi
decision "block" mencegah Claude berhenti. Hilangkan untuk mengizinkan Claude berhenti
reason Wajib ketika decision adalah "block". Memberi tahu Claude mengapa ia harus melanjutkan
hookSpecificOutput.additionalContext Umpan balik non-error untuk Claude. Percakapan berlanjut sehingga Claude dapat menindaklanjutinya, tetapi tidak seperti decision: "block", umpan balik ini ditampilkan di transkrip sebagai umpan balik hook, bukan error hook

Hook yang memblokir dengan keluar menggunakan kode 2 dirutekan dengan cara yang sama seperti reason: Claude menerima pesan stderr sebagai penjelasan mengapa ia harus melanjutkan.

{
  "decision": "block",
  "reason": "Must be provided when Claude is blocked from stopping"
}

Gunakan additionalContext ketika hook berfungsi sesuai rancangan dan memberikan panduan kepada Claude, seperti "jalankan test suite sebelum selesai". Ini menjaga percakapan tetap berjalan melalui perlindungan loop yang sama seperti decision: "block", yaitu input stop_hook_active dan batas 8 kelanjutan berturut-turut, tetapi transkrip melabelinya Stop hook feedback dan tidak ada notifikasi error hook yang ditampilkan:

{
  "hookSpecificOutput": {
    "hookEventName": "Stop",
    "additionalContext": "Please run the test suite before finishing"
  }
}

StopFailure

Berjalan sebagai pengganti Stop ketika giliran berakhir karena error API. Claude Code mengabaikan output dan exit code hook, kecuali terminalSequence. Gunakan ini untuk mencatat kegagalan, mengirim peringatan, atau mengambil tindakan pemulihan ketika Claude tidak dapat menyelesaikan respons karena rate limit, masalah autentikasi, atau error API lainnya.

Input StopFailure

Selain field input umum, hook StopFailure menerima error, error_details opsional, dan last_assistant_message opsional. Field error mengidentifikasi jenis error dan digunakan untuk pemfilteran matcher.

Field Deskripsi
error Jenis error: rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, account_on_hold, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, cloud_credential_error, atau unknown
error_details Detail tambahan tentang error, jika tersedia
last_assistant_message Teks error yang dirender dan ditampilkan dalam percakapan. Tidak seperti Stop dan SubagentStop, di mana field ini berisi output percakapan Claude, untuk StopFailure field ini berisi string error API itu sendiri, seperti "API Error: Rate limit reached"
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "StopFailure",
  "error": "rate_limit",
  "error_details": "429 Too Many Requests",
  "last_assistant_message": "API Error: Rate limit reached"
}

Hook StopFailure tidak memiliki kontrol keputusan. Hook ini hanya berjalan untuk tujuan notifikasi dan pencatatan log.

TeammateIdle

Berjalan ketika rekan tim tim agent akan menjadi idle setelah menyelesaikan gilirannya. Gunakan ini untuk menegakkan gerbang kualitas sebelum rekan tim berhenti bekerja, seperti mewajibkan lolos pemeriksaan lint atau memverifikasi bahwa file output ada.

Hook TeammateIdle tidak mendukung matcher dan dipicu pada setiap kejadian.

Input TeammateIdle

Selain field input umum, hook TeammateIdle menerima teammate_name dan team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TeammateIdle",
  "teammate_name": "researcher",
  "team_name": "session-a1b2c3d4"
}
Field Deskripsi
teammate_name Nama rekan tim yang akan menjadi idle
team_name Deprecated. Nama tim yang diturunkan dari sesi; akan dihapus dalam rilis mendatang

Kontrol keputusan TeammateIdle

Hook TeammateIdle mendukung dua cara untuk mengontrol perilaku rekan tim:

  • Exit code 2: rekan tim menerima pesan stderr sebagai umpan balik dan terus bekerja alih-alih menjadi idle.
  • JSON {"continue": false, "stopReason": "..."}: menghentikan rekan tim sepenuhnya, sesuai dengan perilaku hook Stop. stopReason ditampilkan kepada pengguna.

Contoh ini memeriksa bahwa artefak build ada sebelum mengizinkan rekan tim menjadi idle:

#!/bin/bash

if [ ! -f "./dist/output.js" ]; then
  echo "Build artifact missing. Run the build before stopping." >&2
  exit 2
fi

exit 0

ConfigChange

Berjalan ketika file konfigurasi berubah selama sesi. Gunakan ini untuk mengaudit perubahan pengaturan, menegakkan kebijakan keamanan, atau memblokir modifikasi yang tidak sah pada file konfigurasi.

Claude Code menjalankan hook ConfigChange ketika file pengaturan, file kebijakan terkelola, atau file skill berubah. Untuk kebijakan terkelola, Claude Code menjalankannya hanya ketika managed-settings.json atau file di managed-settings.d/ berubah. Claude Code menerapkan pengaturan yang dikelola server dan perubahan pada preferensi terkelola macOS atau kebijakan registri Windows tanpa menjalankannya. Di WSL dengan wslInheritsWindowsSettings, Claude Code juga menerapkan file pengaturan terkelola sisi Windows yang berubah pada polling kebijakannya tanpa menjalankannya.

Matcher memfilter berdasarkan sumber konfigurasi:

Matcher Kapan dipicu
user_settings ~/.claude/settings.json berubah
project_settings .claude/settings.json berubah
local_settings .claude/settings.local.json berubah
policy_settings managed-settings.json atau file di managed-settings.d/ berubah
skills File skill di .claude/skills/ berubah

Contoh ini mencatat semua perubahan konfigurasi untuk audit keamanan:

{
  "hooks": {
    "ConfigChange": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/audit-config-change.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

Input ConfigChange

Selain field input umum, hook ConfigChange menerima source dan secara opsional file_path. Field source menunjukkan jenis konfigurasi mana yang berubah, dan file_path menyediakan path ke file spesifik yang dimodifikasi.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "ConfigChange",
  "source": "project_settings",
  "file_path": "/Users/.../my-project/.claude/settings.json"
}

Kontrol keputusan ConfigChange

Hook ConfigChange dapat memblokir perubahan konfigurasi agar tidak berlaku. Gunakan exit code 2 atau decision JSON untuk mencegah perubahan. Ketika diblokir, pengaturan baru tidak diterapkan ke sesi yang sedang berjalan.

Field Deskripsi
decision "block" mencegah perubahan konfigurasi diterapkan. Hilangkan untuk mengizinkan perubahan
reason Diterima tetapi tidak pernah ditampilkan
{
  "decision": "block",
  "reason": "Configuration changes to project settings require admin approval"
}

Perubahan policy_settings tidak dapat diblokir. Hook tetap dipicu untuk sumber policy_settings ketika file pengaturan terkelola di mesin berubah, sehingga Anda dapat menggunakannya untuk mencatat pengeditan tersebut, tetapi keputusan pemblokiran apa pun akan diabaikan. Ini memastikan pengaturan yang dikelola perusahaan selalu berlaku. Claude Code tidak menjalankan hook ConfigChange ketika pengaturan yang dikelola server tiba atau diperbarui.

Claude Code menindaklanjuti keputusan pemblokiran dari output JSON hook ConfigChange dan membuang systemMessage dan continue. Perubahan yang diblokir tidak memunculkan pesan apa pun kepada Anda atau kepada Claude, baik Anda memblokir dengan reason maupun dengan stderr pada exit 2. Claude Code hanya menulis satu baris ke log debug.

CwdChanged

Berjalan ketika perintah shell dalam percakapan utama mengubah direktori kerja, misalnya ketika Claude mengeksekusi perintah cd. Gunakan ini untuk bereaksi terhadap perubahan direktori: memuat ulang environment variable, mengaktifkan toolchain khusus proyek, atau menjalankan skrip penyiapan secara otomatis. Berpasangan dengan FileChanged untuk tool seperti direnv yang mengelola environment per direktori.

Hook CwdChanged memiliki akses ke CLAUDE_ENV_FILE. Variabel yang ditulis ke file tersebut bertahan ke perintah Bash berikutnya hingga event CwdChanged berikutnya, ketika Claude Code menghapusnya.

CwdChanged tidak mendukung matcher dan dipicu pada setiap kejadian.

Input CwdChanged

Selain field input umum, hook CwdChanged menerima old_cwd dan new_cwd.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project/src",
  "hook_event_name": "CwdChanged",
  "old_cwd": "/Users/my-project",
  "new_cwd": "/Users/my-project/src"
}

Output CwdChanged

Selain field output JSON yang tersedia untuk semua hook, hook CwdChanged dapat mengembalikan watchPaths untuk secara dinamis mengatur path file mana yang dipantau oleh FileChanged:

Field Deskripsi
watchPaths Array path absolut. Menggantikan daftar pantauan dinamis saat ini. Path dari konfigurasi matcher Anda selalu dipantau. Mengembalikan array kosong akan mengosongkan daftar dinamis, yang umum dilakukan saat memasuki direktori baru

Hook CwdChanged tidak memiliki kontrol keputusan. Hook ini tidak dapat memblokir perubahan direktori.

Claude Code membaca watchPaths dan systemMessage dari output JSON-nya dan membuang continue. Dalam sesi interaktif, Claude Code menampilkan systemMessage sebagai notifikasi terminal singkat. Pesan tersebut tidak mencapai aliran pesan SDK.

DirectoryAdded

Berjalan setelah Anda menambahkan direktori kerja di tengah sesi dengan perintah /add-dir, atau setelah klien SDK menambahkannya dengan permintaan kontrol register_repo_root. Gunakan ini untuk menyiapkan repositori yang baru ditambahkan, misalnya dengan menginstal dependensinya.

Claude Code tidak memicu event ini ketika:

  • Anda meneruskan direktori dengan flag startup --add-dir; SessionStart mencakup direktori tersebut
  • Anda menambahkan direktori pada tab Workspace di /permissions
  • Anda menambahkan direktori yang sudah merupakan direktori kerja atau berada di dalamnya

Claude Code memicu DirectoryAdded setelah menyegarkan status sandbox dan izin, sehingga tool yang di-sandbox sudah melihat direktori baru ketika hook Anda berjalan. Perintah hook itu sendiri berjalan tanpa sandbox.

Claude Code tidak menunggu hook: penambahan selesai segera, dan hook berjalan di latar belakang dengan timeout default 600 detik.

Matcher memfilter berdasarkan cara direktori ditambahkan:

Matcher Kapan dipicu
slash_command Anda menambahkan direktori dengan /add-dir
register_repo_root Klien SDK menambahkan direktori dengan permintaan kontrol register_repo_root

Input DirectoryAdded

Selain field input umum, hook DirectoryAdded menerima directory dan source.

Field Deskripsi
directory Path absolut dari direktori yang ditambahkan
source Cara direktori ditambahkan, "slash_command" untuk /add-dir atau "register_repo_root" untuk permintaan kontrol SDK
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "DirectoryAdded",
  "directory": "/Users/my-other-repo",
  "source": "slash_command"
}

Hook DirectoryAdded tidak memiliki kontrol keputusan. Hook ini tidak dapat memblokir penambahan, yang sudah selesai saat hook berjalan. Claude Code membuang field continue dari output JSON-nya dan memunculkan sisanya secara berbeda per sumber:

  • slash_command: Claude Code mengirimkan systemMessage hook kepada Claude sebagai konteks pada giliran percakapan berikutnya, alih-alih menampilkannya kepada Anda. Jumlah hook yang gagal muncul di transkrip. Output kegagalan lengkap masuk ke log debug
  • register_repo_root: Claude Code menulis output systemMessage dan output kegagalan hanya ke log debug

FileChanged

Berjalan ketika file yang dipantau berubah di disk. Claude Code mendeteksi perubahan dengan pemantau filesystem, bukan dengan memeriksa panggilan tool, sehingga ia menjalankan hook apa pun yang mengubah file tersebut: panggilan tool Edit atau Write, skrip yang dijalankan Claude dengan Bash, atau proses yang sepenuhnya berada di luar Claude Code. Penggunaan umum adalah memuat ulang environment variable ketika file konfigurasi proyek berubah.

matcher untuk event ini memiliki dua peran:

  • Membangun daftar pantauan: nilainya dipisah berdasarkan | dan setiap segmen didaftarkan sebagai nama file literal di direktori kerja, sehingga ".envrc|.env" memantau tepat kedua file tersebut. Pola regex tidak berguna di sini: nilai seperti ^\.env akan memantau file yang secara literal bernama ^\.env.
  • Memfilter hook mana yang berjalan: ketika file yang dipantau berubah, nilai yang sama memfilter grup hook mana yang berjalan menggunakan aturan matcher standar terhadap basename file yang berubah.

Contoh ini menormalkan akhir baris di data.csv setelah perubahan apa pun, termasuk perintah Bash atau skrip eksternal yang menulis ulang file:

{
  "hooks": {
    "FileChanged": [
      {
        "matcher": "data.csv",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/normalize-line-endings.sh"
          }
        ]
      }
    ]
  }
}

Hook membaca path absolut file yang berubah dari field file_path pada input JSON di stdin. Penjaga grep-nya menguji hal yang sama yang dihapus oleh perl, yaitu CR di akhir baris, sehingga eksekusi setelah normalisasi keluar tanpa menyentuh file. Penjaga yang lebih longgar akan berulang tanpa henti, karena perl -i menulis ulang file bahkan ketika tidak mengganti apa pun dan Claude Code menjalankan hook lagi setelah setiap penulisan ulang. Simpan skrip ini di /path/to/normalize-line-endings.sh dan jadikan dapat dieksekusi:

#!/bin/bash
FILE=$(jq -r .file_path)
if grep -q $'\r$' "$FILE"; then
  perl -pi -e 's/\r$//' "$FILE"
fi

Untuk memastikan hook berfungsi, minta Claude menambahkan baris CRLF ke data.csv dengan perintah Bash. Claude Code menjalankan hook dan file akhirnya memiliki akhir baris LF.

Untuk memantau file yang tidak dapat Anda sebutkan di awal, kembalikan watchPaths dari hook untuk memperbarui daftar pantauan secara dinamis. Claude Code memulai pemantau hanya ketika sesuatu menyebutkan file untuk dipantau, jadi isi awal daftar dengan grup FileChanged yang matcher-nya menyebutkan setidaknya satu file, atau dengan hook SessionStart atau CwdChanged yang mengembalikan watchPaths. Matcher tetap memfilter grup hook mana yang berjalan ketika file yang dipantau berubah, jadi berikan grup yang menangani path dinamis matcher yang dihilangkan, yang cocok dengan setiap file yang dipantau dan tidak menambahkan apa pun ke daftar pantauan. Matcher "*" juga cocok dengan setiap file, tetapi Claude Code mendaftarkannya dalam daftar pantauan seperti nilai lainnya, sebagai file literal bernama *.

Hook FileChanged memiliki akses ke CLAUDE_ENV_FILE. Variabel yang ditulis ke file tersebut bertahan ke perintah Bash berikutnya hingga event CwdChanged berikutnya, ketika Claude Code menghapusnya.

Input FileChanged

Selain field input umum, hook FileChanged menerima file_path dan event.

Field Deskripsi
file_path Path absolut ke file yang berubah
event Apa yang terjadi: "change" untuk file yang dimodifikasi, "add" untuk file yang dibuat, atau "unlink" untuk file yang dihapus
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "FileChanged",
  "file_path": "/Users/my-project/.envrc",
  "event": "change"
}

Output FileChanged

Selain field output JSON yang tersedia untuk semua hook, hook FileChanged dapat mengembalikan watchPaths untuk secara dinamis memperbarui path file mana yang dipantau:

Field Deskripsi
watchPaths Array path absolut. Menggantikan daftar pantauan dinamis saat ini. Path dari konfigurasi matcher Anda selalu dipantau. Gunakan ini ketika skrip hook Anda menemukan file tambahan untuk dipantau berdasarkan file yang berubah

Hook FileChanged tidak memiliki kontrol keputusan. Hook ini tidak dapat memblokir terjadinya perubahan file.

Claude Code membaca watchPaths dan systemMessage dari output JSON-nya dan membuang continue. Dalam sesi interaktif, Claude Code menampilkan systemMessage sebagai notifikasi terminal singkat. Pesan tersebut tidak mencapai aliran pesan SDK.

WorktreeCreate

Berjalan ketika worktree sedang dibuat, baik dari claude --worktree, dari subagent yang menggunakan isolation: "worktree", atau untuk sesi latar belakang yang diisolasi Claude Code dalam worktree-nya sendiri. Secara default, Claude Code membuat salinan kerja terisolasi dengan git worktree. Mengonfigurasi hook WorktreeCreate akan menggantikan perilaku git default tersebut, memungkinkan Anda menggunakan sistem kontrol versi yang berbeda seperti SVN, Perforce, atau Mercurial.

Karena hook sepenuhnya menggantikan perilaku default, .worktreeinclude tidak diproses. Jika Anda perlu menyalin file konfigurasi lokal seperti .env ke worktree baru, lakukan di dalam skrip hook Anda.

Hook harus mengembalikan path ke direktori worktree yang dibuat. Claude Code menggunakan path ini sebagai direktori kerja untuk sesi terisolasi. Lihat Output WorktreeCreate untuk cara setiap jenis hook mengembalikan path.

Claude Code menindaklanjuti keberhasilan hook dan path yang dikembalikan, serta membuang systemMessage dan continue.

Contoh ini membuat salinan kerja SVN dan mencetak path untuk digunakan Claude Code. Ganti URL repositori dengan milik Anda sendiri:

{
  "hooks": {
    "WorktreeCreate": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
          }
        ]
      }
    ]
  }
}

Hook membaca name worktree dari input JSON di stdin, melakukan checkout salinan baru ke direktori baru, dan mencetak path direktori. echo pada baris terakhir adalah apa yang dibaca Claude Code sebagai path worktree. Alihkan output lainnya ke stderr agar tidak mengganggu path.

Input WorktreeCreate

Selain field input umum, hook WorktreeCreate menerima field name. Ini adalah pengidentifikasi slug untuk worktree baru, baik yang ditentukan oleh pengguna maupun yang dibuat secara otomatis, misalnya bold-oak-a3f2.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "WorktreeCreate",
  "name": "feature-auth"
}

Output WorktreeCreate

Hook WorktreeCreate tidak menggunakan model keputusan allow/block standar. Sebaliknya, keberhasilan atau kegagalan hook menentukan hasilnya. Hook harus mengembalikan path ke direktori worktree yang dibuat:

  • Hook command (type: "command"): cetak path sebagai baris tidak kosong terakhir dari stdout. Claude Code menghapus kode escape ANSI sebelum membaca baris tersebut, sehingga banner startup shell yang dicetak sebelum echo Anda diabaikan. Alihkan output hook lainnya ke stderr.
  • Hook HTTP (type: "http"): kembalikan { "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } } di body respons.

Jika hook gagal atau tidak menghasilkan path, pembuatan worktree gagal dengan error.

Claude Code menyelesaikan path relatif terhadap direktori tempat hook dijalankan, sambil menyederhanakan segmen . atau .. di dalamnya. Jika path yang dihasilkan bukan direktori yang dapat dimasuki Claude Code, sesi mencetak error yang menyebutkan path tersebut dan keluar dengan kode 1.

Claude Code menolak path absolut yang berisi segmen . atau .., serta path apa pun yang melewati symlink di bawah root repositori, karena symlink yang di-commit ke repositori dapat mengalihkan worktree ke luar repositori. Error tersebut menyebutkan komponen yang ditolak. Kembalikan path yang dinormalisasi yang tidak melewati symlink di dalam repositori. Sebelum v2.1.216, pembuatan worktree mengikuti path dari hook tanpa penyaringan ini.

WorktreeRemove

Berjalan saat worktree sedang dihapus. Ini adalah pasangan pembersihan dari WorktreeCreate. Event ini dipicu saat:

  • Anda keluar dari sesi --worktree dan memilih untuk menghapusnya
  • subagent dengan isolation: "worktree" selesai
  • Anda menghapus sesi latar belakang yang worktree-nya dibuat oleh hook

Untuk worktree berbasis git, Claude Code menangani pembersihan secara otomatis dengan git worktree remove. Jika Anda mengonfigurasi hook WorktreeCreate, pasangkan dengan hook WorktreeRemove untuk mengontrol pembersihan worktree yang dibuatnya:

  • Tanpa hook WorktreeRemove: saat Anda keluar dari sesi --worktree dan memilih penghapusan, Claude Code melakukan fallback ke git worktree remove --force pada path yang dikembalikan hook WorktreeCreate Anda, sehingga worktree yang dikenali git akan dihapus. Worktree yang tidak dikenali git, misalnya yang dibuat hook Anda dengan sistem kontrol versi non-git, tetap ada di disk. Untuk apa yang dilakukan penghapusan sesi latar belakang terhadap worktree yang dibuat hook, lihat aturan penghapusan di agent view.
  • Hook keluar dengan 0: worktree dianggap telah dihapus. Claude Code tidak membaca apa pun lagi dari hook, jadi pastikan hook Anda telah menghapus direktori tersebut.
  • Hook keluar dengan non-zero: penghapusan gagal jika direktori di worktree_path masih ada setelahnya, dan worktree tetap ada di disk tanpa fallback git. Hook yang menghapus direktori sebelum keluar dengan non-zero dianggap telah dihapus. Untuk cara kegagalan dilaporkan, lihat Input WorktreeRemove.

Claude Code tidak pernah menghapus branch milik worktree yang dibuat hook, karena Claude Code hanya mengetahui path yang dikembalikan hook WorktreeCreate Anda. Jika hook WorktreeCreate Anda membuat branch, hapus branch tersebut di hook WorktreeRemove Anda.

Claude Code membuang field output JSON dari hook WorktreeRemove, seperti systemMessage dan continue.

Untuk penghapusan sesi latar belakang, Claude Code memverifikasi path worktree yang tersimpan sebelum menjalankan hook dan menolak path yang merupakan symlink atau melewati symlink di bawah root repositori. Hook berjalan untuk worktree yang masih berisi file hanya ketika Anda mengonfirmasi penghapusan di agent view; untuk worktree seperti itu, claude rm justru mempertahankan sesi dan worktree. Sebelum v2.1.216, hook berjalan pada path yang tersimpan tanpa pemeriksaan ini.

Claude Code meneruskan path yang dikembalikan oleh WorktreeCreate sebagai worktree_path di input hook. Contoh ini membaca path tersebut dan menghapus direktorinya:

{
  "hooks": {
    "WorktreeRemove": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
          }
        ]
      }
    ]
  }
}

Input WorktreeRemove

Selain field input umum, hook WorktreeRemove menerima field worktree_path, yaitu path absolut ke worktree yang sedang dihapus.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "WorktreeRemove",
  "worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
}

Exit code hook WorktreeRemove menentukan hasilnya. Ketika hook keluar dengan non-zero dan direktori di worktree_path masih ada setelahnya, penghapusan gagal:

  • Worktree tetap ada di disk, dan perintah serta stderr hook masuk ke log debug.
  • Jika Anda sedang menghapus sesi latar belakang, sesi tersebut juga tetap ada. Pesan penolakan di agent view melaporkan bagaimana hook berakhir, seperti exited 1, mengutip bagian awal stderr-nya, dan menyebutkan apakah menghapus sesi lagi akan tetap menghapus direktori tersebut.

PreCompact

Berjalan sebelum Claude Code akan menjalankan operasi compact.

Nilai matcher menunjukkan apakah compaction dipicu secara manual atau otomatis:

Matcher Kapan dipicu
manual /compact
auto Auto-compact saat percakapan mencapai jendela auto-compact

Keluar dengan kode 2 untuk memblokir compaction. Untuk /compact manual, pesan stderr ditampilkan kepada pengguna. Anda juga dapat memblokir dengan mengembalikan JSON dengan "decision": "block".

Memblokir compaction otomatis memiliki efek berbeda tergantung kapan dipicu. Jika compaction dipicu secara proaktif sebelum batas konteks, Claude Code melewatinya dan percakapan berlanjut tanpa dipadatkan. Jika compaction dipicu untuk memulihkan dari error batas konteks yang sudah dikembalikan oleh API, error yang mendasarinya muncul dan permintaan saat ini gagal.

Claude Code membuang field systemMessage dan continue dari hook PreCompact.

Input PreCompact

Selain field input umum, hook PreCompact menerima trigger dan custom_instructions. Untuk manual, custom_instructions berisi apa yang diteruskan pengguna ke /compact dan bernilai null jika pengguna tidak meneruskan apa pun. Untuk auto, custom_instructions bernilai null.

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

PostCompact

Berjalan setelah Claude Code menyelesaikan operasi compact. Gunakan event ini untuk bereaksi terhadap status baru yang telah dipadatkan, misalnya untuk mencatat ringkasan yang dihasilkan atau memperbarui status eksternal. Claude Code membuang field systemMessage dan continue dari hook PostCompact.

Nilai matcher yang sama berlaku seperti untuk PreCompact:

Matcher Kapan dipicu
manual Setelah /compact
auto Setelah auto-compact saat percakapan mencapai jendela auto-compact

Input PostCompact

Selain field input umum, hook PostCompact menerima trigger dan compact_summary. Field compact_summary berisi ringkasan percakapan yang dihasilkan oleh operasi compact.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PostCompact",
  "trigger": "manual",
  "compact_summary": "Summary of the compacted conversation..."
}

Hook PostCompact tidak memiliki kontrol keputusan. Hook ini tidak dapat memengaruhi hasil compaction tetapi dapat melakukan tugas lanjutan.

PreModelSwitch

Berjalan sebelum Claude Code menerapkan peralihan model yang diminta oleh Anda atau klien. Gunakan untuk memblokir peralihan, meminta konfirmasi, atau menampilkan biaya peralihan sebelum terjadi.

PreModelSwitch memerlukan Claude Code v2.1.251 atau lebih baru. Claude Code menjalankannya untuk permintaan berikut:

  • /model <name> dan pemilih /model
  • Pemilih model Option+P atau Alt+P
  • Pengaturan Model di /config
  • Mengaktifkan fast mode ketika hal itu mengubah model sesi
  • Permintaan set_model, atau perubahan model dalam permintaan apply_flag_settings, dari host Agent SDK atau Remote Control

Claude Code tidak menjalankan hook PreModelSwitch untuk peralihan yang dilakukannya sendiri, seperti fallback model otomatis atau memulihkan model saat Anda melanjutkan sesi. Perubahan tersebut hanya mencapai PostModelSwitch.

Claude Code membandingkan matcher dengan nama kanonis model tujuan peralihan sesi, dengan mengabaikan sufiks [1m]. Alias seperti opus, ID model bertanggal, dan ID khusus penyedia seperti ID model Amazon Bedrock semuanya cocok dengan satu nama kanonis yang menjadi hasil resolusinya, sehingga claude-opus-5 mencakup setiap penulisan Opus 5.

Ketika Claude Code tidak dapat menentukan nama kanonis untuk target, misalnya ID model kustom yang hanya dikenal oleh LLM gateway Anda, Claude Code menjalankan setiap hook PreModelSwitch terlepas dari matcher. Oleh karena itu, hook yang memblokir sebaiknya memeriksa to_model dari inputnya alih-alih hanya mengandalkan matcher.

Tulis matcher sebagai nama persis, daftar yang dipisahkan | seperti claude-opus-4-6|claude-opus-5, atau regular expression seperti .*opus.*. Contoh ini menggunakan matcher nama persis dan juga memeriksa to_model dari input hook, sehingga menolak peralihan ke Opus 4.6 dengan keluar menggunakan kode 2 dan membiarkan target lain lolos:

Perintah ini memeriksa to_model dengan jq:

{
"hooks": {
"PreModelSwitch": [
{
"matcher": "claude-opus-4-6",
"hooks": [
{
"type": "command",
"command": "jq -e '.to_model | test(\"opus-4-6\")' > /dev/null && { echo 'Opus 4.6 is retired for this project. Use a newer model.' >&2; exit 2; }; exit 0"
}
]
}
]
}
}

Untuk memastikan hook berfungsi, jalankan /model claude-opus-4-6 dari sesi yang menjalankan model lain. Claude Code mempertahankan model saat ini dan melaporkan bahwa hook PreModelSwitch memblokir peralihan, dengan pesan Anda sebagai alasannya.

Input PreModelSwitch

Selain field input umum, hook PreModelSwitch menerima field dalam tabel ini. Lima field terakhir menjelaskan biaya pengiriman ulang percakapan ke model baru, sehingga hook dapat menampilkan angka tersebut sebelum peralihan terjadi.

Field Tipe Deskripsi
from_model string ID model asal peralihan
to_model string ID model tujuan peralihan. Matcher dibandingkan dengan nama kanonis model ini
requested_model string atau null Model yang disebutkan dalam permintaan: alias seperti opus, ID model lengkap, atau null jika permintaan ditujukan untuk model default
source string Asal permintaan: "command" untuk /model <name>, pengaturan Model di /config, atau mengaktifkan fast mode; "picker" untuk pemilih model; "sdk" untuk permintaan set_model, atau perubahan model dalam permintaan apply_flag_settings, dari host Agent SDK atau Remote Control
context_tokens number Token yang dikirim ulang oleh permintaan berikutnya sebagai prompt-nya: token input, cache read, cache creation, dan output dari respons terakhir dalam percakapan utama, digabungkan. 0 sebelum respons pertama
prompt_cache_warm boolean Apakah prompt cache model saat ini kemungkinan masih hangat, yang berarti peralihan akan mengorbankannya
cache_ttl string Masa berlaku prompt cache yang diminta Claude Code untuk sesi ini: "5m" atau "1h"
estimated_cache_write_usd number Estimasi biaya dalam dolar AS untuk menulis context_tokens ke prompt cache pada to_model dengan tarif cache_ttl, tidak termasuk respons berikutnya. Server mungkin tidak perlu melakukan cache ulang seluruh konteks, jadi perlakukan ini sebagai estimasi
pricing string Cara Claude Code menghitung harga estimated_cache_write_usd: "configured" dengan tarif organisasi Anda sendiri jika organisasi telah mengonfigurasinya, "catalog" dengan harga daftar, atau "default" jika to_model tidak memiliki harga yang diketahui dan Claude Code mengasumsikan tarif default

Contoh ini menunjukkan input untuk /model opus dalam sesi yang menjalankan Sonnet 5:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreModelSwitch",
  "from_model": "claude-sonnet-5",
  "to_model": "claude-opus-5",
  "requested_model": "opus",
  "source": "command",
  "context_tokens": 182340,
  "prompt_cache_warm": true,
  "cache_ttl": "5m",
  "estimated_cache_write_usd": 1.1396,
  "pricing": "catalog"
}

Kontrol keputusan PreModelSwitch

Hook PreModelSwitch dapat membatalkan peralihan, meminta pengguna untuk mengonfirmasinya, atau membiarkannya berlanjut. Exit code 2 atau decision: "block" tingkat atas membatalkan peralihan.

Untuk kontrol yang lebih rinci, kembalikan permissionDecision dan permissionDecisionReason dalam objek hookSpecificOutput, seperti pada PreToolUse. PreModelSwitch menerima "allow", "deny", dan "ask". Hook ini tidak menerima "defer", updatedInput, atau additionalContext. Tabel di bawah menjelaskan kedua field:

Field Deskripsi
permissionDecision "allow" melanjutkan dan melewati konfirmasi yang ditampilkan Claude Code saat prompt cache masih hangat. "deny" membatalkan peralihan. "ask" meminta pengguna untuk mengonfirmasinya
permissionDecisionReason Untuk "deny", ditampilkan kepada pengguna sebagai alasan peralihan diblokir, atau dikembalikan sebagai error untuk permintaan set_model. Untuk "ask", ditampilkan dalam dialog konfirmasi. Diabaikan untuk "allow"

Hanya /model dalam sesi interaktif yang dapat menampilkan dialog "ask". Di setiap surface lainnya, termasuk mode non-interaktif dengan flag -p, /config, dan permintaan set_model, Claude Code memperlakukan "ask" sebagai penolakan.

Contoh ini meminta pengguna untuk mengonfirmasi dan mengutip jumlah token dari context_tokens:

{
  "hookSpecificOutput": {
    "hookEventName": "PreModelSwitch",
    "permissionDecision": "ask",
    "permissionDecisionReason": "Switching now re-sends about 180k tokens to the new model. Continue?"
  }
}

Ketika beberapa hook PreModelSwitch mengembalikan keputusan yang berbeda, urutan prioritasnya adalah deny > ask > allow.

Claude Code menampilkan kepada pengguna setiap systemMessage yang dikembalikan hook Anda terlepas dari keputusannya, sehingga hook pelaporan biaya dapat mengembalikan {"systemMessage": "..."} dan keluar dengan 0.

Hook PreModelSwitch yang tidak merespons sebelum timeout-nya akan memblokir peralihan. Sebaliknya, pada PreToolUse, hook command yang mengalami timeout membiarkan panggilan tool berlanjut. Timeout default untuk event ini adalah 30 detik. PreModelSwitch hanya menjalankan hook command, http, dan mcp_tool, sehingga default prompt dan agent tidak berlaku.

Hook yang keluar dengan kode selain 0 atau 2 dan tidak mencetak keputusan JSON tidak memblokir: Claude Code menampilkan stderr-nya dan menerapkan peralihan, seperti dijelaskan di Exit code lainnya.

PostModelSwitch

Berjalan setelah model sesi berubah. Gunakan untuk memberi Claude panduan khusus model tanpa mengedit setiap CLAUDE.md, misalnya instruksi tingkat organisasi yang berlaku pada model tertentu.

PostModelSwitch memerlukan Claude Code v2.1.251 atau lebih baru. Hook ini tidak dapat memblokir, karena model sudah berubah. Claude Code menjalankan hook PostModelSwitch setelah salah satu perubahan berikut:

  • Peralihan yang diminta oleh Anda atau klien
  • Fallback model otomatis, yang mengubah model sesi
  • Pengaturan seperti opusplan yang masuk atau keluar dari plan mode
  • Claude Code memulihkan model saat Anda melanjutkan sesi

Claude Code tidak menjalankan hook PostModelSwitch ketika model dari rantai model fallback melayani sebuah giliran, karena substitusi tersebut hanya berlangsung satu giliran dan tidak mengubah model sesi.

Matcher mengikuti aturan yang sama seperti PreModelSwitch: Claude Code membandingkannya dengan nama kanonis model tujuan peralihan sesi.

Contoh ini menambahkan panduan setiap kali model sesi berubah ke model Opus mana pun:

{
  "hooks": {
    "PostModelSwitch": [
      {
        "matcher": ".*opus.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'On Opus, delegate implementation work to subagents and keep this conversation for planning and review.'"
          }
        ]
      }
    ]
  }
}

Untuk memastikan hook berfungsi, beralihlah ke model Opus dari sesi yang menjalankan model lain, misalnya jalankan /model opus dari sesi Sonnet, lalu tanyakan kepada Claude panduan apa yang dimilikinya tentang model saat ini.

Input PostModelSwitch

Hook PostModelSwitch menerima field yang sama seperti PreModelSwitch, dengan hook_event_name diatur ke "PostModelSwitch" dan dua nilai source tambahan: "auto" untuk fallback otomatis atau perubahan lain yang dilakukan Claude Code sendiri, dan "resume" untuk model yang dipulihkan saat Anda melanjutkan sesi.

requested_model bernilai null ketika source adalah "auto". Ketika source adalah "resume", nilainya adalah pengaturan model tersimpan yang dipulihkan Claude Code.

Kontrol keputusan PostModelSwitch

Claude Code mengambil stdout teks biasa hook Anda saat keluar dengan 0, atau additionalContext dari output JSON, dan mengirimkannya ke Claude bersama permintaan berikutnya setelah peralihan. Selain field output JSON yang tersedia untuk semua hook, Anda dapat mengembalikan:

Field Deskripsi
additionalContext String yang ditambahkan ke konteks Claude bersama permintaan berikutnya. Lihat Menambahkan konteks untuk Claude

Jika hook belum selesai dalam lima detik setelah Anda mengirim prompt berikutnya, Claude Code mengirim permintaan tersebut tanpa output dan melampirkannya ke permintaan setelahnya. Jika model berubah beberapa kali sebelum permintaan berikutnya, Claude Code hanya mengirimkan output untuk model target dari peralihan terakhir.

SessionEnd

Berjalan saat sesi Claude Code berakhir. Berguna untuk tugas pembersihan, mencatat statistik sesi, atau menyimpan status sesi. Mendukung matcher untuk memfilter berdasarkan alasan keluar.

Field reason dalam input hook menunjukkan mengapa sesi berakhir:

Alasan Deskripsi
clear Sesi dibersihkan dengan perintah /clear
resume Sesi dialihkan melalui /resume interaktif
logout Pengguna keluar (log out)
prompt_input_exit Pengguna keluar saat input prompt terlihat
other Alasan keluar lainnya
bypass_permissions_disabled Dihapus di v2.1.234; Claude Code tidak mengirimkannya. Hapus dari matcher SessionEnd Anda

Input SessionEnd

Selain field input umum, hook SessionEnd menerima field reason yang menunjukkan mengapa sesi berakhir. Lihat tabel alasan di atas untuk semua nilai.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionEnd",
  "reason": "other"
}

Hook SessionEnd tidak memiliki kontrol keputusan. Hook ini tidak dapat memblokir penghentian sesi tetapi dapat melakukan tugas pembersihan. Claude Code membuang field output JSON hook ini, seperti systemMessage.

Hook SessionEnd memiliki timeout default 1,5 detik. Timeout ini berlaku saat Anda keluar, menjalankan /clear, atau beralih sesi dengan /resume interaktif. Anda dapat memberi hook lebih banyak waktu dengan dua cara:

  • timeout per hook: atur timeout di konfigurasi hook tersebut. Anggaran keseluruhan naik secara otomatis untuk menyesuaikan dengan timeout per hook tertinggi di file pengaturan Anda, hingga 60 detik. Jika Anda menaikkan anggaran dengan cara ini, hook tanpa timeout sendiri tetap menggunakan default. Timeout yang diatur pada hook yang disediakan plugin tidak menaikkan anggaran.
  • CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS: atur environment variable ini dalam milidetik untuk menimpa anggaran secara eksplisit. Nilai yang Anda atur juga menjadi timeout untuk setiap hook tanpa timeout sendiri.

Contoh ini mengatur anggaran menjadi 5 detik:

CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude

Sebelum v2.1.268, CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS hanya menaikkan anggaran keseluruhan, dan hook tanpa timeout sendiri tetap dibatalkan setelah 1,5 detik.

Elicitation

Berjalan saat server MCP meminta input pengguna di tengah tugas. Secara default, Claude Code menampilkan dialog interaktif agar pengguna merespons. Hook dapat mencegat permintaan ini dan merespons secara terprogram, melewati dialog sepenuhnya.

Field matcher dicocokkan dengan nama server MCP.

Input Elicitation

Selain field input umum, hook Elicitation menerima field mcp_server_name, message, serta field opsional mode, url, elicitation_id, dan requested_schema.

Untuk elicitation mode formulir, kasus yang paling umum:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Elicitation",
  "mcp_server_name": "my-mcp-server",
  "message": "Please provide your credentials",
  "mode": "form",
  "requested_schema": {
    "type": "object",
    "properties": {
      "username": { "type": "string", "title": "Username" }
    }
  }
}

Untuk elicitation mode URL, yang digunakan untuk autentikasi berbasis browser:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Elicitation",
  "mcp_server_name": "my-mcp-server",
  "message": "Please authenticate",
  "mode": "url",
  "url": "https://auth.example.com/login"
}

Output Elicitation

Untuk merespons secara terprogram tanpa menampilkan dialog, kembalikan objek JSON dengan hookSpecificOutput:

{
  "hookSpecificOutput": {
    "hookEventName": "Elicitation",
    "action": "accept",
    "content": {
      "username": "alice"
    }
  }
}
Field Nilai Deskripsi
action accept, decline, cancel Apakah akan menerima, menolak, atau membatalkan permintaan
content object Nilai field formulir yang akan dikirim. Hanya digunakan ketika action adalah accept

Exit code 2 menolak elicitation. Claude Code tidak menampilkan pesan stderr Anda di mana pun.

Claude Code menindaklanjuti hookSpecificOutput dari output JSON hook Elicitation dan membuang systemMessage serta continue.

ElicitationResult

Berjalan setelah pengguna merespons elicitation MCP. Hook dapat mengamati, memodifikasi, atau memblokir respons sebelum dikirim kembali ke server MCP.

Field matcher dicocokkan dengan nama server MCP.

Input ElicitationResult

Selain field input umum, hook ElicitationResult menerima field mcp_server_name, action, serta field opsional mode, elicitation_id, dan content.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "ElicitationResult",
  "mcp_server_name": "my-mcp-server",
  "action": "accept",
  "content": { "username": "alice" },
  "mode": "form",
  "elicitation_id": "elicit-123"
}

Output ElicitationResult

Untuk menimpa respons pengguna, kembalikan objek JSON dengan hookSpecificOutput:

{
  "hookSpecificOutput": {
    "hookEventName": "ElicitationResult",
    "action": "decline",
    "content": {}
  }
}
Field Nilai Deskripsi
action accept, decline, cancel Menimpa tindakan pengguna
content object Menimpa nilai field formulir. Hanya bermakna ketika action adalah accept

Exit code 2 memblokir respons, mengubah tindakan efektif menjadi decline. Claude Code tidak menampilkan pesan stderr Anda di mana pun.

Claude Code menindaklanjuti hookSpecificOutput dari output JSON hook ElicitationResult dan membuang systemMessage serta continue.

Prompt-based hooks

Selain command, HTTP, dan MCP tool hooks, Claude Code mendukung prompt-based hooks (type: "prompt") yang menggunakan LLM untuk mengevaluasi apakah akan mengizinkan atau memblokir tindakan, dan agent hooks (type: "agent") yang spawn agentic verifier dengan akses tool. Tidak semua events mendukung setiap tipe hook.

Events yang mendukung semua lima tipe hook (command, http, mcp_tool, prompt, dan agent):

  • PermissionDenied
  • PostToolBatch
  • PostToolUse
  • PostToolUseFailure
  • PreToolUse
  • Stop
  • SubagentStop
  • TaskCompleted
  • TaskCreated
  • TeammateIdle
  • UserPromptExpansion
  • UserPromptSubmit

PermissionRequest mendukung hooks command, http, mcp_tool, dan prompt tetapi bukan hooks agent. Jika Anda mengonfigurasi agent hook pada event ini, Claude Code melewatinya dan aliran izin berlanjut tidak berubah. Untuk mengizinkan atau menolak dari hook, kembalikan objek keputusan dari command atau HTTP hook.

Events yang mendukung hooks command, http, dan mcp_tool tetapi bukan prompt atau agent:

  • ConfigChange
  • CwdChanged
  • DirectoryAdded
  • Elicitation
  • ElicitationResult
  • FileChanged
  • InstructionsLoaded
  • MessageDisplay
  • Notification
  • PostCompact
  • PostModelSwitch
  • PreCompact
  • PreModelSwitch
  • SessionEnd
  • StopFailure
  • SubagentStart
  • WorktreeCreate
  • WorktreeRemove

SessionStart dan Setup mendukung hooks command dan mcp_tool, dan MCP tool hook fields menjelaskan kapan hooks mcp_tool mereka berjalan. Mereka tidak mendukung hooks http, prompt, atau agent.

Bagaimana prompt-based hooks bekerja

Alih-alih menjalankan perintah Bash, prompt-based hooks:

  1. Mengirimkan input hook dan prompt Anda ke model Claude, secara default model yang digunakan Claude Code untuk fungsionalitas latar belakang
  2. LLM merespons dengan JSON terstruktur yang berisi keputusan
  3. Claude Code memproses keputusan secara otomatis

Konfigurasi prompt hook

Atur type ke "prompt" dan sediakan string prompt alih-alih command. Gunakan placeholder $ARGUMENTS untuk menyuntikkan data JSON input hook ke dalam teks prompt Anda.

Hook Stop ini meminta LLM untuk mengevaluasi apakah semua tugas selesai sebelum mengizinkan Claude selesai:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
          }
        ]
      }
    ]
  }
}
Bidang Diperlukan Deskripsi
type ya Harus "prompt"
prompt ya Teks prompt untuk dikirim ke LLM. Gunakan $ARGUMENTS sebagai placeholder untuk JSON input hook. Jika $ARGUMENTS tidak ada, JSON input ditambahkan ke prompt
model tidak Model untuk digunakan untuk evaluasi. Default ke model yang digunakan Claude Code untuk fungsionalitas latar belakang
timeout tidak Timeout dalam detik. Default: 30
continueOnBlock tidak Pada events yang berlaku, true mengirimkan alasan ok: false kembali ke Claude dan melanjutkan alih-alih mengakhiri giliran. Default: false. Lihat Response schema untuk perilaku per-event

Skema respons

LLM harus merespons dengan JSON yang berisi:

{
  "ok": true | false,
  "reason": "Explanation for the decision",
  "impossible": true | false
}
Bidang Deskripsi
ok true untuk mengizinkan. Untuk false, lihat perilaku per-event di bawah
reason Diperlukan saat ok adalah false
impossible Opsional. Model mengembalikannya dengan ok: false ketika menilai kondisi tidak dapat pernah terpenuhi. Pada Stop dan SubagentStop, Claude Code kemudian membiarkan giliran berakhir alih-alih mengirimkan alasan kembali. Agent hooks dan events lainnya mengabaikannya

Apa yang terjadi pada ok: false tergantung pada event:

  • Stop dan SubagentStop: alasan diumpankan kembali ke Claude sebagai instruksi berikutnya dan giliran berlanjut, kecuali respons juga menetapkan impossible: true, dalam hal ini Claude Code mengizinkan stop dan giliran berakhir
  • PreToolUse: panggilan tool ditolak; secara default giliran berakhir dan alasan penolakan muncul dalam chat sebagai baris peringatan. Atur continueOnBlock: true untuk mengembalikan alasan ke Claude sebagai kesalahan tool sehingga dapat menyesuaikan dan melanjutkan, setara dengan permissionDecision: "deny" dari command hook. Sebelum v2.1.210, alasan penolakan dikembalikan ke Claude sebagai kesalahan tool dan giliran berlanjut
  • PostToolUse: secara default giliran berakhir dan alasan muncul dalam chat sebagai baris peringatan. Atur continueOnBlock: true untuk mengirimkan alasan kembali ke Claude dan melanjutkan giliran alih-alih
  • PostToolBatch, UserPromptSubmit, dan UserPromptExpansion: giliran berakhir dan alasan muncul sebagai baris peringatan. Events ini mengakhiri giliran pada decision: "block" terlepas dari continue
  • PostToolUseFailure dan TaskCreated: alasan dikembalikan ke Claude sebagai kesalahan tool dan giliran berlanjut, terlepas dari continueOnBlock
  • TaskCompleted: ketika terjadi karena tugas ditandai selesai selama giliran, alasan dikembalikan ke Claude sebagai kesalahan tool dan giliran berlanjut, terlepas dari continueOnBlock. Ketika terjadi karena rekan kerja berhenti, berperilaku seperti TeammateIdle dan menghentikan rekan kerja secara default
  • TeammateIdle: secara default rekan kerja berhenti dan alasan muncul sebagai baris peringatan. Atur continueOnBlock: true untuk mengirimkan alasan kembali ke rekan kerja dan membiarkannya tetap bekerja alih-alih
  • PermissionRequest: ok: false tidak berpengaruh. Untuk menolak persetujuan dari hook, gunakan command hook yang mengembalikan hookSpecificOutput.decision.behavior: "deny"
  • PermissionDenied: ok: false tidak berpengaruh karena penolakan sudah terjadi. Satu-satunya output yang dibaca event ini adalah hookSpecificOutput.retry, yang prompt dan agent hooks tidak dapat atur. Mereka berjalan pada event ini, tetapi output mereka diabaikan. Gunakan command hook untuk mengembalikan retry

Jika Anda memerlukan kontrol yang lebih halus pada event apa pun, gunakan command hook dengan bidang per-event yang dijelaskan dalam Decision control.

Periksa beberapa kondisi sebelum berhenti

Hook Stop ini menggunakan prompt detail untuk memeriksa tiga kondisi sebelum mengizinkan Claude berhenti. Hooks SubagentStop menggunakan format yang sama untuk mengevaluasi apakah subagent harus berhenti. Jika model mengembalikan "ok": false karena kondisi belum terpenuhi, Claude terus bekerja dengan alasan yang disediakan sebagai instruksi berikutnya:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Agent-based hooks

Agent-based hooks (type: "agent") seperti prompt-based hooks tetapi dengan akses tool multi-turn. Alih-alih pemanggilan LLM tunggal, hook agent spawn subagent yang dapat membaca file, mencari kode, dan memeriksa codebase untuk memverifikasi kondisi. Agent hooks mendukung events yang sama seperti prompt-based hooks, kecuali PermissionRequest.

Bagaimana agent hooks bekerja

Ketika hook agent dijalankan:

  1. Claude Code spawn subagent dengan prompt Anda dan JSON input hook
  2. Subagent dapat menggunakan tools seperti Read, Grep, dan Glob untuk menyelidiki
  3. Setelah hingga 50 turn, subagent mengembalikan keputusan terstruktur { "ok": true/false }
  4. Claude Code memungkinkan tindakan jika ok adalah true. Jika ok adalah false, Claude Code menangani blokir dengan cara yang sama seperti prompt hook dengan continueOnBlock: true pada event tersebut, seperti yang tercantum di bawah Response schema

Agent hooks berguna ketika verifikasi memerlukan memeriksa file aktual atau output test, bukan hanya mengevaluasi data input hook saja.

Konfigurasi agent hook

Atur type ke "agent" dan sediakan string prompt, menggunakan $ARGUMENTS sebagai placeholder untuk JSON input hook. Bidang konfigurasi sama seperti prompt hooks, kecuali bahwa agent hooks memiliki timeout default yang lebih lama yaitu 60 detik dan tidak ada field continueOnBlock.

Skema respons adalah { "ok": true } untuk mengizinkan atau { "ok": false, "reason": "..." } untuk memblokir. Pada ok: false, Claude Code menangani agent hook dengan cara yang sama seperti menangani prompt hook dengan continueOnBlock: true pada event yang sama; agent hooks tidak memiliki field continueOnBlock, dan tidak mendukung field impossible dari prompt hook.

Hook Stop ini memverifikasi bahwa semua unit tests lulus sebelum mengizinkan Claude selesai:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

Jalankan hooks di latar belakang

Secara default, hooks memblokir eksekusi Claude sampai selesai. Untuk tugas yang berjalan lama seperti deployments, test suites, atau panggilan API eksternal, atur "async": true untuk menjalankan hook di latar belakang sementara Claude terus bekerja. Async hooks tidak dapat memblokir atau mengontrol perilaku Claude: bidang respons seperti decision, permissionDecision, dan continue tidak berpengaruh, karena tindakan yang akan mereka kontrol sudah selesai.

Konfigurasi async hook

Tambahkan "async": true ke konfigurasi command hook untuk menjalankannya di latar belakang tanpa memblokir Claude. Bidang ini hanya tersedia pada hooks type: "command".

Hook ini menjalankan skrip test setelah setiap pemanggilan tool Write. Claude terus bekerja segera sementara run-tests.sh dijalankan. Ketika skrip selesai, outputnya disampaikan pada turn percakapan berikutnya:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/run-tests.sh",
            "async": true
          }
        ]
      }
    ]
  }
}

Setelah async hook berjalan di latar belakang, Claude Code tidak memberlakukan timeout padanya. Claude Code masih memberlakukan timeout pada hook yang Anda jalankan dengan asyncRewake.

Claude Code mengirimkan hasil async hook hanya saat sesi berjalan:

  • Dalam mode non-interaktif dengan flag -p, Claude Code membunuh hook async apa pun yang masih berjalan saat teardown dan menyelesaikannya dengan outcome cancelled
  • Jika pekerjaan hook Anda harus bertahan lebih lama dari sesi claude -p, mulai proses yang sepenuhnya terpisah darinya

Bagaimana async hooks dijalankan

Ketika async hook dijalankan, Claude Code memulai proses hook dan segera melanjutkan tanpa menunggu selesai. Hook menerima JSON input yang sama melalui stdin seperti hook sinkron.

Setelah proses latar belakang keluar, Claude Code mengirimkan bidang additionalContext dan systemMessage dari respons JSON hook ke Claude pada turn percakapan berikutnya. Tidak seperti systemMessage hook sinkron, tidak ada bidang yang ditampilkan kepada Anda.

Claude Code memvalidasi respons JSON tersebut terhadap output schema yang sama seperti hooks sinkron, dan menghapus bidang apa pun yang nilainya memiliki tipe yang salah, seperti systemMessage yang bukan string, alih-alih menyampaikannya. Jalankan dengan --debug untuk melihat peringatan yang menyebutkan setiap bidang yang dihapus. Sebelum v2.1.202, output JSON yang tidak terbentuk dengan baik dari async hook dapat menghancurkan sesi, dan kerusakan terulang setiap kali sesi dilanjutkan.

Notifikasi penyelesaian async hook ditekan secara default. Untuk melihatnya, aktifkan mode verbose dengan Ctrl+O atau mulai Claude Code dengan --verbose.

Jalankan tests setelah perubahan file

Hook ini memulai test suite di latar belakang setiap kali Claude menulis file, kemudian melaporkan hasil kembali ke Claude ketika tests selesai. Simpan skrip ini ke .claude/hooks/run-tests-async.sh dalam proyek Anda dan buat dapat dijalankan dengan chmod +x:

#!/bin/bash
# run-tests-async.sh

# Baca hook input dari stdin
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Hanya jalankan tests untuk file sumber
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
  exit 0
fi

# Jalankan tests dan laporkan hasil ke Claude via additionalContext
RESULT=$(npm test 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
  MSG="Tests passed after editing $FILE_PATH"
else
  MSG="Tests failed after editing $FILE_PATH: $RESULT"
fi
jq -nc --arg msg "$MSG" '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $msg}}'

Kemudian tambahkan konfigurasi ini ke .claude/settings.json dalam akar proyek Anda. Flag async: true memungkinkan Claude terus bekerja sementara tests dijalankan:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",
            "args": [],
            "async": true
          }
        ]
      }
    ]
  }
}

Keterbatasan

Async hooks memiliki batasan tambahan dibandingkan dengan hooks sinkron:

  • Output hook disampaikan pada turn percakapan berikutnya. Jika sesi idle, respons menunggu sampai interaksi pengguna berikutnya. Pengecualian: hook asyncRewake yang keluar dengan kode 2 membangunkan Claude segera bahkan ketika sesi idle.
  • Setiap eksekusi membuat proses latar belakang terpisah. Tidak ada deduplikasi di seluruh beberapa penjalankan hook async yang sama.

Pertimbangan keamanan

Penafian

Kepercayaan workspace

Claude Code memeriksa kepercayaan workspace sebelum menjalankan hook apa pun dari file pengaturan. Apa yang dianggap terpercaya tergantung pada jenis sesi:

  • Sesi interaktif: Claude Code menahan hook dari setiap file pengaturan, termasuk ~/.claude/settings.json Anda sendiri, sampai Anda menerima dialog kepercayaan workspace untuk folder, atau untuk direktori induk yang kepercayaannya meluas ke dalamnya
  • Sesi -p atau SDK: Claude Code tidak pernah menampilkan dialog dan memperlakukan folder sebagai terpercaya, sehingga hook yang dikomit dalam .claude/settings.json repositori berjalan di folder yang belum pernah Anda percayai

Sebelum Anda menjalankan skrip claude -p di atas repositori yang tidak Anda tulis, tinjau file pengaturan .claude/ nya, mulai dengan --bare, atau matikan hooks untuk run itu dengan --settings '{"disableAllHooks": true}'. Hook frontmatter dalam subagent proyek mengikuti aturan yang lebih ketat daripada hook file pengaturan. Apa yang berjalan sebelum Anda mempercayai folder mencantumkan setiap jenis konten repositori berdasarkan jenis sesi.

Praktik terbaik keamanan

Ingat praktik-praktik ini saat menulis hooks:

  • Validasi dan sanitasi input: jangan pernah mempercayai data input secara membabi buta
  • Selalu kutip variabel shell: gunakan "$VAR" bukan $VAR
  • Blokir path traversal: periksa .. dalam path file
  • Gunakan path absolut: tentukan path lengkap untuk skrip. Dalam bentuk exec, gunakan ${CLAUDE_PROJECT_DIR} dan path tidak perlu dikutip. Dalam bentuk shell, bungkus dengan tanda kutip ganda
  • Lewati file sensitif: hindari .env, .git/, keys, dll.

Windows PowerShell tool

Di Windows, Anda dapat menjalankan hook individual dalam PowerShell dengan menetapkan "shell": "powershell" pada command hook. Claude Code auto-detects pwsh.exe, executable PowerShell 7 dan yang lebih baru, dan fallback ke powershell.exe untuk Windows PowerShell 5.1.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "shell": "powershell",
            "command": "Write-Host 'File written'"
          }
        ]
      }
    ]
  }
}

Untuk mereferensikan root proyek dari perintah bentuk shell PowerShell, tulis ${CLAUDE_PROJECT_DIR} atau $env:CLAUDE_PROJECT_DIR. Mulai dari v2.1.198, Claude Code menulis ulang placeholder ${CLAUDE_PROJECT_DIR}, ${CLAUDE_PLUGIN_ROOT}, dan ${CLAUDE_PLUGIN_DATA} dalam perintah bentuk shell PowerShell ke bentuk ${env:NAME} PowerShell, baik hook didefinisikan dalam settings.json, plugin, atau skill. PowerShell kemudian menyelesaikan nilai dari lingkungan yang diekspor setelah parsing, jadi placeholder bekerja di dalam string dengan tanda kutip ganda tetapi tidak di dalam string dengan tanda kutip tunggal, di mana PowerShell tidak pernah memperluas variabel.

Sebelum v2.1.198, penulisan ulang ini hanya berlaku untuk plugin hooks. Pada versi yang lebih awal, hook settings.json memerlukan bentuk $env: atau exec form, di mana ${CLAUDE_PROJECT_DIR} diganti di setiap elemen args terlepas dari di mana hook didefinisikan.

Jangan tulis ejaan bare $CLAUDE_PROJECT_DIR dalam hook PowerShell. PowerShell menguraikannya sebagai variabel lokal yang tidak terdefinisi dan menyelesaikannya ke $null, yang meninggalkan jalur skrip tanpa awalan root proyeknya. Claude Code tidak menulis ulang bentuk itu; sebaliknya, ia mencatat peringatan dalam debug log.

Contoh di bawah menunjukkan hook settings.json yang menjalankan skrip proyek dengan bentuk $env:, yang bekerja di setiap versi:

{
  "type": "command",
  "shell": "powershell",
  "command": "& \"$env:CLAUDE_PROJECT_DIR\\.claude\\hooks\\check.ps1\""
}

Debug hooks

Detail eksekusi hook ditulis ke file debug log. Mulai Claude Code dengan claude --debug-file <path> untuk menulis log ke lokasi yang diketahui, atau jalankan claude --debug dan baca log di ~/.claude/debug/<session-id>.txt. Flag --debug tidak mencetak ke terminal.

Sebagai contoh, hook PostToolUse pada Write yang perintahnya mencetak hook-ran menghasilkan entri seperti:

2026-07-19T02:03:24.382Z [DEBUG] Hook output does not start with {, treating as plain text
2026-07-19T02:03:24.382Z [DEBUG] "Hook PostToolUse:Write (PostToolUse) success:\nhook-ran"

Untuk detail pencocokan hook yang lebih granular, atur CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose untuk melihat baris log tambahan seperti jumlah matcher hook dan pencocokan query.

Untuk troubleshooting masalah umum seperti hooks tidak dijalankan, Stop hooks yang terus memblokir, atau kesalahan konfigurasi, lihat Limitations and troubleshooting dalam panduan. Untuk panduan diagnostik yang lebih luas mencakup /context, /doctor, dan precedence pengaturan, lihat Debug your config.