Panduan

Hooks Claude Code: cara mengubah aturan agen menjadi jaminan

Diperbarui: · Skillaria.Top

Hooks Claude Code adalah perintah shell yang dijalankan Claude Code sendiri pada event tertentu: sebelum dan sesudah pemanggilan alat, saat prompt dikirim, saat sesi dimulai, dan sebelum agen berhenti. Hooks dikonfigurasi di settings.json, dan kode keluar 2 memblokir tindakan serta mengembalikan pesan kesalahan ke agen, sehingga hook mengubah aturan dari CLAUDE.md menjadi jaminan yang tidak bisa dilupakan model.

Apa itu hooks Claude Code

Hook adalah perintah yang dijalankan bukan oleh model, melainkan oleh program Claude Code itu sendiri pada momen tertentu dalam kerja agen. Agen hendak menjalankan rm -rf — hook berjalan dan memutuskan apakah perintah itu dilewatkan. Agen selesai mengedit file — hook menjalankan formatter. Agen hendak mengakhiri pekerjaan — hook menjalankan tes dan tidak membiarkannya berhenti selama tes masih merah.

Perbedaan utama dengan instruksi di CLAUDE.md: instruksi adalah teks yang dipertimbangkan model dengan probabilitas tertentu, sedangkan hook adalah kode deterministik. Ia selalu berjalan, terlepas dari seberapa panjang konteksnya, apa yang «diputuskan» agen, dan apakah ia lupa aturan pada menit ke-100 sesi. Itulah sebabnya hooks Claude Code menjadi cara utama mengubah harapan menjadi jaminan.

Mengapa aturan di CLAUDE.md saja tidak cukup

Gambaran yang sering terjadi: agen menyelesaikan fitur, tes unit hijau, di chat muncul pernyataan yakin «Selesai». Tetapi skenario end-to-end tidak pernah dijalankan, linter tidak dijalankan, dan migrasi gagal di tengah jalan. Padahal di CLAUDE.md tertulis «sebelum selesai, jalankan tes». Model tidak berbohong dengan sengaja — ia hanya menilai dirinya sendiri, dan penilaian diri itu tidak andal.

Dari sini lahir prinsip harness: semua yang bisa diperiksa secara mekanis, diperiksa oleh mekanisme, bukan oleh model. Aturan «jalankan linter» menjadi hook yang menjalankan linter. Aturan «jangan sentuh konfigurasi produksi» menjadi hook yang memblokir penulisan ke berkas itu. Instruksi tekstual tetap ada, tetapi hanya untuk menjelaskan kepada agen mengapa ia dihentikan.

Event: kapan hook berjalan

Setiap hook terikat pada event siklus hidup sesi. Event utamanya:

EventKapan berjalanPenggunaan tipikal
PreToolUsesebelum pemanggilan alat (Bash, Edit, Write, dan lain-lain)larangan perintah berbahaya, perlindungan file
PostToolUsesetelah pemanggilan alat berhasilformatter, linter, tes cepat setelah edit
UserPromptSubmitsaat pengguna mengirim prompt, sebelum diprosesmenambah konteks, menolak prompt yang berisi rahasia
Stopsaat agen hendak menyelesaikan responsmencegah berhenti sebelum pemeriksaan lulus
SubagentStopsaat subagen menyelesaikan pekerjaanpemeriksaan hasil subagen
SessionStartsaat sesi dimulai atau dilanjutkanmemuat status: git status, rencana, jurnal kemajuan
SessionEndsaat sesi berakhirpembersihan, penulisan log
Notificationsaat Claude Code mengirim notifikasi (misalnya menunggu izin)suara atau pesan ke ponsel
PreCompactsebelum pemadatan konteksmenyimpan hal penting sebelum kompaksi

Yang paling berguna untuk keandalan adalah PreToolUse, PostToolUse, dan Stop: ketiganya menutup tiga kelemahan agen — tindakan berbahaya, edit yang tidak diperiksa, dan klaim «selesai» yang terlalu dini.

Di mana hooks dikonfigurasi: settings.json

Hooks dideskripsikan di berkas pengaturan Claude Code:

  • ~/.claude/settings.json — pribadi Anda, untuk semua proyek;
  • .claude/settings.json di repositori — bersama untuk tim, di-commit ke git;
  • .claude/settings.local.json — lokal untuk proyek, tidak masuk ke git.

Strukturnya: event → daftar grup dengan matcher → daftar perintah.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/lint.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

matcher adalah nama alat atau ekspresi reguler terhadapnya: Bash, Edit|Write, .*. Ia diperlukan untuk event yang terkait alat; untuk Stop atau UserPromptSubmit boleh dihilangkan. Variabel CLAUDE_PROJECT_DIR menunjuk ke akar proyek, sehingga skrip sebaiknya disimpan di .claude/hooks/ dan dipanggil lewat variabel itu. Daftar hook yang aktif bisa dilihat dengan perintah /hooks di dalam Claude Code.

Bagaimana hook menerima data dan memberi jawaban

Masukan: JSON di stdin

Claude Code memberikan hook JSON berisi informasi tentang event: id sesi, direktori kerja, nama event, dan untuk alat — tool_name dan tool_input. Untuk Bash, perintahnya ada di tool_input.command; untuk Edit dan Write, path file ada di tool_input.file_path. Membacanya lebih mudah dengan jq.

{
  "session_id": "abc123",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "rm -rf build/" },
  "cwd": "/home/dev/project"
}

Keluaran: kode kembali

KodeYang terjadi
0sukses, pekerjaan berlanjut; untuk UserPromptSubmit dan SessionStart, stdout ditambahkan ke konteks agen
2kesalahan pemblokir: stderr diteruskan ke agen, dan tindakan dihentikan (untuk PreToolUse — pemanggilan alat dibatalkan; untuk Stop — agen melanjutkan pekerjaan)
lainnyakesalahan tidak memblokir: stderr ditampilkan ke pengguna, pekerjaan tetap berjalan

Exit code 2 adalah mekanisme kuncinya. Agen menerima teks dari stderr sebagai umpan balik dan bisa memperbaiki diri: menulis ulang perintah, memperbaiki tes, tidak menyentuh berkas itu. Karena itu, tulislah di stderr bukan «error», melainkan alasan yang jelas dan apa yang harus dilakukan sebagai gantinya.

Selain kode kembali, hook bisa menulis JSON ke stdout dengan keputusan, misalnya "decision": "block" dan "reason". Ini berguna untuk penyesuaian halus, tetapi untuk sebagian besar tugas cukup kode 2 dan pesan di stderr.

Contoh 1: linter dan tes setelah setiap edit

Hook di PostToolUse dengan matcher Edit|Write berjalan setelah file diubah. Alat sudah dijalankan dan edit tidak bisa dibatalkan, tetapi kode 2 mengembalikan keluaran linter kepada agen, sehingga ia memperbaiki kesalahan segera, bukan setengah jam kemudian.

#!/usr/bin/env bash
# .claude/hooks/lint.sh
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in
  *.py)
    if ! out=$(ruff check "$file" 2>&1); then
      echo "Linter menemukan kesalahan di $file:" >&2
      echo "$out" >&2
      exit 2
    fi ;;
  *.php)
    if ! out=$(php -l "$file" 2>&1); then
      echo "$out" >&2
      exit 2
    fi ;;
esac
exit 0

Jangan pasang di sini seluruh tes yang berjalan sepuluh menit: hook berjalan di setiap edit. Setelah edit cukup pemeriksaan cepat (sintaks, linter, tes satu modul), sedangkan yang berat diletakkan di hook Stop.

Contoh 2: larangan perintah berbahaya

Hook di PreToolUse dengan matcher Bash melihat perintah sebelum dijalankan. Kode 2 membatalkan pemanggilan, dan agen mendapat penjelasannya.

#!/usr/bin/env bash
# .claude/hooks/guard.sh
cmd=$(jq -r '.tool_input.command // empty')
if echo "$cmd" | grep -Eq 'rm -rf /|git push (-f|--force)|DROP (TABLE|DATABASE)'; then
  echo "Perintah diblokir kebijakan proyek: $cmd. Operasi berbahaya dijalankan oleh manusia." >&2
  exit 2
fi
exit 0

Berkas dilindungi dengan cara serupa: hook di Edit|Write memeriksa file_path dan memblokir penulisan ke .env, konfigurasi produksi, kode yang dihasilkan, atau berkas migrasi yang sudah diterapkan. Perhatikan bahwa pencocokan dengan regex bisa diakali — agen bisa menulis perintah dengan cara lain. Hook adalah pengaman yang kuat, tetapi bukan pengganti izin akses dan sandbox.

Contoh 3: agen tidak bisa mengatakan «selesai» selama tes merah

Hook Stop berjalan ketika agen hendak selesai. Jika tes gagal, kode 2 mencegah penghentian, dan keluaran tes dikirim ke agen sebagai tugas berikutnya. Ini versi mekanis dari Definition of Done.

#!/usr/bin/env bash
# .claude/hooks/done.sh
input=$(cat)
# pengaman dari loop tak berujung: hook sudah sekali memperpanjang pekerjaan
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0
fi
if ! out=$(npm test --silent 2>&1); then
  echo "Tes gagal, pekerjaan belum selesai:" >&2
  echo "$out" | tail -n 40 >&2
  exit 2
fi
exit 0

Field stop_hook_active bernilai true jika agen sudah melanjutkan pekerjaan karena stop-hook. Tanpa pengecekan ini, hook bisa terus menggiring agen berputar tanpa akhir — tepat situasi ketika agen «bekerja semalaman ke arah yang salah». Dalam siklus otonom, tambahkan juga batas percobaan dan kondisi yang memanggil manusia.

Contoh 4: memuat status di awal sesi

Pada SessionStart, stdout hook masuk ke konteks. Berguna untuk menampilkan cabang saat ini, perubahan yang belum di-commit, dan tugas berikutnya dari rencana, agar agen tidak mulai dari nol.

#!/usr/bin/env bash
echo "Cabang: $(git branch --show-current)"
git status --short | head -n 20
[ -f PROGRESS.md ] && tail -n 15 PROGRESS.md
exit 0

Bagaimana hooks mengubah aturan menjadi jaminan

Aturan harness berguna dibagi menjadi tiga tingkat. Pertama, teks di CLAUDE.md: menjelaskan cara bekerja, tetapi dipatuhi secara probabilistik. Kedua, pemeriksaan yang bisa dijalankan: tes, E2E, aturan arsitektur dalam bentuk skrip yang gagal ketika dilanggar. Ketiga, hook yang menjalankan pemeriksaan ini secara otomatis pada momen yang tepat, tanpa keterlibatan model.

Aturan praktisnya: jika agen melanggar aturan yang sama dua kali, jangan tambahkan peringatan lain dengan huruf kapital di CLAUDE.md. Tanyakan apakah pelanggaran itu bisa diperiksa dengan skrip. Jika bisa, buat skripnya dan pasang di hook. Dengan begitu teks instruksi menjadi lebih pendek dan perilakunya lebih stabil.

Aturan dalam teksJaminan lewat hook
«Format kode sesuai standar»PostToolUse menjalankan formatter pada file yang diubah
«Jangan push dengan force»PreToolUse pada Bash memblokir perintah, exit 2
«Jangan bilang selesai selama tes merah»Stop menjalankan tes dan tidak membiarkan agen berhenti
«Jangan edit .env»PreToolUse pada Edit|Write memeriksa path
«Ingat di mana berhenti»SessionStart menampilkan rencana dan git status

Kesalahan umum

  • Kode 1 alih-alih 2. Skrip gagal dengan kode 1, agen tidak mengetahuinya, dan tindakan tetap lolos. Hanya kode 2 yang memblokir.
  • Pesan di stdout. Pada kode 2, agen menerima stderr. Tulis lewat >&2.
  • Hook lambat di event yang sering. Menjalankan seluruh tes di PostToolUse membuat setiap edit memakan waktu semenit. Yang berat letakkan di Stop.
  • Stop tak berujung. Tanpa pengecekan stop_hook_active atau batas, agen berputar tanpa henti.
  • Path relatif. Hook berjalan di direktori kerja sesi; gunakan $CLAUDE_PROJECT_DIR.
  • Hook asing yang tidak diperiksa. Hook berjalan dengan hak akses Anda tanpa konfirmasi. Baca skrip dari repositori orang lain sebelum mempercayainya.

Ringkasan

Hooks Claude Code memindahkan aturan terpenting dari teks ke kode: PreToolUse menghentikan tindakan berbahaya, PostToolUse memeriksa setiap edit, Stop mencegah laporan kesiapan terlalu dini, dan SessionStart mengembalikan ingatan tentang proyek kepada agen. Mulailah dengan dua atau tiga hook untuk aturan yang sudah pernah dilanggar agen, dan buat pesan di stderr jelas — dengan begitu agen memperbaiki kesalahannya sendiri, dan Anda memeriksa hasilnya, bukan prosesnya. Validasi, pemeriksaan kesiapan, dan siklus otonom dibahas praktis di kursus.

Pertanyaan umum

Perlukah me-restart Claude Code setelah mengubah hook?

Perubahan di settings.json paling andal diterapkan di sesi baru; konfigurasi hook yang aktif bisa dilihat dengan perintah /hooks.

Bolehkah menulis hook tidak dalam bash?

Boleh. Hook adalah perintah apa pun: skrip Python, Node.js, atau binari. Yang penting, ia membaca JSON dari stdin dan mengembalikan kode keluar yang benar.

Apakah agen melihat keluaran hook yang berhasil?

Biasanya tidak: dengan kode 0 keluarannya tidak masuk ke konteks. Pengecualiannya UserPromptSubmit dan SessionStart, di mana stdout ditambahkan ke konteks agen.

Apakah hook berlaku untuk subagen?

Ya. Pemanggilan alat subagen melewati hook PreToolUse dan PostToolUse yang sama, dan untuk penyelesaiannya ada event tersendiri, SubagentStop.

Amankah meng-commit hook ke repositori?

Aman. .claude/settings.json dan skrip di .claude/hooks/ adalah praktik umum untuk tim; hook pribadi simpan di settings.local.json atau ~/.claude/settings.json.