Hooki w Claude Code wyjaśnione: deterministyczna warstwa wokół agenta
Czym są hooki w Claude Code? Hooki to zdefiniowane przez użytkownika polecenia powłoki (a także endpointy HTTP, narzędzia MCP i prompty do modelu), które Claude Code wykonuje automatycznie w ustalonych punktach swojego cyklu życia: przed wywołaniem narzędzia, po edycji, na starcie sesji oraz wtedy, gdy Claude kończy odpowiedź.1 Podczas gdy CLAUDE.md daje modelowi instrukcje, których zapewne posłucha, hooki wykonują się niezależnie od tego, czy model współpracuje. Wystarczy wpisać /hooks w dowolnej sesji, aby zobaczyć każde zdarzenie cyklu życia i to, co zostało do niego podpięte.
{.answer-block}
Większość programistów korzysta z Claude Code z dwiema warstwami kontroli: uprawnieniami, które decydują, co agentowi wolno, oraz plikiem CLAUDE.md, który opisuje, co powinien robić. Hooki są trzecią warstwą i jedyną, która cokolwiek gwarantuje. Poniżej: model mentalny, wszystkie zdarzenia cyklu życia obecne w aktualnej dokumentacji, dokładny kontrakt wejścia i wyjścia, konfiguracja, pięć działających wzorców oraz ramy decyzyjne. Każdy szczegół API został zweryfikowany 8 sierpnia 2026 roku względem oficjalnej referencji hooków i przewodnika — ten system zmienia się szybko, więc tam, gdzie ten wpis i referencja się rozjeżdżają, rację ma referencja. (Pierwszy raz z Claude Code? Warto zacząć od instalacji w 5 minut lub od ścieżki startowej).
W skrócie: hooki odbierają JSON na stdin i odpowiadają kodami wyjścia albo JSON-em na stdout. Kod 0 zezwala, kod 2 blokuje (w zdarzeniach, które potrafią blokować), a kod 1 — konwencjonalny uniksowy kod błędu — nie blokuje niczego, i jest to największa pułapka całego mechanizmu.2 Konfiguruje się je w pliku settings.json pod nazwami zdarzeń takimi jak PreToolUse czy Stop, filtrowanych przez matchery. Hooków należy używać do wszystkiego, co musi się zdarzyć zawsze; CLAUDE.md — do wszystkiego, o czym model ma jedynie wiedzieć.
Model mentalny: gwarancje wokół niedeterministycznego rdzenia
Agent programistyczny to system probabilistyczny. Można poprosić go o uruchomienie Prettier po każdej edycji, a on to zrobi — najczęściej. Może pominąć ten krok, gdy zmiana wygląda trywialnie, gdy kontekst się wydłuża albo gdy sformułowanie zabrzmi trochę inaczej. CLAUDE.md, skille i prompty są sugestiami: dobrej jakości, zwykle respektowanymi, nigdy gwarantowanymi.
Hooki są deterministyczną powłoką wokół tego rdzenia. Przewodnik otwiera jednozdaniowa definicja – „Hooki to zdefiniowane przez użytkownika polecenia powłoki.” – i od razu stawia sprawę jasno: hooki dają „deterministyczną kontrolę: pewne działania zawsze się wydarzą, zamiast polegać na tym, że LLM zdecyduje się je uruchomić”.3 (Ta jedna linijka z przewodnika zaniża dzisiejszy zakres; pełniejsza definicja z referencji dokłada już endpointy HTTP i prompty do LLM, a handlery występują również jako narzędzia MCP – o czym niżej, w sekcji o konfiguracji). Formater uruchamia się przy każdej edycji. Blokada poleceń ocenia każde wywołanie Bash. Bramka końcowa sprawdza każde zakończenie.
Egzekwowanie jest realne, nie kosmetyczne: hooki PreToolUse uruchamiają się przed jakąkolwiek kontrolą trybu uprawnień, więc hook zwracający permissionDecision: "deny" blokuje narzędzie nawet w trybie bypassPermissions czy pod flagą --dangerously-skip-permissions. Odwrotność nie zachodzi — hook zwracający "allow" nie poluzuje reguł odmowy z ustawień. Hooki mogą zaostrzyć politykę ponad to, na co pozwalają uprawnienia, ale nigdy jej nie osłabią.4
Cykl życia: wszystkie zdarzenia hooków
Na 8 sierpnia 2026 roku referencja opisuje 31 zdarzeń hooków.1 Dzielą się na trzy rytmy: raz na sesję (SessionStart, SessionEnd), raz na turę (UserPromptSubmit, Stop, StopFailure) oraz przy każdym wywołaniu narzędzia wewnątrz pętli agentowej (PreToolUse, PostToolUse). Reszta odpala się w konkretnych okolicznościach — przy zmianach konfiguracji, kompaktowaniu, subagentach i interakcjach MCP.
| Zdarzenie | Kiedy się odpala | Jedno realne zastosowanie |
|---|---|---|
SessionStart |
Sesja się zaczyna lub wznawia | Wstrzyknięcie gałęzi git i otwartych zgłoszeń jako kontekstu |
Setup |
--init-only albo --init/--maintenance w trybie -p |
Instalacja zależności w CI, zanim ruszy agent |
UserPromptSubmit |
Prompt zostaje wysłany, zanim Claude go przetworzy | Doklejenie bieżącej daty; odrzucanie promptów z sekretami |
UserPromptExpansion |
Wpisane polecenie rozwija się w prompt | Audyt lub weto wobec rozwinięć skilli i poleceń |
PreToolUse |
Przed wykonaniem wywołania narzędzia | Blokowanie destrukcyjnych poleceń powłoki |
PermissionRequest |
Pojawia się okno z prośbą o uprawnienie | Automatyczna zgoda na zaufane polecenia, bez pytania |
PermissionDenied |
Klasyfikator trybu automatycznego odmawia wywołania | Zwrócenie retry: true, aby model mógł spróbować ponownie |
PostToolUse |
Po udanym wywołaniu narzędzia | Automatyczne formatowanie każdego edytowanego pliku |
PostToolUseFailure |
Po nieudanym wywołaniu narzędzia | Rejestrowanie nieudanych poleceń do dalszej analizy |
PostToolBatch |
Po paczce równoległych wywołań, przed kolejnym wywołaniem modelu | Zapis punktu kontrolnego albo zatrzymanie pętli agentowej |
Notification |
Claude Code wysyła powiadomienie | Alert na pulpicie, gdy Claude czeka na odpowiedź |
MessageDisplay |
Podczas wyświetlania tekstu wiadomości asystenta | Zamazywanie na ekranie (tylko widok; transkrypcja bez zmian) |
SubagentStart |
Zostaje uruchomiony subagent | Wstrzyknięcie kontekstu właściwego dla typu agenta |
SubagentStop |
Subagent kończy pracę | Walidacja wyniku subagenta, zanim zostanie zwrócony |
TaskCreated |
Zadanie powstaje przez TaskCreate |
Egzekwowanie reguł nazewnictwa lub zakresu zadań |
TaskCompleted |
Zadanie zostaje oznaczone jako ukończone | Sprawdzenie kryteriów odbioru, zanim zamknięcie się utrwali |
Stop |
Claude kończy odpowiedź | Bramka końcowa: brak zakończenia, dopóki testy nie przechodzą |
StopFailure |
Tura kończy się błędem API | Alert przy rate_limit lub billing_error (tylko log; wyjście ignorowane) |
TeammateIdle |
Członek zespołu agentów zaraz przejdzie w bezczynność | Utrzymanie pracy zespołu dzięki kolejce zadań |
InstructionsLoaded |
Plik CLAUDE.md lub .claude/rules/*.md trafia do kontekstu |
Rejestrowanie, które instrukcje weszły do sesji |
ConfigChange |
Plik konfiguracyjny zmienia się w trakcie sesji | Blokowanie nieautoryzowanych zmian w ustawieniach |
CwdChanged |
Zmienia się katalog roboczy | Przeładowanie środowisk w stylu direnv |
DirectoryAdded |
Katalog roboczy zostaje zarejestrowany w trakcie sesji przez /add-dir lub przez register_repo_root z SDK (od v2.1.219) |
Wczytanie kontekstu tego repozytorium w chwili, gdy dołącza do sesji |
FileChanged |
Obserwowany plik zmienia się na dysku | Odświeżenie zmiennych środowiskowych po zmianie .env |
WorktreeCreate |
Worktree powstaje przez --worktree lub isolation: "worktree" |
Zastąpienie domyślnego tworzenia worktree w git |
WorktreeRemove |
Worktree zostaje usunięty | Własne sprzątanie przy końcu sesji lub subagenta |
PreCompact |
Przed kompaktowaniem kontekstu | Zapis stanu, którego nie wolno stracić |
PostCompact |
Po zakończeniu kompaktowania | Ponowne wstrzyknięcie krytycznego kontekstu |
Elicitation |
Serwer MCP prosi o dane od użytkownika | Automatyczne wypełnianie formularzy w przebiegach headless |
ElicitationResult |
Po udzieleniu odpowiedzi na prośbę MCP | Walidacja lub nadpisanie odpowiedzi, zanim zostanie zwrócona |
SessionEnd |
Sesja się kończy | Archiwizacja logów, zwolnienie zasobów |
Większość z nich nigdy nie będzie potrzebna. Niemal każda produkcyjna konfiguracja opiera się na pięciu: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart i Stop. Reszta czeka na dzień, w którym okaże się niezbędna.
Kontrakt: JSON na wejściu, kody wyjścia albo JSON na wyjściu
Hooki typu polecenie odbierają JSON na stdin i odpowiadają przez kody wyjścia, stdout i stderr. (Hooki HTTP dostają ten sam JSON jako treść żądania POST i odpowiadają treścią odpowiedzi).2
Każde zdarzenie dostarcza wspólną kopertę — session_id, transcript_path, cwd oraz hook_event_name, a w większości zdarzeń również permission_mode — plus pola właściwe dla danego zdarzenia. Hook PreToolUse dla polecenia Bash otrzymuje:
{
"session_id": "abc123",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "npm test" }
}
Inne zdarzenia podmieniają końcówkę: UserPromptSubmit niesie prompt, SessionStart niesie source (startup/resume/clear/compact/fork – piąta wartość pojawiła się wraz z rozwidlonymi sesjami w v2.1.214, a hook dopasowujący source, przepisany ze starszej listy czterech wartości, po cichu przegapi forki), Stop niesie stop_hook_active i last_assistant_message. Hooki odpalane wewnątrz subagentów dostają dodatkowo agent_id i agent_type.2
Kody wyjścia
Trzy możliwe wyniki:2
- Kod 0 — sukces. Claude Code przegląda stdout w poszukiwaniu pól wyjściowych w JSON-ie. W większości zdarzeń stdout trafia wyłącznie do logu diagnostycznego; przy
UserPromptSubmit,UserPromptExpansioniSessionStartzwykły stdout zostaje dołączony jako kontekst widoczny dla Claude’a. - Kod 2 — błąd blokujący. Stdout (razem z ewentualnym JSON-em) jest ignorowany, a stderr wraca do Claude’a jako komunikat błędu. Co dokładnie znaczy „zablokować”, zależy od zdarzenia.
- Każdy inny kod wyjścia — błąd nieblokujący. W transkrypcji pojawia się informacja
<hook name> hook error, a wykonanie toczy się dalej.
Ostatnia linijka zasługuje na pogrubienie: kod 1 nie blokuje niczego. Dokumentacja ostrzega o tym wprost — Claude Code traktuje kod 1 jako błąd nieblokujący i idzie dalej, choć 1 jest konwencjonalnym uniksowym kodem niepowodzenia. Hooki egzekwujące politykę muszą kończyć się exit 2.2
Co robi kod 2 w poszczególnych zdarzeniach:2
| Zdarzenie | Efekt kodu 2 |
|---|---|
PreToolUse |
Blokuje wywołanie narzędzia |
PermissionRequest |
Odmawia uprawnienia |
UserPromptSubmit |
Blokuje przetwarzanie i kasuje prompt |
UserPromptExpansion |
Blokuje rozwinięcie |
Stop / SubagentStop |
Zapobiega zatrzymaniu; rozmowa toczy się dalej |
TeammateIdle |
Nie pozwala członkowi zespołu przejść w bezczynność |
TaskCreated / TaskCompleted |
Wycofuje utworzenie / uniemożliwia zamknięcie |
ConfigChange |
Blokuje zmianę konfiguracji (poza policy_settings) |
PreCompact |
Blokuje kompaktowanie |
PostToolBatch |
Zatrzymuje pętlę agentową przed kolejnym wywołaniem modelu |
Elicitation / ElicitationResult |
Odrzuca prośbę / zamienia odpowiedź w odmowę |
WorktreeCreate |
Każdy niezerowy kod przerywa tworzenie worktree |
Wszystko pozostałe nie potrafi blokować. PostToolUse i PostToolUseFailure pokazują stderr Claude’owi (narzędzie już się wykonało); SessionStart, Notification, SessionEnd, CwdChanged, FileChanged, PostCompact, SubagentStart i Setup pokazują stderr wyłącznie użytkownikowi, natomiast DirectoryAdded kieruje stderr jedynie do logu diagnostycznego; StopFailure, InstructionsLoaded, MessageDisplay i PermissionDenied ignorują kod wyjścia — a w przypadku PermissionDenied jedyną dźwignią jest pole JSON retry: true.2
Wyjście w formacie JSON
Aby uzyskać kontrolę subtelniejszą niż blokada albo milczenie, należy zakończyć kodem 0 i wypisać obiekt JSON na stdout. Jedna zasada na wstępie: albo kody wyjścia, albo JSON, nigdy oba naraz — JSON jest przetwarzany wyłącznie przy kodzie 0, a kod 2 go odrzuca.5
Pola uniwersalne działają w każdym zdarzeniu: continue: false zatrzymuje Claude’a całkowicie (z stopReason pokazanym użytkownikowi), suppressOutput ukrywa stdout w transkrypcji, systemMessage wyświetla użytkownikowi ostrzeżenie, a terminalSequence emituje dopuszczoną sekwencję sterującą terminala (powiadomienie na pulpicie, tytuł okna, dzwonek). Pola decyzyjne są natomiast zależne od zdarzenia:5
| Zdarzenia | Wzorzec decyzji | Kluczowe pola |
|---|---|---|
UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact |
decision na najwyższym poziomie |
decision: "block" + reason (pokazywany Claude’owi). Pominięcie decision oznacza zgodę |
PreToolUse |
hookSpecificOutput |
permissionDecision: "allow" | "deny" | "ask" | "defer", a do tego permissionDecisionReason i updatedInput do przepisania argumentów narzędzia przed wykonaniem |
PermissionRequest |
hookSpecificOutput |
decision.behavior: "allow" | "deny", opcjonalnie decision.updatedInput |
PermissionDenied |
hookSpecificOutput |
retry: true mówi modelowi, że może spróbować ponownie |
PostToolUse |
hookSpecificOutput |
updatedToolOutput zastępuje wynik narzędzia |
Stop / SubagentStop |
hookSpecificOutput |
additionalContext: informacja zwrotna bez charakteru błędu, która przedłuża rozmowę, nie licząc się jako błąd hooka |
SessionStart, Setup, SubagentStart |
Wyłącznie kontekst | additionalContext, a także zarezerwowane dla SessionStart initialUserMessage, sessionTitle, watchPaths, reloadSkills. Bez blokowania |
MessageDisplay |
hookSpecificOutput |
displayContent zastępuje jedynie tekst na ekranie |
Elicitation / ElicitationResult |
hookSpecificOutput |
action: "accept" | "decline" | "cancel", a do tego content |
TeammateIdle, TaskCreated, TaskCompleted |
Uniwersalne continue |
continue: false + stopReason zatrzymuje całkowicie przepływ członka zespołu lub zadania (kod 2 to blokada właściwa dla zdarzenia) |
WorktreeCreate |
Zwrócenie ścieżki | Hooki typu polecenie wypisują ścieżkę worktree na stdout; hooki HTTP zwracają hookSpecificOutput.worktreePath; niepowodzenie lub brak ścieżki przerywa tworzenie |
WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, DirectoryAdded, FileChanged |
Brak | Wyłącznie efekty uboczne |
Dwa szczegóły, na których łatwo się przejechać. Po pierwsze, PreToolUse jest wyjątkiem od wzorca z decision na najwyższym poziomie: historycznie używał tam decision/reason, ale dla tego zdarzenia oba pola są przestarzałe ("approve"/"block" odpowiadają "allow"/"deny"); należy korzystać z hookSpecificOutput.permissionDecision.5 Po drugie, gdy kilka hooków PreToolUse orzeka sprzecznie, obowiązuje kolejność deny > defer > ask > allow – wygrywa odpowiedź najbardziej restrykcyjna. Mimo to lepiej przypisać każdą decyzję jednemu odpowiedzialnemu hookowi, niż opierać się na tym rozstrzygnięciu.5
Konfiguracja: settings.json, matchery, zasięg
Konfiguracja hooków zagnieżdża się na trzech poziomach: wybiera się zdarzenie, dodaje grupę matcherów filtrującą moment odpalenia i definiuje jeden lub kilka handlerów hooka do uruchomienia.6
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "/path/to/lint-check.sh" }
]
}
]
}
}
Miejsce zapisu decyduje o zasięgu: ~/.claude/settings.json obowiązuje we wszystkich projektach, .claude/settings.json jest związany z projektem i nadaje się do commitowania, .claude/settings.local.json jest związany z projektem i pomijany przez git, a obowiązuje standardowa kolejność pierwszeństwa ustawień — polityka zarządzana przed lokalnymi, te przed projektowymi, a projektowe przed użytkownika.9 Hooki mogą też przyjeżdżać w pluginach (hooks/hooks.json) oraz we frontmatterze skilla albo agenta, a administratorzy firmowi mogą wymusić hooki zarządzane, których użytkownik nie nadpisze.6
Matchery są oceniane po znakach, z których się składają: "*", "" albo pominięty matcher pasują do wszystkiego; wartość zawierająca wyłącznie litery, cyfry, _, -, spacje, przecinki i | jest dokładnym ciągiem lub listą (Bash, Edit|Write); wszystko inne staje się niezakotwiczonym wyrażeniem regularnym JavaScriptu, przez co Edit.* pasuje zarówno do Edit, jak i do NotebookEdit — przy jednym konkretnym narzędziu trzeba zakotwiczyć wzorzec jako ^Edit$. Matchery rozróżniają wielkość liter, a każde zdarzenie dopasowuje się po własnym polu: nazwa narzędzia przy zdarzeniach narzędziowych, source przy SessionStart, typ agenta przy SubagentStart, typ powiadomienia przy Notification.6 Do ostrzejszego filtrowania zdarzeń narzędziowych pole if każdego handlera przyjmuje jedną regułę uprawnień w rodzaju "Bash(git *)" — działa ono jednak w miarę możliwości (przy nieparsowalnym poleceniu przepuszcza), więc twarde gwarancje należy budować na regułach uprawnień, a nie na if.6 Warto znać jedną zmianę semantyki: od v2.1.214 jednosegmentowy wzorzec ścieżki w if (na przykład Edit(src/**)) pasuje wyłącznie do katalogu src na najwyższym poziomie katalogu roboczego; if napisany przed tym wydaniem po cichu przestaje pasować do ścieżek zagnieżdżonych, takich jak packages/app/src/ – dawne zachowanie na dowolnej głębokości daje zapis Edit(**/src/**).6
Handlery występują w pięciu typach: command (powłoka), http (endpoint POST), mcp_tool, prompt (jednoturowa ocena modelu) oraz agent (subagent z dostępem do Read/Grep/Glob; eksperymentalny). Domyślne limity czasu: 600 sekund dla command/http/mcp_tool (obniżone do 30 przy UserPromptSubmit i do 10 przy MessageDisplay), 30 dla prompt, 60 dla agent — z możliwością nadpisania w każdym hooku przez timeout.6 Wszystkie pasujące hooki działają równolegle, z deduplikacją identycznych handlerów, a $CLAUDE_PROJECT_DIR kieruje skrypty do katalogu głównego projektu.
Do weryfikacji służy /hooks: przeglądarka tylko do odczytu pokazująca każde zdarzenie, przypisane mu hooki i plik ustawień, z którego każdy pochodzi. Zmiany wprowadza się, edytując JSON (albo prosząc o to Claude’a). Aby tymczasowo wyłączyć wszystko, wystarczy ustawić "disableAllHooks": true.6
Pięć wzorców
Uogólnione i minimalne. Samouczek o hookach rozbudowuje kilka z nich do pełnych wersji produkcyjnych, a Hooki w rozwoju oprogramowania Apple przenosi je na łańcuch narzędzi iOS.
1. Automatyczne formatowanie po edycjach (PostToolUse)
Prosto z oficjalnego przewodnika — każdy plik dotknięty przez Claude’a zostaje sformatowany, bez wyjątków:3
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}
Polecenie można wymienić na ruff format, gofmt albo swiftformat, zależnie od wymagań stosu technologicznego.
2. Blokowanie niebezpiecznych poleceń (PreToolUse, kod 2)
#!/bin/bash
# .claude/hooks/guard-bash.sh — register on PreToolUse, matcher "Bash"
command=$(jq -r '.tool_input.command // empty')
case "$command" in
*"rm -rf"* | *"git push --force"* | *"DROP TABLE"*)
echo "Blocked: matches a destructive pattern. Propose a safer alternative." >&2
exit 2 ;;
esac
exit 0
Kod 2 blokuje wywołanie i podaje stderr z powrotem Claude’owi, który koryguje kurs, zamiast ponawiać próbę na ślepo. Odpowiednik w JSON-ie — permissionDecision: "deny" wraz z uzasadnieniem — robi to samo, a przy tym zostawia miejsce na "ask" (eskalację do człowieka) albo updatedInput (przepisanie polecenia).5
3. Wstrzykiwanie kontekstu na starcie sesji (SessionStart)
Zwykły stdout z hooka SessionStart staje się kontekstem widocznym dla Claude’a — bez żadnego JSON-a:1
#!/bin/bash
# .claude/hooks/session-context.sh — register on SessionStart
echo "Current branch: $(git branch --show-current)"
echo "Recent commits:"
git log --oneline -5
echo "Uncommitted files: $(git status --porcelain | wc -l | tr -d ' ')"
exit 0
Nadaje się to do stanu dynamicznego. Statyczne konwencje należą do CLAUDE.md, co sama dokumentacja zaleca dla kontekstu niewymagającego skryptu.1
4. Bramka końcowa na Stop
Stop odpala się, gdy Claude kończy odpowiedź. Zablokowanie go zmusza agenta do dalszej pracy, dopóki warunek nie zostanie spełniony:
#!/bin/bash
# .claude/hooks/stop-gate.sh — register on Stop
input=$(cat)
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # already continuing because of this hook; don't loop forever
fi
if ! npm test --silent > /tmp/stop-gate.log 2>&1; then
jq -n '{decision: "block", reason: "Tests are failing. Fix them before finishing. Log: /tmp/stop-gate.log"}'
fi
exit 0
Sprawdzenie stop_hook_active ma znaczenie: Claude Code domyślnie ogranicza hooka Stop do 8 kolejnych blokad (limit podnosi CLAUDE_CODE_STOP_HOOK_BLOCK_CAP), a bramka, która nigdy nie sprawdza, czy sama wywołała kontynuację, przepali je jedna po drugiej.7 Do łagodniejszego sterowania lepiej zwrócić hookSpecificOutput.additionalContext zamiast decision: "block" — ta sama kontynuacja, lecz jako oznaczona informacja zwrotna, a nie błąd hooka. Przy warunkach jednorazowych wbudowane polecenie /goal jest promptowym hookiem Stop o zasięgu sesji, zupełnie bez konfiguracji.1
5. Dyspozytor: jeden punkt wejścia, wiele małych hooków
Zarejestrowanie dziesięciu hooków oznacza dziesięć wpisów w settings.json, które rozjeżdżają się między maszynami i projektami. Alternatywa: zarejestrować jednego dyspozytora na zdarzenie i routować zgodnie z konwencją.
#!/bin/bash
# .claude/hooks/dispatch.sh — register once per event you care about
input=$(cat)
event=$(echo "$input" | jq -r '.hook_event_name')
dir="$CLAUDE_PROJECT_DIR/.claude/hooks/$event"
[ -d "$dir" ] || exit 0
for hook in "$dir"/*.sh; do
[ -x "$hook" ] || continue
echo "$input" | "$hook" || exit $?
done
exit 0
Dodanie blokady sprowadza się teraz do chmod +x na nowym pliku w .claude/hooks/PreToolUse/ — settings.json nigdy się nie zmienia, każdy skrypt pozostaje dość mały, by testować go osobno, a pierwszy kod 2 propaguje się dalej. Jedno zastrzeżenie: dyspozytor szereguje to, co Claude Code uruchomiłby równolegle, i najlepiej pasuje do hooków opartych na kodach wyjścia — hooka emitującego JSON warto zostawić samodzielnym, ponieważ stdout musi zawierać dokładnie jeden obiekt JSON.5
Hook, CLAUDE.md, skill czy pamięć
Cztery mechanizmy, cztery zadania:
| Mechanizm | Zadanie | Reguła wyboru |
|---|---|---|
| Hook | Egzekwowanie | Jeśli pominięcie musi być niemożliwe — formatowanie, bezpieczeństwo, bramki — to hook |
| CLAUDE.md | Wskazówki | Jeśli to konwencja, którą model powinien znać w każdej sesji — stos, styl, polecenia — to CLAUDE.md |
| Skill | Zdolność | Jeśli to procedura z własnymi instrukcjami i skryptami, wywoływana wtedy, gdy jest potrzebna, to skill |
| Pamięć | Przypomnienie | Jeśli to fakt poznany w jednej sesji, którego potrzebują sesje przyszłe, to pamięć |
Tryb awarii działa w obie strony. Zakodowanie konwencji jako hooków daje kruche skrypty wymuszające coś, z czym jedno zdanie wskazówki radzi sobie bez trudu. Zakodowanie polityki jako prozy w CLAUDE.md daje agenta, który robi force push do main dokładnie tego dnia, kiedy to ma znaczenie. Test brzmi: ile kosztuje jednorazowe zignorowanie tego przez model? Irytacja → CLAUDE.md. Incydent → hook.
Czego hooki nie potrafią
Uczciwe ograniczenia, wszystkie z oficjalnej dokumentacji:7
- Hooki nie mogą wywoływać narzędzi ani poleceń ze slashem. Hooki typu polecenie mówią przez stdout, stderr i kody wyjścia — nic więcej. Kontekst zwrócony przez
additionalContextzostaje wstrzyknięty jako czysty tekst. PostToolUseniczego nie cofnie. Narzędzie już się wykonało. Zapobieganie mieszka wPreToolUse.Stopodpala się przy każdym końcu odpowiedzi, nie tylko przy „zadanie ukończone”, i nigdy przy przerwaniu przez użytkownika (błędy API odpalają zamiast tegoStopFailure). Logika bramki musi znosić zatrzymania w połowie zadania.PermissionRequestnie odpala się w zwykłych przebiegach headless (-p). Odpala się natomiast pod-p, gdy prośbę dostarcza callbackcanUseToolz Agent SDK, a także przy wywołaniach narzędzi przez subagenty działające w tle; do całej reszty automatyzacji służyPreToolUse.PreToolUsenie widzi plików wskazanych przez@. Pliki wciągnięte przez@w promptcie nie wiążą się z żadnym wywołaniem narzędzia; ścieżki na tej drodze chronią reguły odmowy dlaRead.1- Równoległy
updatedInputjest z założenia niepewny. Gdy kilka hooków PreToolUse przepisuje argumenty tego samego narzędzia, przetrwa tylko jedno przepisanie i nie da się wskazać które. Za każde przepisanie powinien odpowiadać jeden hook. - Limity czasu anulują hooka. Domyślnie 600 sekund dla hooków typu polecenie (30 przy
UserPromptSubmit, 10 przyMessageDisplay); powolna bramka, która przekroczy limit, jest bramką, która się nie wykonała. - Wyjście jest ograniczone do 10 000 znaków — nadmiar trafia do pliku i zostaje zastąpiony podglądem.
- Hooki działają z pełnymi uprawnieniami użytkownika. Ostrzeżenie z samej referencji: „mogą modyfikować, usuwać lub odczytywać dowolne pliki dostępne dla konta użytkownika. Przejrzyj i przetestuj wszystkie polecenia hooków, zanim dodasz je do konfiguracji”.8 Zmienne należy brać w cudzysłowy, używać ścieżek bezwzględnych i omijać pliki wrażliwe.
- Zepsuty hook pogarsza każdą sesję, dopóki nie zostanie naprawiony. Diagnozę ułatwiają widok transkrypcji (
Ctrl+O),claude --debug-file /tmp/claude.logoraz/debugw trakcie sesji; klasyczną pułapką jest profil powłoki, który coś wypisuje przy starcie i psuje wyjście JSON hooka.7
Często zadawane pytania
Czym są hooki w Claude Code?
Hooki to zdefiniowane przez użytkownika polecenia — skrypty powłoki, endpointy HTTP, narzędzia MCP albo prompty do modelu — które Claude Code wykonuje automatycznie w określonych punktach cyklu życia.3 Odbierają JSON zdarzenia na stdin i odpowiadają kodami wyjścia albo JSON-em: zablokowaniem wywołania narzędzia, wstrzyknięciem kontekstu, przepisaniem argumentów, utrzymaniem agenta przy pracy. W przeciwieństwie do instrukcji z CLAUDE.md wykonują się za każdym razem, niezależnie od zachowania modelu.
Czym hooki PreToolUse różnią się od uprawnień?
Reguły uprawnień są deklaratywne: to statyczne wzorce zgody, odmowy i pytania, które Claude Code sam ocenia. Hooki PreToolUse są natomiast programowalne: własny kod ogląda całe wejście narzędzia i podejmuje decyzję. Hooki odpalają się przed kontrolą trybu uprawnień, więc "deny" z hooka utrzymuje się nawet w trybie bypassPermissions — ale "allow" z hooka nie przebije reguły odmowy z ustawień.4 Reguł uprawnień warto używać do wszystkiego, co da się wyrazić wzorcem; po hooka trzeba sięgnąć, gdy decyzja wymaga logiki, stanu zewnętrznego albo przepisania wejścia.
Czy hooki działają w trybie headless (-p)?
Tak — z jednym niuansem: hooki PermissionRequest pomijają zwykłe przebiegi -p (nic nie dostarcza tam prośby o uprawnienie), choć odpalają się, gdy dostarcza ją callback canUseTool z Agent SDK, a także przy wywołaniach narzędzi przez subagenty w tle. Automatyczne decyzje o uprawnieniach dla zwykłych przebiegów headless należą do PreToolUse.7 Tryb headless odblokowuje dodatkowo opcję, którą sesje interaktywne ignorują: permissionDecision: "defer", wstrzymującą wywołanie narzędzia, aby proces nadrzędny (aplikacja na Agent SDK, własny interfejs) mógł zebrać dane i wznowić sesję później.5
Dlaczego mój hook się uruchamia, ale niczego nie blokuje?
Prawie zawsze jest to złamanie kontraktu. Kod 1 nie blokuje — robi to wyłącznie kod 2 i tylko w zdarzeniach, które blokowanie obsługują.2 Decyzje w JSON-ie są parsowane jedynie przy kodzie 0 — skrypt, który wypisze {"decision": "block"}, a potem zakończy się kodem 2, traci swój JSON. Matchery rozróżniają też wielkość liter — bash nigdy nie dopasuje się do Bash. Rejestrację potwierdza /hooks, a test polega na przepuszczeniu przykładowego JSON-a przez skrypt i sprawdzeniu echo $?.7
Źródła
Zweryfikowano względem oficjalnej dokumentacji 8 sierpnia 2026 roku. API hooków zmieniło się istotnie na przestrzeni wydań Claude Code v2.1.x (nowe zdarzenia, nowe pola, nowa semantyka matcherów), dlatego szczegóły zależne od wersji należy traktować jako aktualne właśnie na tę datę.
Powiązane na tej stronie: sekcja o hookach w przewodniku po Claude Code dla spojrzenia na cały system, łącznie z hookami typu prompt i agent, samouczek o hookach dla pięciu produkcyjnych realizacji z pełnymi konfiguracjami, Hooki w rozwoju oprogramowania Apple dla zastosowanych wzorców iOS oraz szybki start, jeśli Claude Code nie został jeszcze zainstalowany.
-
Anthropic, „Hooks reference — Hook lifecycle and hook events”. code.claude.com/docs/en/hooks#hook-events ↩↩↩↩↩↩
-
Anthropic, „Hooks reference — Hook input and output; exit code output; exit code 2 behavior per event”. code.claude.com/docs/en/hooks#exit-code-output ↩↩↩↩↩↩↩↩
-
Anthropic, „Automate actions with hooks”. code.claude.com/docs/en/hooks-guide ↩↩↩
-
Anthropic, „Hooks guide — Hooks and permission modes”. code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes ↩↩
-
Anthropic, „Hooks reference — JSON output and decision control”. code.claude.com/docs/en/hooks#json-output ↩↩↩↩↩↩↩
-
Anthropic, „Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, menu /hooks”. code.claude.com/docs/en/hooks#configuration ↩↩↩↩↩↩↩
-
Anthropic, „Hooks guide — Limitations and troubleshooting”. code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting ↩↩↩↩↩
-
Anthropic, „Hooks reference — Security considerations”. code.claude.com/docs/en/hooks#security-considerations ↩
-
Anthropic, „Claude Code settings”. code.claude.com/docs/en/settings ↩