Samouczek hooków Claude Code: 5 produkcyjnych hooków od zera
Claude Code w zdecydowanej większości przypadków wykonuje właściwą akcję. Pozostają przypadki brzegowe: force-push na main, pominięty formatter, commit kodu, który nie przechodzi lintera. Hooki eliminują te przypadki, dodając deterministyczne bramki w 31 punktach cyklu życia workflow Claude’a (stan na sierpień 2026).1 Ten samouczek należy do mojej serii AI engineering poświęconej budowie produkcyjnych systemów agentowych. Uruchamiają się za każdym razem, bez wyjątku, niezależnie od brzmienia promptu i zachowania modelu.
TL;DR: hooki to polecenia powłoki wyzwalane przez zdarzenia cyklu życia Claude Code.1 Hooki PreToolUse badają akcje i je blokują (kod wyjścia 2 = blokada, kod 0 = zgoda).2 Hooki PostToolUse sprawdzają i formatują już po fakcie. Konfiguruje się je w .claude/settings.json za pomocą klucza matcher (dokładna nazwa narzędzia, lista rozdzielona znakiem | albo wyrażenie regularne) oraz zagnieżdżonej tablicy hooks.3 Poniższy samouczek buduje pięć produkcyjnych hooków: autoformatter, bramkę bezpieczeństwa, uruchamianie testów, powiadomienie oraz kontrolę jakości przed commitem.
Najważniejsze wnioski
- Programiści pracujący samodzielnie: warto zacząć od autoformattera (hook 1) i bramki bezpieczeństwa (hook 2). Te dwa hooki zapobiegają najczęstszym pomyłkom Claude Code i nie wymagają późniejszej obsługi.
- Liderzy zespołów: hooki należy umieścić w pliku
.claude/settings.jsonw repozytorium. Każda osoba w zespole automatycznie otrzymuje te same bramki bezpieczeństwa i kontrole jakości. - Inżynierowie bezpieczeństwa: kod wyjścia 2 blokuje akcję.2 Kod wyjścia 1 jedynie zapisuje ostrzeżenie. Każdy hook bezpieczeństwa PreToolUse musi używać
exit 2, w przeciwnym razie niczego nie egzekwuje.
Czym są hooki?
Hooki to polecenia powłoki uruchamiane przy określonych zdarzeniach cyklu życia sesji Claude Code. Działają poza LLM, jako zwykłe skrypty wyzwalane akcjami Claude’a, a nie jako prompty interpretowane przez model.
Cztery główne kategorie pokrywają najczęstsze zastosowania (Claude Code dokumentuje 31 typów zdarzeń, stan na sierpień 2026):1
- Zdarzenia sesji:
SessionStarturuchamia się na początku sesji,SessionEndprzy jej zamknięciu, aStopza każdym razem, gdy Claude kończy odpowiedź (nie tylko na koniec sesji). Przydają się do przygotowania środowiska, sprzątania i powiadomień. - Zdarzenia narzędzi:
PreToolUseiPostToolUseuruchamiają się przed użyciem narzędzia przez Claude’a i po nim (zapis pliku, wykonanie polecenia bash, przeszukanie kodu). To najpotężniejsze hooki, ponieważ mogą badać i blokować konkretne akcje. - Zdarzenia powiadomień:
Notificationuruchamia się, gdy Claude generuje powiadomienie. Przydaje się do kierowania alertów na Slacka, do powiadomień systemowych albo do systemów logowania. - Zdarzenia subagentów:
SubagentStopuruchamia się, gdy subagent (uruchomiony narzędziem Agent) kończy pracę.4 Hooki uruchamiają się także dla akcji subagentów, więc bramki bezpieczeństwa działają rekurencyjnie.
Semantyka kodów wyjścia ma znaczenie.2 Kod 0 oznacza powodzenie (kontynuuj). Kod 2 oznacza zablokowanie akcji. Kod 1 to nieblokujący błąd hooka, po którym akcja i tak dochodzi do skutku. Każdy hook krytyczny dla bezpieczeństwa musi używać exit 2, aby jego bramka faktycznie działała.
Model myślowy: trzy rodzaje gwarancji
Zanim powstanie jakikolwiek hook, warto zapytać: jakiej gwarancji potrzebuję?
Gwarancje formatowania zapewniają spójność po fakcie. Hooki PostToolUse na Write/Edit uruchamiają formatter po każdej zmianie pliku. To, co wypisał model, nie ma znaczenia, bo formatter wszystko normalizuje. Takie hooki są idempotentne i można je bezpiecznie uruchamiać przy każdej edycji.
Gwarancje bezpieczeństwa zapobiegają niebezpiecznym akcjom, zanim te się wykonają. Hooki PreToolUse na Bash badają polecenia i blokują destrukcyjne wzorce kodem wyjścia 2. Muszą być szybkie (poniżej 500 ms), bo stoją przed każdym pasującym wywołaniem narzędzia, i muszą używać kodu 2 (a nie 1), ponieważ kod 1 tylko ostrzega, niczego nie blokując.
Gwarancje jakości sprawdzają stan w punktach decyzyjnych. Hooki PreToolUse na polecenia git commit uruchamiają linter albo zestaw testów i blokują commit, gdy kontrole jakości zawiodą. W odróżnieniu od hooków formatujących, które uruchamiają się przy każdej edycji, hooki jakości działają tylko w wybranych momentach, dzięki czemu narzut pozostaje niewielki.
Konceptualnym przodkiem są hooki Gita8: pre-commit, pre-push i post-commit pełnią te same trzy role. Hooki Claude Code rozszerzają ten wzorzec z operacji Gita na każdą akcję narzędziową agenta. Tę ewolucję rozkładam na części w every hook is a scar: każdy hook istnieje dlatego, że bez niego coś poszło nie tak.
Podstawy konfiguracji hooków
Hooki mieszkają w plikach ustawień:
- Na poziomie projektu:
.claude/settings.jsonw katalogu głównym repozytorium (współdzielony z zespołem)3 - Na poziomie użytkownika:
~/.claude/settings.json(prywatne hooki, stosowane globalnie)3
Struktura JSON:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/path/to/your/script.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "/path/to/another-script.sh"
}
]
}
]
}
}
Każdy wpis ma matcher filtrujący nazwy narzędzi, takie jak Bash, Write, Edit, Read, Glob, Grep czy Agent, oraz tablicę hooks z definicjami hooków. Semantyka matchera według dokumentacji: "*", "" lub pominięty matcher pasuje do wszystkiego; wartość złożona z liter, cyfr oraz znaków _, -, |, przecinków i spacji to dokładna nazwa albo lista, więc Write|Edit pasuje do obu narzędzi (podkreślnik ma znaczenie przy nazwach narzędzi MCP, takich jak mcp__github__search_code); wszystko inne traktowane jest jak niezakotwiczone wyrażenie regularne. Dopasowanie rozróżnia wielkość liter – bash nigdy nie pasuje do Bash. Każdy hook podaje type ("command" dla poleceń powłoki) oraz command do wykonania.
Zarejestrowane hooki można obejrzeć przeglądarką /hooks w trybie tylko do odczytu w trakcie sesji; aby dodać, zmienić lub usunąć hooka, należy edytować plik ustawień JSON bezpośrednio.5
Gdy hook się uruchamia, Claude Code przekazuje kontekst jako obiekt JSON na stdin: nazwę narzędzia, dane wejściowe narzędzia (w tym file_path przy operacjach na plikach) oraz metadane sesji.6 Skrypt czyta stdin – zwykle przy użyciu jq – i na tej podstawie podejmuje decyzje. Ustawianych jest też kilka zmiennych środowiskowych z kontekstem – $CLAUDE_PROJECT_DIR do rozwiązywania ścieżek, $CLAUDE_EFFORT dla bieżącego poziomu wysiłku – ale pola specyficzne dla narzędzia, jak ścieżka pliku, docierają wyłącznie przez stdin; nie istnieje żadna zmienna $FILE_PATH przypisana do narzędzia.
5 praktycznych hooków
Każdy hook poniżej rozwiązuje realny problem, na który natrafiłem, używając Claude Code jako podstawowego narzędzia pracy. Wszystkie przykłady korzystają z poprawnego, zagnieżdżonego schematu z dokumentacji hooków7.
1. Automatyczne formatowanie przy edycji pliku
Claude pisze funkcjonalnie poprawny kod, który co jakiś czas łamie reguły formatowania projektu. Najpierw dopisałem do swojego pliku CLAUDE.md „po edycji plików Pythona zawsze uruchamiaj black”, ale instrukcja działała tylko w około 80 % przypadków. Przy złożonej zmianie obejmującej wiele plików model potrafił pominąć krok formatowania. Hook PostToolUse usuwa tę niekonsekwencję całkowicie: formatter uruchamia się po każdym zapisie pliku, niezależnie od tego, co postanowił model.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'FILE=$(jq -r \".tool_input.file_path // empty\"); if [[ \"$FILE\" == *.py ]]; then black --quiet \"$FILE\" 2>/dev/null; elif [[ \"$FILE\" == *.js || \"$FILE\" == *.ts ]]; then npx prettier --write \"$FILE\" 2>/dev/null; fi'"
}
]
}
]
}
}
Hook czyta wejście JSON narzędzia ze stdin i wyciąga .tool_input.file_path przy użyciu jq – ten obiekt na stdin to jedyne miejsce, w którym istnieje ścieżka pliku, bo Claude Code nie ustawia zmiennych środowiskowych właściwych dla narzędzia. Następnie sprawdza rozszerzenie i uruchamia odpowiedni formatter: pliki Pythona dostają black, pliki JavaScriptu i TypeScriptu – prettier. Fragment 2>/dev/null wycisza zbędne komunikaty, dzięki czemu widoczne są tylko prawdziwe błędy.
W większych projektach warto przenieść polecenie zapisane w jednej linii do osobnego skryptu, dla czytelności.
2. Bramka bezpieczeństwa dla niebezpiecznych poleceń
Hooki PreToolUse na narzędziu Bash badają polecenie, które Claude zamierza wykonać, i blokują je, gdy pasuje do niebezpiecznego wzorca. Pierwszą wersję tego hooka napisałem po tym, jak Claude zrobił force-push na main w trakcie sesji refaktoryzacji. (Szersze konsekwencje autonomii agentów omawiam w anatomy of a claw oraz Claude Code as infrastructure). Model dostał polecenie „wypchnij zmiany” i zinterpretował je jako git push --force origin main, ponieważ gałąź się rozjechała. Naprawa zajęła sekundy; incydent stał się powodem do postawienia stałej bramki.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r \".tool_input.command\"); if echo \"$CMD\" | grep -qE \"rm\\s+-rf\\s+/|git\\s+push\\s+(-f|--force)\\s+(origin\\s+)?main|git\\s+reset\\s+--hard|DROP\\s+TABLE|:\\(\\)\\s*\\{\\s*:\"; then echo \"BLOCKED: Dangerous command detected: $CMD\" >&2; exit 2; fi'"
}
]
}
]
}
}
Gdy ten hook kończy się kodem 2, Claude Code anuluje oczekujące polecenie. Komunikat błędu trafia jednocześnie do terminala i do kontekstu Claude’a, więc model rozumie, dlaczego akcja się nie powiodła, i proponuje bezpieczniejszy wariant.
Blokowane wzorce:
- rm -rf / (rekurencyjne usuwanie od katalogu głównego)
- git push --force main oraz git push -f main (force-push na gałąź main)
- git reset --hard (zniszczenie zmian, które nie trafiły do commita)
- DROP TABLE (przypadkowe zniszczenie bazy danych)
- Fork bomby (wzorzec łapie początek :(){, obejmując warianty ze spacjami i bez nich)
Tę listę należy dopasować do własnego środowiska. Bazy produkcyjne wymagają destrukcyjnych wzorców SQL. Wdrożenia sterowane z CLI wymagają strażników na polecenia wdrożeniowe.
3. Uruchamianie testów po zmianach
Gdy Claude edytuje plik Pythona, odpowiednie testy powinny uruchomić się automatycznie. Natychmiastowe wykonanie testów wyłapuje regresje, zanim skumulują się przez trzy czy cztery kolejne edycje.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'FILE=$(jq -r \".tool_input.file_path // empty\"); if [[ \"$FILE\" == *.py && \"$FILE\" != *test_* ]]; then TEST_FILE=\"tests/test_$(basename \"$FILE\")\"; if [[ -f \"$TEST_FILE\" ]]; then if ! OUT=$(python -m pytest \"$TEST_FILE\" -x --tb=short 2>&1); then echo \"TESTS FAILED after editing $FILE:\" >&2; echo \"$OUT\" | tail -20 >&2; exit 2; fi; fi; fi'"
}
]
}
]
}
}
Hook wyciąga z JSON-a na stdin ścieżkę edytowanego pliku, sprawdza, czy to plik źródłowy Pythona (a nie sam plik testowy), szuka odpowiadającego pliku testowego zgodnie z konwencją nazw z przedrostkiem test_ i uruchamia go, jeśli taki istnieje. Flaga -x zatrzymuje się na pierwszym niepowodzeniu, a tail -20 utrzymuje zwięzłość wyniku. Użyteczność hooka bierze się z exit 2 przy niepowodzeniu: hook PostToolUse nie cofnie edycji, która już się wydarzyła, ale kod 2 podaje Claude’owi na stderr wynik nieudanych testów, a ten naprawia usterkę, zanim ruszy dalej. Wersja, która jedynie wypisuje niepowodzenie z kodem 0, wysyła je do dziennika debugowania, gdzie nikt nie zagląda.
Uwaga: powyższy hook zakłada płaski katalog tests/ i nazwy z przedrostkiem test_. W projektach odwzorowujących drzewo źródeł (np. tests/api/test_users.py odpowiadające src/api/users.py) linię TEST_FILE należy zastąpić poniższą:
TEST_FILE="tests/$(echo "$FILE" | sed 's|.*/src/||; s|\([^/]*\)\.py$|test_\1.py|')"
Hook uruchamiający testy okazuje się szczególnie cenny podczas sesji refaktoryzacji, w których Claude dotyka wielu plików. Bez natychmiastowej informacji zwrotnej błędy się kumulują: Claude edytuje plik A, psuje testy pliku B, a potem edytuje plik C na podstawie zepsutego stanu B. Zanim niepowodzenie zostanie zauważone, do naprawy są trzy pliki zamiast jednego. Uruchomienie testów po każdej edycji wyłapuje pierwsze pęknięcie od razu.
4. Powiadomienie, gdy Claude skończy
Długie tury Claude Code potrafią trwać minutami. Zamiast wpatrywać się w terminal, lepiej dostać powiadomienie, gdy Claude skończy odpowiadać. (Stop uruchamia się na końcu każdej odpowiedzi – hook na faktyczne zamknięcie sesji należy zarejestrować jako SessionEnd.)
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude finished responding\" with title \"Claude Code\"'"
}
]
}
]
}
}
Powyższy wariant dla macOS używa osascript do wywołania natywnego powiadomienia. W Linuksie linię z osascript należy zastąpić przez notify-send "Claude Code" "Finished responding". Do powiadomień na Slacku służy webhook:
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-type: application/json' \
-d '{"text": "Claude Code finished responding"}'
Wariantu ze Slackiem używam do zadań w tle uruchamianych przez & <task> (tryb działania w tle w Claude Code). Powiadomienie systemowe obsługuje sesje interaktywne.
5. Kontrola jakości przed commitem
Zanim Claude wykona git commit, warto sprawdzić, czy kod przechodzi lintera. Bramka lintera przed commitem wyłapuje to, czego samo formatowanie nie widzi: nieużywane importy, niezdefiniowane zmienne, błędy typów.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r \".tool_input.command\"); if echo \"$CMD\" | grep -qE \"^git\\s+commit\"; then if ! LINT_OUTPUT=$(ruff check . --select E,F,W 2>&1); then echo \"LINT FAILED -- fix before committing:\" >&2; echo \"$LINT_OUTPUT\" >&2; exit 2; fi; fi'"
}
]
}
]
}
}
Bramka jakości włącza się tylko wtedy, gdy polecenie Bash zaczyna się od git commit. Uruchamia ruff (szybki linter Pythona) z regułami błędów, pyflakes i ostrzeżeń. Gdy pojawią się zastrzeżenia, hook blokuje commit (kod 2), a Claude widzi wynik lintera, co zwykle skłania go do poprawienia problemów i ponowienia próby.
Kontrole jakości można nakładać warstwami: mypy do sprawdzania typów, bandit do skanowania bezpieczeństwa albo własne skrypty walidacyjne projektu. Hooki PreToolUse na polecenia Bash dają programowalną bramkę przed dowolną akcją w powłoce.
PreToolUse i PostToolUse w .claude/settings.json: kompendium
Jeśli ktoś szukał kształtu konfiguracji PreToolUse/PostToolUse i trafił tutaj, oto wersja zwięzła. Oba zdarzenia zagnieżdżają się pod kluczem hooks w .claude/settings.json (projekt) albo ~/.claude/settings.json (użytkownik); zakresy się łączą, a identyczne handlery są odsiewane. Jeden blok podłączający oba zdarzenia:3
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": ".claude/hooks/guard.sh" }]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{ "type": "command", "command": ".claude/hooks/format.sh" }]
}
]
}
}
Kontrakt w trzech zdaniach: oba zdarzenia dostarczają JSON narzędzia na stdin (.tool_input.command dla Bash, .tool_input.file_path dla Write/Edit – zmiennej środowiskowej właściwej dla narzędzia nie ma).6 PreToolUse uruchamia się przed wywołaniem narzędzia i może je zablokować kodem 2. PostToolUse uruchamia się po udanym wywołaniu – nie cofnie akcji, ale kod 2 zwraca Claude’owi treść ze stderr, dzięki czemu model naprawia to, co hook wytknął.2
Pełną dokumentację obu zdarzeń – pola wyjścia JSON, permissionDecision, updatedInput, limity czasu – zawiera oficjalna dokumentacja code.claude.com/docs/en/hooks; sekcja o hookach w moim przewodniku po Claude Code omawia to samo, z wzorcami sprawdzonymi w praktyce.
Zgadywanie nazwy zdarzenia? Oto mapowanie
Zdarzenia hooków, których ludzie szukają, wobec tego, co Claude Code faktycznie uruchamia:1
| Jeśli padło zgadywanie… | Prawdziwe zdarzenie |
|---|---|
onStart / onSessionStart |
SessionStart |
onFinish / onEnd / onStop |
Stop (uruchamia się, gdy Claude kończy każdą odpowiedź) albo SessionEnd (zamknięcie sesji) |
onToolUse / beforeToolUse |
PreToolUse |
afterToolUse |
PostToolUse |
onPrompt / onUserMessage |
UserPromptSubmit |
onError |
PostToolUseFailure (błędy narzędzi) albo StopFailure (błędy API) |
Zdarzeń jest w sumie trzydzieści jeden – tabela zdarzeń w przewodniku wymienia każde z nich.
Hooki PreToolUse, PostToolUse i Stop w jednej konfiguracji
Trzy najczęściej wyszukiwane zdarzenia, podłączone razem – strażnik poleceń, formatter i powiadomienie o zakończeniu:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": ".claude/hooks/guard-bash.sh" }]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{ "type": "command", "command": "bash -c 'FILE=$(jq -r \".tool_input.file_path // empty\"); [[ \"$FILE\" == *.py ]] && black --quiet \"$FILE\" || true'" }]
}
],
"Stop": [
{
"hooks": [{ "type": "command", "command": "osascript -e 'display notification \"Claude finished responding\" with title \"Claude Code\"'" }]
}
]
}
}
guard-bash.sh to bramka bezpieczeństwa z hooka 2 przeniesiona do osobnego skryptu: treść jednolinijkowca bash -c z hooka 2 (wszystko pomiędzy zewnętrznymi apostrofami) należy zapisać jako .claude/hooks/guard-bash.sh, dodać shebang #!/bin/bash i nadać prawa wykonywania poleceniem chmod +x – albo wziąć gotowy skrypt o tej samej nazwie z Claude Code Hooks Explained. Każde zdarzenie zachowuje własną semantykę: strażnik PreToolUse może zawetować polecenie (kod 2), formatter PostToolUse uruchamia się po każdej pasującej edycji, a hook Stop po każdym końcu odpowiedzi – matcher nie jest tu potrzebny: Stop nie jest zdarzeniem narzędziowym, więc nie ma czego filtrować.1
Wskazówki do debugowania hooków
Hooki zawodzą po cichu częściej, niż można się spodziewać. Pięć technik, których używam przy ich debugowaniu:
- Najpierw test skryptu osobno. Wystarczy ręcznie podać przykładowy JSON na wejście skryptu:
echo '{"tool_input":{"command":"git commit -m test"}}' | bash your-hook.sh. Jeśli zawodzi poza Claude Code, zawiedzie też wewnątrz. - Trzeba wiedzieć, dokąd naprawdę trafia stderr. Stderr dociera do kontekstu Claude’a tylko wtedy, gdy hook kończy się kodem 2; przy kodzie 0 ląduje w dzienniku debugowania, a przy innych kodach niezerowych w transkrypcie pojawia się jedynie informacja o błędzie hooka. Podczas prac warto uruchomić
claude --debug(albo/debugw trakcie sesji) i obserwować dziennik debugowania, w którym ląduje wyjście hooków kończących się kodem 0. - Uwaga na awarie jq. Przy błędnej ścieżce JSON
jq9 po cichu zwracanull, a warunki nigdy nie zadziałają. Wyrażeniajqwarto sprawdzić na prawdziwym wejściu narzędzia. - Weryfikacja kodów wyjścia. Kod 2 blokuje akcje. Kod 1 tylko ostrzega. Hook PreToolUse, który przez przypadek używa
exit 1, nie egzekwuje niczego, choć sprawia wrażenie działającego. Lepiej zacząć pozwalająco (domyślnie kod 0) i sięgać poexit 2wyłącznie przy konkretnych blokowanych wzorcach. - Hooki muszą być szybkie. Hooki działają synchronicznie. Hook zajmujący 5 sekund dokłada 5 sekund do każdego pasującego użycia narzędzia. Wszystkie swoje hooki trzymam poniżej 2 sekund, najlepiej poniżej 500 milisekund.
Najczęstszy błąd przy hookach: napisanie bramki bezpieczeństwa z exit 1 zamiast exit 2. Podczas testów hook sprawia wrażenie działającego, bo ostrzeżenie pojawia się w terminalu. Kod 1 jest jednak ostrzeżeniem nieblokującym. Niebezpieczne polecenie i tak się wykonuje. Widziałem ten błąd w konfiguracjach hooków trzech różnych zespołów, z których każdy był przekonany, że zablokował force-push. Każdy hook bezpieczeństwa należy przetestować, wyzwalając blokowany wzorzec i sprawdzając, czy akcja rzeczywiście została powstrzymana, a nie tylko opatrzona ostrzeżeniem.
Kolejne kroki
Te pięć hooków pokrywa podstawy: formatowanie, bezpieczeństwo, testy, powiadomienia i bramki jakości. Gdy te wzorce wejdą w krew, można budować hooki do wstrzykiwania kontekstu (dodawanie instrukcji projektowych na starcie sesji), strażników rekurencji (zapobieganie nieskończonym pętlom subagentów) i orkiestracji przepływów pracy (łączenie procesów wieloetapowych).
Architekturę hooków, pełny cykl życia z 31 zdarzeniami i zaawansowane wzorce opisuje sekcja o hookach w moim pełnym Claude Code guide, a przejście zdarzenie po zdarzeniu znajduje się w Claude Code Hooks Explained.
O historiach powstania moich 95 produkcyjnych hooków napisałem też w Claude Code Hooks: Why Each of My 95 Hooks Exists, gdzie opisuję incydenty, które doprowadziły do każdego z nich.
Źródła
FAQ
Czy hooki mogą zablokować wykonanie polecenia przez Claude Code?
Tak. Hooki PreToolUse blokują dowolną akcję narzędzia, kończąc się kodem 2. Claude Code anuluje oczekującą akcję i pokazuje modelowi treść ze stderr hooka. Kod 1 to nieblokujący błąd hooka, po którym akcja i tak dochodzi do skutku. Rozróżnienie kodów ma znaczenie: każdy hook bezpieczeństwa musi używać exit 2, a nie exit 1.2 Claude widzi powód odmowy i proponuje bezpieczniejszy wariant.
Gdzie umieścić pliki konfiguracji hooków?
Konfiguracje hooków trafiają do .claude/settings.json w przypadku hooków na poziomie projektu (zapisanych w repozytorium, współdzielonych z zespołem) albo do ~/.claude/settings.json w przypadku hooków na poziomie użytkownika (prywatnych, stosowanych w każdym projekcie). Gdy istnieją oba pliki, hooki łączą się, zamiast się nadpisywać: uruchamiany jest każdy pasujący hook z każdego zakresu, a identyczne handlery są odsiewane. Dla plików skryptów polecam ścieżki bezwzględne, żeby uniknąć kłopotów z katalogiem roboczym.
Czy hooki działają z subagentami?
Tak. Hooki uruchamiają się także dla akcji subagentów.4 Jeśli Claude uruchomi subagenta narzędziem Agent, hooki PreToolUse i PostToolUse wykonają się dla każdego narzędzia, którego ten subagent użyje. Bez rekurencyjnego egzekwowania subagent mógłby obejść bramki bezpieczeństwa. Zdarzenie SubagentStop pozwala uruchomić sprzątanie albo walidację w chwili, gdy subagent zakończy swoje zadanie.4
Ile hooków to za dużo?
Ograniczeniem jest wydajność, a nie liczba. Każdy hook działa synchronicznie, więc łączny czas wykonania dokłada się do każdego pasującego wywołania narzędzia. Mam 95 hooków rozłożonych między ustawienia użytkownika i projektu, bez zauważalnych opóźnień, bo każdy kończy się poniżej 200 ms. Próg, który obserwuję: jeśli hook PostToolUse dokłada ponad 500 ms do każdej edycji pliku, sesja zaczyna się wlec. Hooki warto zmierzyć poleceniem time przed wdrożeniem. Dziesięć szybkich hooków bije dwa wolne.
-
Anthropic, “Hooks reference — Hook events.” code.claude.com/docs/en/hooks#hook-events ↩↩↩↩↩
-
Anthropic, “Hooks reference — Exit code output.” code.claude.com/docs/en/hooks#exit-code-output ↩↩↩↩↩
-
Anthropic, “Hooks reference — Configuration.” code.claude.com/docs/en/hooks#configuration ↩↩↩↩
-
Anthropic, “Hooks reference — Hook events” (SubagentStart/SubagentStop). code.claude.com/docs/en/hooks#hook-events ↩↩↩
-
Anthropic, “Hooks reference — Configuration” (menu
/hooks). code.claude.com/docs/en/hooks#configuration ↩ -
Anthropic, “Hooks reference — Hook input and output.” code.claude.com/docs/en/hooks#hook-input-and-output ↩↩
-
Anthropic, “Hooks reference — Configuration” (zagnieżdżony schemat hooka). code.claude.com/docs/en/hooks#configuration ↩
-
Dokumentacja Gita, “Customizing Git: Git Hooks.” git-scm.com/book/en/v2/Customizing-Git-Git-Hooks ↩
-
Podręcznik jq, “Command-line JSON processor.” jqlang.github.io/jq/manual ↩