Belakangan ini, saya sedang mengatasi isu claude yang memungkinkan modifikasi file tanpa persetujuan pengguna dengan membuat hook.
Namun, muncul pertanyaan:
Setelah diperiksa, ternyata blok hook selama proses subagen menyebabkan kegagalan dan laporan bahwa pekerjaan subagen telah dihentikan.
Ini jelas pemborosan token.
Beruntungnya, saya menemukan cabang yang dapat membedakan subagen dan memicu permintaan ask sebelum modifikasi dengan menggunakan hook.
Saya khawatir ask akan diabaikan dalam mode yolo, jadi saya mengujinya. Ternyata, pengaturan ask melalui hook mengoverride perilaku default dan berfungsi dengan baik.
Karena sudah membahas tentang claude, saya memutuskan untuk memeriksa read_once hook yang telah saya instal sebelumnya. Hook ini dirancang untuk mengirimkan peringatan ke llm jika file dibaca dua kali. Namun, saya ragu apakah itu benar-benar berfungsi.
Setelah diuji, ternyata peringatan tidak dikirim ke llm.
Ternyata, bidang hook tersebut salah konfigurasi. Ketika saya meminta claude untuk mengujinya, tiba-tiba ia mulai menganalisis biner claude.
Dengan susah payah, masalahnya terselesaikan. Lalu muncul pertanyaan:
Saya meminta claude untuk menunjukkan lokasi kode sumber dan memeriksa opsi hook yang dapat diterapkan. Hasilnya, ia menemukan banyak opsi hook yang tidak tercantum dalam manual. Beberapa bahkan telah diuji.
Saya akan membagikan hasil pengujian tersebut nanti jika Anda tertarik.
🔌 Ringkasan Lengkap Output Field Claude Code Hook
Referensi field output hook berdasarkan kode sumber (src/types/hooks.ts v2.1.88) + analisis biner (v2.1.123) + verifikasi melalui eksperimen langsung.
Perbandingan Peran Field
Field | Lokasi | Target | Tindakan | Verifikasi |
|---|
systemMessage
| Teratas | Pengguna (Tampilan UI) | Hanya ditampilkan di UI, tidak dimasukkan ke konteks AI | ✅ |
hookSpecificOutput.additionalContext
| Di dalam hookSpecificOutput | Model AI | Konteks AI diinjeksikan dengan | ✅ |
hookSpecificOutput.permissionDecisionReason
| Di dalam hookSpecificOutput | Log/UI | Alasan keputusan izin tidak dimasukkan ke konteks AI jika diizinkan, tetapi dikirimkan ke AI jika ditolak. | ✅ |
hookSpecificOutput.updatedInput
| Di dalam hookSpecificOutput | Input alat | Mengganti nilai input sebelum menjalankan alat. | ✅ |
hookSpecificOutput.updatedMCPToolOutput
| Di dalam hookSpecificOutput | Output alat MCP | Mengganti nilai hasil alat MCP. | Belum diverifikasi |
Dasar Kode Sumber (src/types/hooks.ts)
// Field teratas
systemMessage: z.string().describe('Pesan peringatan yang ditampilkan ke pengguna').optional()
// hookSpecificOutput — PreToolUse
z.object({
hookEventName: z.literal('PreToolUse'),
permissionDecision: permissionBehaviorSchema().optional(),
permissionDecisionReason: z.string().optional(),
updatedInput: z.record(z.string(), z.unknown()).optional(), // Modifikasi input alat
additionalContext: z.string().optional(), // Injeksi konteks AI
})
// hookSpecificOutput — PostToolUse
z.object({
hookEventName: z.literal('PostToolUse'),
additionalContext: z.string().optional(),
updatedMCPToolOutput: z.unknown().optional(), // Modifikasi output MCP
})
Alur Pengolahan Internal (src/utils/hooks.ts):
// systemMessage → hook_system_message (tampilan TUI saja)
if (result.systemMessage) {
yield { message: createAttachmentMessage({ type: 'hook_system_message', ... }) }
}
// additionalContext → hook_additional_context (injeksi konteks AI)
if (result.additionalContext) {
yield { additionalContexts: [result.additionalContext] }
}
// → createAttachmentMessage({ type: 'hook_additional_context', content: [...] })
Format Output yang Divalidasi
Modus PreToolUse warn (allow + injeksi konteks AI)
jq -cn --arg r "$REASON" \
'{"systemMessage":$r,"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":$r}}'
Modus PreToolUse deny
{
"hookSpecificOutput": {
"permissionDecision": "deny",
"permissionDecisionReason": "alasan pemblokiran"
}
}
PreToolUse updatedInput — Modifikasi Input Alat
# Intersep dan modifikasi perintah Bash
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": {
"command": "echo OVERRIDDEN" # Bash tool
# "file_path": "/new/path" # Read tool
}
}
}))
PostToolUse additionalContext
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "konten untuk disuntikkan ke konteks AI setelah eksekusi alat"
}
}
Hasil Eksperimen Langsung (2026-05-01)
Eksperimen 1: Validasi Injeksi additionalContext
Sisipkan instruksi ACKNOWLEDGE: tulis X di baris pertama respons ke dalam additionalContext → verifikasi AI menjalankan instruksi.
Eksperimen 2: updatedInput — Modifikasi Perintah Bash ✅
# Perintah aktual yang dieksekusi
echo HOOK_TEST_ORIGINAL
# Output
HOOK_TEST_OVERRIDDEN ← hook mengganti
Eksperimen 3: updatedInput — Pengalihan Jalur File Read ✅
Permintaan Read("/tmp/REDIRECT_SOURCE.txt")
→ hook mengganti file_path ke "/tmp/REDIRECT_TARGET.txt"
→ kembalikan konten file TARGET
Eksperimen 4: PostToolUse additionalContext ✅
Verifikasi additionalContext hook disuntikkan ke konteks AI sebagai setelah eksekusi alat Bash.
Hook Events yang Mendukung additionalContext
Event dengan field additionalContext berdasarkan kode sumber:
hookEventName | additionalContext |
|---|
PreToolUse | ✅ |
PostToolUse | ✅ |
PostToolUseFailure | ✅ |
UserPromptSubmit | ✅ |
SessionStart | ✅ |
Setup | ✅ |
SubagentStart | ✅ |
Notification | ✅ |
Field Lain yang Patut Diperhatikan
Bidang | hook | Keterangan |
|---|
updatedInput
| PreToolUse | Modifikasi input alat (perintah Bash, baca file_path, dll) |
updatedMCPToolOutput
| PostToolUse | Penggantian nilai hasil alat MCP |
initialUserMessage
| SessionStart | Penyuntikan pesan pertama saat awal sesi |
watchPaths
| SessionStart / CwdChanged | Pendaftaran jalur pemantauan file |
retry
| PermissionDenied | Apakah akan mencoba lagi setelah penolakan izin |
updatedPermissions
| PermissionRequest | Perubahan izin dinamis |
Penerapan hook read-once
~/.claude/read-once/hook.sh mode warn ditambahkan additionalContext (2026-05-01):
# Sebelum perubahan (konteks AI belum dimasukkan)
jq -cn --arg r "$REASON" \
'{"systemMessage":$r,"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
# Setelah perubahan (konteks AI dimasukkan)
jq -cn --arg r "$REASON" \
'{"systemMessage":$r,"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":$r}}'
Perubahan diff (mode warn) juga dimodifikasi + heredoc → penggantian jq (karakter khusus aman).
🤖 Pola Deteksi dan Kontrol Sub-agent Hook Claude Code
Pola deteksi dan kontrol sub-agent di Claude Code saat mencoba modifikasi file (Edit/Write/Bash Write) menggunakan PreToolUse hook.
Penemuan Utama
1. Pembagian ID Sesi
Sub-agent berbagi ID sesi yang sama dengan parent. Karena hook UserPromptSubmit hanya dijalankan saat ada pesan pengguna, sub-agent tidak membuat file prompt terpisah. Akibatnya, sub-agent membaca file pesan pengguna parent.
2. Deteksi melalui Bidang agent_id
Sub-agent dapat dideteksi dari bidang agent_id pada JSON yang diterima hook PreToolUse:
Bidang | Agen Utama | Sub-agent | Logika yang Digunakan |
|---|
agent_id
| Tidak ada | Ada (contoh: aaa6abfab...) | ✅ Digunakan |
agent_type
| Tidak ada | Ada (contoh: general-purpose) | ❌ Tidak Digunakan |
Konfirmasi Dokumen Resmi: "agent_id and agent_type are populated when the hook fires inside a subagent."
Meskipun agent_type diamati sebagai general-purpose pada sub-agent yang dipanggil secara acak, pembedaan dengan panggilan berbasis keterampilan masih belum jelas. Oleh karena itu, hanya deteksi melalui keberadaan atau tidaknya bidang agent_id yang dianggap cukup dan handal.
3. Sub-agent Tidak Secara Otomatis Memaksakan Izin Parent
Konfirmasi Dokumen Resmi: "Subagents do not automatically inherit parent agent permissions."
Ketika memanggil keterampilan, nama keterampilan disertakan dalam pesan pengguna dan melewati daftar putih, tetapi sub-agen yang dibangkitkan oleh Claude melalui penilaian mandiri dapat secara tidak sengaja lulus atau diblokir berdasarkan pesan pengguna induk.
permissionDecision 동작 비교
Nilai | Tindakan | Konteks AI yang disuntikkan |
|---|
"allow"
| Hanya lulus | — |
"deny"
| Diblokir sepenuhnya | Alasan disampaikan ke sub-agen |
"ask"
| Dialog persetujuan UI pengguna | Tidak disampaikan ke AI |
"defer"
| Ditunda nanti (khusus TypeScript) | — |
Prioritas: deny > defer > ask > allow
Catatan
ask naik langsung ke UI pengguna dan tidak disuntikkan ke konteks AI. deny menyampaikan alasan ke konteks sub-agen sehingga sub-agen dapat memahami dan melaporkan alasannya.
Pola Penerapan
Kode yang mendeteksi sub-agen dan mengalihkan ke ask (hook perintah shell):
raw = sys.argv[1]
try:
d = json.loads(raw)
# mendeteksi sub-agen → mengalihkan ke ask
agent_id = d.get("agent_id", "")
if agent_id:
file_path = d.get("tool_input", {}).get("file_path", "?")
reason = f"Sub-agen({agent_id[:8]}) meminta modifikasi file: {file_path}"
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "ask",
"permissionDecisionReason": reason
}
}))
sys.exit(0)
# logika selanjutnya (untuk agen utama)
...
Peringatan
ask dapat disetujui secara otomatis saat permission_mode adalah acceptEdits. Periksa mode izin lingkungan jika diperlukan konfirmasi pengguna.
Penting
Hook ask tidak dilewati bahkan dalam mode yolo. Mode yolo secara otomatis menyetujui prompt izin umum Claude Code, tetapi hook mengintervensi pada lapisan yang lebih tinggi. Hook ask selalu menampilkan dialog pengguna terlepas dari status aktifnya mode yolo. → Mekanisme keamanan yang dapat diandalkan untuk mengontrol modifikasi sub-agen.
Hook yang Diterapkan ke Bolt Ini
File | Target | Isi Penerapan |
|---|
~/.claude/scripts/hooks/pre-edit-question-block.sh
| Edit / Tulis | sub-agen → ask |
~/.claude/scripts/hooks/pre-bash-write-block.sh
| Perintah Tulis Bash | sub-agen → ask |