Hooks memungkinkan Anda untuk menginterceptor eksekusi agen pada titik-titik kunci untuk menambahkan validasi, logging, kontrol keamanan, atau logika kustom. Dengan hooks, Anda dapat:
Sebuah hook memiliki dua bagian:
PreToolUse) dan tool mana yang cocokContoh berikut memblokir agen dari memodifikasi file .env. Pertama, tentukan callback yang memeriksa jalur file, kemudian teruskan ke query() untuk dijalankan sebelum panggilan tool Write atau Edit apa pun:
Ini adalah hook PreToolUse. Hook ini berjalan sebelum tool dieksekusi dan dapat memblokir atau mengizinkan operasi berdasarkan logika Anda. Sisa panduan ini mencakup semua hook yang tersedia, opsi konfigurasi mereka, dan pola untuk kasus penggunaan umum.
SDK menyediakan hooks untuk tahap berbeda dari eksekusi agen. Beberapa hooks tersedia di kedua SDK, sementara yang lain hanya TypeScript karena Python SDK tidak mendukungnya.
| Hook Event | Python SDK | TypeScript SDK | Apa yang memicunya | Contoh kasus penggunaan |
|---|---|---|---|---|
PreToolUse | Ya | Ya | Permintaan pemanggilan tool (dapat memblokir atau memodifikasi) | Blokir perintah shell berbahaya |
PostToolUse | Ya | Ya | Hasil eksekusi tool | Log semua perubahan file ke jejak audit |
PostToolUseFailure | Tidak | Ya | Kegagalan eksekusi tool | Tangani atau log kesalahan tool |
UserPromptSubmit | Ya | Ya | Pengajuan prompt pengguna | Suntikkan konteks tambahan ke dalam prompt |
Hooks cukup fleksibel untuk menangani banyak skenario berbeda. Berikut adalah beberapa pola paling umum yang diorganisir berdasarkan kategori.
Untuk mengonfigurasi hook untuk agen Anda, teruskan hook dalam parameter options.hooks saat memanggil query():
async for message in query(
prompt="Your prompt",
options=ClaudeAgentOptions(
hooks={
'PreToolUse': [HookMatcher(matcher='Bash', hooks=[my_callback])]
}
)
):
print(message)Opsi hooks adalah kamus (Python) atau objek (TypeScript) di mana:
'PreToolUse', 'PostToolUse', 'Stop')Fungsi callback hook Anda menerima data input tentang acara dan mengembalikan respons sehingga agen tahu untuk mengizinkan, memblokir, atau memodifikasi operasi.
Gunakan matcher untuk memfilter tool mana yang memicu callback Anda:
| Opsi | Tipe | Default | Deskripsi |
|---|---|---|---|
matcher | string | undefined | Pola regex untuk mencocokkan nama tool. Tool bawaan termasuk Bash, Read, Write, Edit, Glob, Grep, WebFetch, Task, dan lainnya. Tool MCP menggunakan pola mcp__<server>__<action>. |
hooks | HookCallback[] | - |
Gunakan pola matcher untuk menargetkan tool tertentu kapan pun memungkinkan. Matcher dengan 'Bash' hanya berjalan untuk perintah Bash, sementara menghilangkan pola menjalankan callback Anda untuk setiap pemanggilan tool. Perhatikan bahwa matcher hanya memfilter berdasarkan nama tool, bukan jalur file atau argumen lainnya—untuk memfilter berdasarkan jalur file, periksa tool_input.file_path di dalam callback Anda.
Matcher hanya berlaku untuk hook berbasis tool (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest). Untuk hook siklus hidup seperti Stop, SessionStart, dan Notification, matcher diabaikan dan hook dipicu untuk semua acara dari tipe itu.
Menemukan nama tool: Periksa array tools dalam pesan sistem awal ketika sesi Anda dimulai, atau tambahkan hook tanpa matcher untuk mencatat semua pemanggilan tool.
Penamaan tool MCP: Tool MCP selalu dimulai dengan mcp__ diikuti oleh nama server dan tindakan: mcp__<server>__<action>. Misalnya, jika Anda mengonfigurasi server bernama playwright, toolnya akan dinamai mcp__playwright__browser_screenshot, mcp__playwright__browser_click, dll. Nama server berasal dari kunci yang Anda gunakan dalam konfigurasi mcpServers.
Contoh ini menggunakan matcher untuk menjalankan hook hanya untuk tool yang memodifikasi file ketika acara PreToolUse dipicu:
options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
HookMatcher(matcher='Write|Edit', hooks=[validate_file_path])
]
}
)Setiap callback hook menerima tiga argumen:
dict / HookInput): Detail acara. Lihat data input untuk bidangstr | None / string | null): Korelasikan acara PreToolUse dan PostToolUseHookContext): Di TypeScript, berisi properti signal (AbortSignal) untuk pembatalan. Teruskan ini ke operasi async seperti fetch() sehingga mereka secara otomatis membatalkan jika hook kedaluwarsa. Di Python, argumen ini dicadangkan untuk penggunaan di masa depan.Argumen pertama untuk callback hook Anda berisi informasi tentang acara. Nama bidang identik di seluruh SDK (keduanya menggunakan snake_case).
Bidang umum yang ada di semua tipe hook:
| Bidang | Tipe | Deskripsi |
|---|---|---|
hook_event_name | string | Tipe hook (PreToolUse, PostToolUse, dll.) |
session_id | string | Pengidentifikasi sesi saat ini |
transcript_path | string | Jalur ke transkrip percakapan |
cwd | string | Direktori kerja saat ini |
Bidang spesifik hook bervariasi menurut tipe hook. Item yang ditandai TS hanya tersedia di TypeScript SDK:
| Bidang | Tipe | Deskripsi | Hooks |
|---|---|---|---|
tool_name | string | Nama tool yang dipanggil | PreToolUse, PostToolUse, PostToolUseFailureTS, PermissionRequestTS |
tool_input | object | Argumen yang dilewatkan ke tool | PreToolUse, PostToolUse, PostToolUseFailureTS, PermissionRequestTS |
tool_response | any | Hasil yang dikembalikan dari eksekusi tool | PostToolUse |
error | string |
Kode di bawah mendefinisikan callback hook yang menggunakan tool_name dan tool_input untuk mencatat detail tentang setiap pemanggilan tool:
Fungsi callback Anda mengembalikan objek yang memberitahu SDK cara melanjutkan. Kembalikan objek kosong {} untuk mengizinkan operasi tanpa perubahan. Untuk memblokir, memodifikasi, atau menambahkan konteks ke operasi, kembalikan objek dengan bidang hookSpecificOutput yang berisi keputusan Anda.
Bidang tingkat atas (di luar hookSpecificOutput):
| Bidang | Tipe | Deskripsi |
|---|---|---|
continue | boolean | Apakah agen harus melanjutkan setelah hook ini (default: true) |
stopReason | string | Pesan yang ditampilkan ketika continue adalah false |
suppressOutput | boolean | Sembunyikan stdout dari transkrip (default: false) |
systemMessage | string |
Bidang di dalam hookSpecificOutput:
| Bidang | Tipe | Hooks | Deskripsi |
|---|---|---|---|
hookEventName | string | Semua | Diperlukan. Gunakan input.hook_event_name untuk mencocokkan acara saat ini |
permissionDecision | 'allow' | 'deny' | 'ask' | PreToolUse | Mengontrol apakah tool dieksekusi |
permissionDecisionReason | string | PreToolUse | Penjelasan yang ditampilkan kepada Claude untuk keputusan |
updatedInput |
Contoh ini memblokir operasi penulisan ke direktori /etc sambil menyuntikkan pesan sistem untuk mengingatkan Claude tentang praktik file yang aman:
Ketika beberapa hook atau aturan izin berlaku, SDK mengevaluasinya dalam urutan ini:
Jika hook apa pun mengembalikan deny, operasi diblokir—hook lain yang mengembalikan allow tidak akan menimpanya.
Kembalikan keputusan deny untuk mencegah eksekusi tool:
Kembalikan input yang diperbarui untuk mengubah apa yang diterima tool:
Saat menggunakan updatedInput, Anda juga harus menyertakan permissionDecision. Selalu kembalikan objek baru daripada mutasi tool_input asli.
Suntikkan konteks ke dalam percakapan:
async def add_security_reminder(input_data, tool_use_id, context):
return {
'systemMessage': 'Remember to follow security best practices.'
}Lewati prompt izin untuk tool terpercaya. Ini berguna ketika Anda ingin operasi tertentu berjalan tanpa konfirmasi pengguna:
Bidang permissionDecision menerima tiga nilai: 'allow' (persetujuan otomatis), 'deny' (blokir), atau 'ask' (minta konfirmasi).
Pola-pola ini membantu Anda membangun sistem hook yang lebih canggih untuk kasus penggunaan yang kompleks.
Hooks dieksekusi dalam urutan kemunculannya di array. Jaga setiap hook tetap fokus pada tanggung jawab tunggal dan rantai beberapa hook untuk logika kompleks. Contoh ini menjalankan keempat hook untuk setiap pemanggilan tool (tidak ada matcher yang ditentukan):
Gunakan pola regex untuk mencocokkan beberapa tool:
Matcher hanya mencocokkan nama tool, bukan jalur file atau argumen lainnya. Untuk memfilter berdasarkan jalur file, periksa tool_input.file_path di dalam callback hook Anda.
Gunakan hook SubagentStop untuk memantau penyelesaian subagen. tool_use_id membantu menghubungkan panggilan agen induk dengan subagen mereka:
Hooks dapat melakukan operasi async seperti permintaan HTTP. Tangani kesalahan dengan baik dengan menangkap pengecualian daripada melemparnya. Di TypeScript, teruskan signal ke fetch() sehingga permintaan dibatalkan jika hook kedaluwarsa:
Gunakan hook Notification untuk menerima pembaruan status dari agen dan meneruskannya ke layanan eksternal seperti Slack atau dashboard pemantauan:
import { query, HookCallback, NotificationHookInput } from "@anthropic-ai/claude-agent-sdk";
const notificationHandler: HookCallback = async (input, toolUseID, { signal }) => {
const notification = input as NotificationHookInput;
await fetch('https://hooks.slack.com/services/YOUR/WEBHOOK/URL', {
method: 'POST',
body: JSON.stringify({
text: `Agent status: ${notification.message}`
}),
signal
});
return {};
};
for await (const message of query({
prompt: "Analyze this codebase",
options: {
hooks: {
Notification: [{ hooks: [notificationHandler] }]
}
}
})) {
console.log(message);
}Bagian ini mencakup masalah umum dan cara menyelesaikannya.
PreToolUse, bukan preToolUse)options.hooksSubagentStop, Stop, SessionStart, SessionEnd, dan Notification, matcher diabaikan. Hook ini dipicu untuk semua acara dari tipe itu.max_turns karena sesi berakhir sebelum hook dapat dieksekusiMatcher hanya mencocokkan nama tool, bukan jalur file atau argumen lainnya. Untuk memfilter berdasarkan jalur file, periksa tool_input.file_path di dalam hook Anda:
const myHook: HookCallback = async (input, toolUseID, { signal }) => {
const preInput = input as PreToolUseHookInput;
const filePath = preInput.tool_input?.file_path as string;
if (!filePath?.endsWith('.md')) return {}; // Skip non-markdown files
// Process markdown files...
};timeout dalam konfigurasi HookMatcherAbortSignal dari argumen callback ketiga untuk menangani pembatalan dengan baik di TypeScriptPreToolUse untuk pengembalian permissionDecision: 'deny'permissionDecisionReason yang mereka kembalikanPastikan updatedInput berada di dalam hookSpecificOutput, bukan di tingkat atas:
return {
hookSpecificOutput: {
hookEventName: input.hook_event_name,
permissionDecision: 'allow',
updatedInput: { command: 'new command' }
}
};Anda juga harus mengembalikan permissionDecision: 'allow' agar modifikasi input berlaku
Hook SessionStart, SessionEnd, dan Notification hanya tersedia di TypeScript SDK. Python SDK tidak mendukung acara ini karena keterbatasan setup.
Saat memijahkan beberapa subagen, masing-masing mungkin meminta izin secara terpisah. Subagen tidak secara otomatis mewarisi izin agen induk. Untuk menghindari prompt berulang, gunakan hook PreToolUse untuk persetujuan otomatis tool tertentu, atau konfigurasi aturan izin yang berlaku untuk sesi subagen.
Hook UserPromptSubmit yang memijahkan subagen dapat membuat loop tak terbatas jika subagen tersebut memicu hook yang sama. Untuk mencegah ini:
parent_tool_use_id untuk mendeteksi jika Anda sudah dalam konteks subagenBidang systemMessage menambahkan konteks ke percakapan yang model lihat, tetapi mungkin tidak muncul di semua mode output SDK. Jika Anda perlu menampilkan keputusan hook ke aplikasi Anda, log mereka secara terpisah atau gunakan saluran output khusus.
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher
# Define a hook callback that receives tool call details
async def protect_env_files(input_data, tool_use_id, context):
# Extract the file path from the tool's input arguments
file_path = input_data['tool_input'].get('file_path', '')
file_name = file_path.split('/')[-1]
# Block the operation if targeting a .env file
if file_name == '.env':
return {
'hookSpecificOutput': {
'hookEventName': input_data['hook_event_name'],
'permissionDecision': 'deny',
'permissionDecisionReason': 'Cannot modify .env files'
}
}
# Return empty object to allow the operation
return {}
async def main():
async for message in query(
prompt="Update the database configuration",
options=ClaudeAgentOptions(
hooks={
# Register the hook for PreToolUse events
# The matcher filters to only Write and Edit tool calls
'PreToolUse': [HookMatcher(matcher='Write|Edit', hooks=[protect_env_files])]
}
)
):
print(message)
asyncio.run(main())Stop | Ya | Ya | Penghentian eksekusi agen | Simpan status sesi sebelum keluar |
SubagentStart | Tidak | Ya | Inisialisasi subagen | Lacak pemijahan tugas paralel |
SubagentStop | Ya | Ya | Penyelesaian subagen | Agregasi hasil dari tugas paralel |
PreCompact | Ya | Ya | Permintaan pemadatan percakapan | Arsipkan transkrip lengkap sebelum merangkum |
PermissionRequest | Tidak | Ya | Dialog izin akan ditampilkan | Penanganan izin kustom |
SessionStart | Tidak | Ya | Inisialisasi sesi | Inisialisasi logging dan telemetri |
SessionEnd | Tidak | Ya | Penghentian sesi | Bersihkan sumber daya sementara |
Notification | Tidak | Ya | Pesan status agen | Kirim pembaruan status agen ke Slack atau PagerDuty |
| Diperlukan. Array fungsi callback untuk dieksekusi ketika pola cocok |
timeout | number | 60 | Timeout dalam detik; tingkatkan untuk hooks yang melakukan panggilan API eksternal |
| Pesan kesalahan dari kegagalan eksekusi tool |
| PostToolUseFailureTS |
is_interrupt | boolean | Apakah kegagalan disebabkan oleh gangguan | PostToolUseFailureTS |
prompt | string | Teks prompt pengguna | UserPromptSubmit |
stop_hook_active | boolean | Apakah hook stop sedang diproses | Stop, SubagentStop |
agent_id | string | Pengidentifikasi unik untuk subagen | SubagentStartTS, SubagentStopTS |
agent_type | string | Tipe/peran subagen | SubagentStartTS |
agent_transcript_path | string | Jalur ke transkrip percakapan subagen | SubagentStopTS |
trigger | string | Apa yang memicu pemadatan: manual atau auto | PreCompact |
custom_instructions | string | Instruksi kustom yang disediakan untuk pemadatan | PreCompact |
permission_suggestions | array | Pembaruan izin yang disarankan untuk tool | PermissionRequestTS |
source | string | Bagaimana sesi dimulai: startup, resume, clear, atau compact | SessionStartTS |
reason | string | Mengapa sesi berakhir: clear, logout, prompt_input_exit, bypass_permissions_disabled, atau other | SessionEndTS |
message | string | Pesan status dari agen | NotificationTS |
notification_type | string | Tipe notifikasi: permission_prompt, idle_prompt, auth_success, atau elicitation_dialog | NotificationTS |
title | string | Judul opsional yang ditetapkan oleh agen | NotificationTS |
async def log_tool_calls(input_data, tool_use_id, context):
if input_data['hook_event_name'] == 'PreToolUse':
print(f"Tool: {input_data['tool_name']}")
print(f"Input: {input_data['tool_input']}")
return {}| Pesan yang disuntikkan ke dalam percakapan untuk Claude lihat |
object |
| PreToolUse |
Input tool yang dimodifikasi (memerlukan permissionDecision: 'allow') |
additionalContext | string | PostToolUse, UserPromptSubmit, SessionStartTS, SubagentStartTS | Konteks yang ditambahkan ke percakapan |
async def block_etc_writes(input_data, tool_use_id, context):
file_path = input_data['tool_input'].get('file_path', '')
if file_path.startswith('/etc'):
return {
# Top-level field: inject guidance into the conversation
'systemMessage': 'Remember: system directories like /etc are protected.',
# hookSpecificOutput: block the operation
'hookSpecificOutput': {
'hookEventName': input_data['hook_event_name'],
'permissionDecision': 'deny',
'permissionDecisionReason': 'Writing to /etc is not allowed'
}
}
return {}async def block_dangerous_commands(input_data, tool_use_id, context):
if input_data['hook_event_name'] != 'PreToolUse':
return {}
command = input_data['tool_input'].get('command', '')
if 'rm -rf /' in command:
return {
'hookSpecificOutput': {
'hookEventName': input_data['hook_event_name'],
'permissionDecision': 'deny',
'permissionDecisionReason': 'Dangerous command blocked: rm -rf /'
}
}
return {}async def redirect_to_sandbox(input_data, tool_use_id, context):
if input_data['hook_event_name'] != 'PreToolUse':
return {}
if input_data['tool_name'] == 'Write':
original_path = input_data['tool_input'].get('file_path', '')
return {
'hookSpecificOutput': {
'hookEventName': input_data['hook_event_name'],
'permissionDecision': 'allow',
'updatedInput': {
**input_data['tool_input'],
'file_path': f'/sandbox{original_path}'
}
}
}
return {}async def auto_approve_read_only(input_data, tool_use_id, context):
if input_data['hook_event_name'] != 'PreToolUse':
return {}
read_only_tools = ['Read', 'Glob', 'Grep', 'LS']
if input_data['tool_name'] in read_only_tools:
return {
'hookSpecificOutput': {
'hookEventName': input_data['hook_event_name'],
'permissionDecision': 'allow',
'permissionDecisionReason': 'Read-only tool auto-approved'
}
}
return {}options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
HookMatcher(hooks=[rate_limiter]), # First: check rate limits
HookMatcher(hooks=[authorization_check]), # Second: verify permissions
HookMatcher(hooks=[input_sanitizer]), # Third: sanitize inputs
HookMatcher(hooks=[audit_logger]) # Last: log the action
]
}
)options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
# Match file modification tools
HookMatcher(matcher='Write|Edit|Delete', hooks=[file_security_hook]),
# Match all MCP tools
HookMatcher(matcher='^mcp__', hooks=[mcp_audit_hook]),
# Match everything (no matcher)
HookMatcher(hooks=[global_logger])
]
}
)async def subagent_tracker(input_data, tool_use_id, context):
if input_data['hook_event_name'] == 'SubagentStop':
print(f"[SUBAGENT] Completed")
print(f" Tool use ID: {tool_use_id}")
print(f" Stop hook active: {input_data.get('stop_hook_active')}")
return {}
options = ClaudeAgentOptions(
hooks={
'SubagentStop': [HookMatcher(hooks=[subagent_tracker])]
}
)import aiohttp
from datetime import datetime
async def webhook_notifier(input_data, tool_use_id, context):
if input_data['hook_event_name'] != 'PostToolUse':
return {}
try:
async with aiohttp.ClientSession() as session:
await session.post(
'https://api.example.com/webhook',
json={
'tool': input_data['tool_name'],
'timestamp': datetime.now().isoformat()
}
)
except Exception as e:
print(f'Webhook request failed: {e}')
return {}Sertakan hookEventName dalam hookSpecificOutput untuk mengidentifikasi tipe hook mana output tersebut