← Wszystkie wpisy

Hooki Claude Code od podstaw: deterministyczna warstwa wokół agenta

Z przewodnika: Claude Code Comprehensive Guide

Czym są hooki Claude Code? Hooki to zdefiniowane przez użytkownika polecenia powłoki (a także endpointy HTTP, narzędzia MCP i prompty modelu), które Claude Code wykonuje automatycznie w ustalonych punktach swojego cyklu życia: przed wywołaniem narzędzia, po edycji, na starcie sesji, gdy Claude kończy odpowiadać.1 Podczas gdy CLAUDE.md przekazuje modelowi instrukcje, których ten prawdopodobnie będzie przestrzegał, 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 jest do niego podpięte. {.answer-block}

Większość programistów korzysta z Claude Code z dwiema warstwami kontroli: uprawnieniami, które ograniczają, co agent może zrobić, oraz CLAUDE.md, który opisuje, co agent powinien zrobić. Hooki są trzecią warstwą — i jedyną, która cokolwiek gwarantuje. Poniżej: model myślowy, każde zdarzenie cyklu życia z aktualnej dokumentacji, dokładny kontrakt wejścia/wyjścia, konfiguracja, pięć działających wzorców oraz framework decyzyjny. Każdy szczegół API został zweryfikowany względem oficjalnej dokumentacji referencyjnej i przewodnika po hookach według stanu na 1 lipca 2026 — ten system rozwija się szybko, więc tam, gdzie ten wpis i dokumentacja referencyjna się różnią, wygrywa dokumentacja. (Pierwsze kroki z Claude Code? Warto zacząć od 5-minutowej konfiguracji lub ścieżki dla początkujących.)

TL;DR: Hooki otrzymują JSON na stdin i odpowiadają kodami wyjścia lub JSON-em na stdout. Kod wyjścia 0 zezwala, kod 2 blokuje (przy zdarzeniach, które potrafią blokować), a kod 1 — konwencjonalny uniksowy kod błędu — nie blokuje niczego, co stanowi największą pułapkę hooków.2 Konfiguruje się je w settings.json pod nazwami zdarzeń takimi jak PreToolUse i Stop, z filtrowaniem przez matchery. Hooków należy używać do wszystkiego, co musi wydarzyć się zawsze; CLAUDE.md — do wszystkiego, o czym model powinien jedynie wiedzieć.

Model myślowy: gwarancje wokół niedeterministycznego rdzenia

Agent kodujący to system probabilistyczny. Poproszony o uruchamianie Prettiera po każdej edycji, będzie to robił — przez większość czasu. Może pominąć ten krok, gdy zmiana wygląda na trywialną, gdy kontekst się wydłuża albo gdy sformułowanie prośby wybrzmi inaczej. CLAUDE.md, skille i prompty to wyłącznie sugestie: wysokiej jakości, zwykle respektowane, nigdy gwarantowane.

Hooki są deterministyczną otoczką wokół tego rdzenia. Oficjalna definicja: „zdefiniowane przez użytkownika polecenia powłoki, endpointy HTTP lub prompty LLM, które wykonują się automatycznie w określonych punktach cyklu życia Claude Code”, zapewniające „deterministyczną kontrolę nad zachowaniem Claude Code i gwarancję, że określone działania zawsze się wydarzą, zamiast polegać na tym, że LLM sam zdecyduje się je wykonać”.3 Formater odpala się przy każdej edycji. Strażnik poleceń ocenia każde wywołanie Bash. Bramka ukończenia sprawdza każde zakończenie pracy.

Egzekwowanie jest realne, nie kosmetyczne: hooki PreToolUse odpalają się przed jakąkolwiek kontrolą trybu uprawnień, więc hook zwracający permissionDecision: "deny" blokuje narzędzie nawet w trybie bypassPermissions czy pod --dangerously-skip-permissions. W drugą stronę to nie działa — hook zwracający "allow" nie może poluzować reguł deny 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

Według stanu na 1 lipca 2026 dokumentacja referencyjna opisuje 30 zdarzeń hooków.1 Dzielą się na trzy rytmy: raz na sesję (SessionStart, SessionEnd), raz na turę (UserPromptSubmit, Stop, StopFailure) i przy każdym wywołaniu narzędzia wewnątrz pętli agentowej (PreToolUse, PostToolUse). Pozostałe odpalają się w konkretnych sytuacjach — zmiany konfiguracji, kompaktowanie, subagenci, interakcje MCP.

Zdarzenie Kiedy się odpala Jedno realne zastosowanie
SessionStart Sesja rozpoczyna się lub zostaje wznowiona Wstrzyknięcie gałęzi git i otwartych issue jako kontekstu
Setup --init-only albo --init/--maintenance w trybie -p Instalacja zależności w CI, zanim agent wystartuje
UserPromptSubmit Prompt zostaje wysłany, zanim Claude go przetworzy Dopisanie bieżącej daty; odrzucanie promptów zawierających sekrety
UserPromptExpansion Wpisane polecenie rozwija się w prompt Audyt lub weto dla rozwinięć skilli i poleceń
PreToolUse Przed wykonaniem wywołania narzędzia Blokowanie destrukcyjnych poleceń powłoki
PermissionRequest Pojawia się okno dialogowe uprawnień Automatyczne zatwierdzanie zaufanych poleceń bez pytania użytkownika
PermissionDenied Klasyfikator trybu automatycznego odmawia wywołania narzędzia 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 Logowanie nieudanych poleceń do późniejszej analizy
PostToolBatch Po paczce równoległych wywołań narzędzi, przed kolejnym wywołaniem modelu Punkt kontrolny lub zatrzymanie pętli agentowej
Notification Claude Code wysyła powiadomienie Alert na pulpicie, gdy Claude czeka na dane wejściowe
MessageDisplay Podczas wyświetlania tekstu wiadomości asystenta Redagowanie treści na ekranie (tylko wyświetlanie; transkrypt bez zmian)
SubagentStart Uruchamiany jest subagent Wstrzyknięcie kontekstu zależnego od typu agenta
SubagentStop Subagent kończy pracę Walidacja wyniku subagenta, zanim zostanie zwrócony
TaskCreated Zadanie tworzone jest przez TaskCreate Egzekwowanie reguł nazewnictwa lub zakresu zadań
TaskCompleted Zadanie zostaje oznaczone jako ukończone Weryfikacja kryteriów akceptacji, zanim ukończenie się utrwali
Stop Claude kończy odpowiadać Bramka ukończenia: blokada zakończenia, dopóki testy nie przejdą
StopFailure Tura kończy się błędem API Alert przy rate_limit lub billing_error (tylko logowanie; wynik ignorowany)
TeammateIdle Członek zespołu agentów ma przejść w stan bezczynności Utrzymywanie członków zespołu w pracy nad kolejką
InstructionsLoaded Plik CLAUDE.md lub .claude/rules/*.md ładuje się do kontekstu Logowanie, które instrukcje weszły do sesji
ConfigChange Plik konfiguracyjny zmienia się w trakcie sesji Blokowanie nieautoryzowanych edycji ustawień
CwdChanged Zmienia się katalog roboczy Przeładowanie środowisk w stylu direnv
FileChanged Obserwowany plik zmienia się na dysku Odświeżenie zmiennych środowiskowych, gdy zmienia się .env
WorktreeCreate Worktree tworzony jest przez --worktree lub isolation: "worktree" Zastąpienie domyślnego tworzenia git worktree własnym
WorktreeRemove Worktree zostaje usunięty Własne sprzątanie przy zakończeniu 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 użytkownika o dane Automatyczne wypełnianie formularzy w przebiegach headless
ElicitationResult Po odpowiedzi na elicytację MCP Walidacja lub nadpisanie odpowiedzi, zanim zostanie zwrócona
SessionEnd Sesja się kończy Archiwizacja logów, sprzątanie zasobów

Większości z nich nie będzie się potrzebować. Niemal każda produkcyjna konfiguracja opiera się na pięciu: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart i Stop. Reszta istnieje na dzień, w którym okaże się potrzebna.

Kontrakt: JSON na wejściu, kody wyjścia lub JSON na wyjściu

Hooki typu command otrzymują JSON na stdin i odpowiadają przez kody wyjścia, stdout i stderr. (Hooki HTTP otrzymują 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 i hook_event_name, z permission_mode przy większości zdarzeń — plus pola specyficzne dla 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), Stop niesie stop_hook_active i last_assistant_message. Hooki odpalane wewnątrz subagentów otrzymują dodatkowo agent_id i agent_type.2

Kody wyjścia

Trzy możliwe wyniki:2

  • Kod wyjścia 0 — sukces. Claude Code parsuje stdout w poszukiwaniu pól wyjściowych JSON. Przy większości zdarzeń stdout trafia wyłącznie do logu debugowania; przy UserPromptSubmit, UserPromptExpansion i SessionStart zwykły stdout jest dodawany jako kontekst widoczny dla Claude’a.
  • Kod wyjścia 2 — błąd blokujący. Stdout (wraz z ewentualnym JSON-em) jest ignorowany; stderr trafia z powrotem do Claude’a jako komunikat błędu. Co oznacza „blokada”, zależy od zdarzenia.
  • Każdy inny kod wyjścia — błąd nieblokujący. W transkrypcie pojawia się notka <hook name> hook error, a wykonanie toczy się dalej.

Ostatnia pozycja zasługuje na pogrubienie: kod wyjścia 1 niczego nie blokuje. Dokumentacja ostrzega przed tym wprost — Claude Code traktuje kod 1 jako błąd nieblokujący i idzie dalej, mimo że 1 to konwencjonalny uniksowy kod błędu. Hooki egzekwujące politykę muszą kończyć się przez exit 2.2

Co robi kod wyjścia 2, według zdarzenia:2

Zdarzenie Efekt kodu wyjścia 2
PreToolUse Blokuje wywołanie narzędzia
PermissionRequest Odmawia uprawnienia
UserPromptSubmit Blokuje przetwarzanie i kasuje prompt
UserPromptExpansion Blokuje rozwinięcie
Stop / SubagentStop Uniemożliwia zatrzymanie; konwersacja trwa dalej
TeammateIdle Uniemożliwia członkowi zespołu przejście w bezczynność
TaskCreated / TaskCompleted Wycofuje utworzenie / uniemożliwia ukończenie
ConfigChange Blokuje zmianę konfiguracji (z wyjątkiem policy_settings)
PreCompact Blokuje kompaktowanie
PostToolBatch Zatrzymuje pętlę agentową przed kolejnym wywołaniem modelu
Elicitation / ElicitationResult Odmawia elicytacji / zamienia odpowiedź w odmowę
WorktreeCreate Każdy niezerowy kod wyjścia przerywa tworzenie worktree

Cała reszta 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; StopFailure, InstructionsLoaded, MessageDisplay i PermissionDenied ignorują kod wyjścia — przy PermissionDenied jedyną dźwignią jest JSON-owe retry: true.2

Wyjście JSON

Gdy potrzebna jest kontrola subtelniejsza niż „blokuj albo milcz”, należy zakończyć proces kodem 0 i wypisać obiekt JSON na stdout. Jedna zasada na wstępie: kody wyjścia albo JSON, nigdy jedno i drugie — JSON jest przetwarzany wyłącznie przy kodzie 0, a kod 2 go odrzuca.5

Pola uniwersalne działają przy każdym zdarzeniu: continue: false zatrzymuje Claude’a całkowicie (ze stopReason pokazywanym użytkownikowi), suppressOutput ukrywa stdout przed transkryptem, systemMessage pokazuje użytkownikowi ostrzeżenie, a terminalSequence emituje dozwoloną sekwencję sterującą terminala (powiadomienie na pulpicie, tytuł okna, dzwonek). Pola decyzyjne są 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", plus permissionDecisionReason i updatedInput do przepisania argumentów narzędzia przed wykonaniem
PermissionRequest hookSpecificOutput decision.behavior: "allow" | "deny", opcjonalnie decision.updatedInput
PermissionDenied hookSpecificOutput retry: true informuje model, że może spróbować ponownie
PostToolUse hookSpecificOutput updatedToolOutput zastępuje wynik narzędzia
Stop / SubagentStop hookSpecificOutput additionalContext: informacja zwrotna niebędąca błędem, która kontynuuje konwersację, nie licząc się jako błąd hooka
SessionStart, Setup, SubagentStart Tylko kontekst additionalContext, plus dostępne wyłącznie dla SessionStart initialUserMessage, sessionTitle, watchPaths, reloadSkills. Bez blokowania
MessageDisplay hookSpecificOutput displayContent zastępuje wyłącznie tekst na ekranie
Elicitation / ElicitationResult hookSpecificOutput action: "accept" | "decline" | "cancel", plus content
WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged Brak Wyłącznie efekty uboczne

Dwa szczegóły, które potrafią zaboleć. Po pierwsze, PreToolUse jest wyjątkiem od wzorca decision na najwyższym poziomie: historycznie używał decision/reason na najwyższym poziomie, ale dla tego zdarzenia są one przestarzałe ("approve"/"block" mapują się na "allow"/"deny"); należy używać hookSpecificOutput.permissionDecision.5 Po drugie, gdy kilka hooków PreToolUse się nie zgadza, pierwszeństwo wygląda tak: deny > defer > ask > allow.5

Konfiguracja: settings.json, matchery, zasięg

Konfiguracja hooków zagnieżdża się na trzech poziomach: wybiera się zdarzenie, dodaje grupę matchera filtrującą, kiedy hook się odpala, i definiuje jeden lub więcej handlerów hooka do uruchomienia.6

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "/path/to/lint-check.sh" }
        ]
      }
    ]
  }
}

Miejsce umieszczenia tej konfiguracji określa zasięg: ~/.claude/settings.json obowiązuje we wszystkich projektach, .claude/settings.json jest przypisany do projektu i nadaje się do commitowania, .claude/settings.local.json jest przypisany do projektu i ignorowany przez git, a do tego stosuje się standardowe pierwszeństwo ustawień — polityka zarządzana nad ustawieniami lokalnymi, te nad projektowymi, te nad użytkownika.9 Hooki mogą też być dostarczane w pluginach (hooks/hooks.json) oraz we frontmatterze skilli i agentów, a administratorzy enterprise mogą wymuszać hooki zarządzane, których użytkownicy nie mogą nadpisać.6

Matchery są oceniane po zawartych w nich znakach: "*", "" lub pominięty matcher dopasowuje wszystko; wartość zawierająca wyłącznie litery, cyfry, _, -, spacje, przecinki i | to dokładny ciąg lub lista (Bash, Edit|Write); wszystko inne staje się niezakotwiczonym wyrażeniem regularnym JavaScript, więc Edit.* dopasowuje zarówno Edit, jak i NotebookEdit — gdy chodzi o dokładnie jedno narzędzie, należy zakotwiczyć wzorzec: ^Edit$. Matchery rozróżniają wielkość liter, a każde zdarzenie dopasowuje po własnym polu: nazwie narzędzia dla zdarzeń narzędziowych, source dla SessionStart, typie agenta dla SubagentStart, typie powiadomienia dla Notification.6 Do ostrzejszego filtrowania zdarzeń narzędziowych służy pole if na poziomie handlera, przyjmujące jedną regułę uprawnień, np. "Bash(git *)" — działa jednak na zasadzie best-effort (przy nieparsowalnych poleceniach przepuszcza), więc dla twardych gwarancji należy używać reguł uprawnień, nie if.6

Handlery występują w pięciu typach: command (powłoka), http (endpoint POST), mcp_tool, prompt (jednorazowa ocena przez model) i 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 dla UserPromptSubmit i 10 dla MessageDisplay), 30 dla prompt, 60 dla agent — nadpisywane per hook polem timeout.6 Wszystkie pasujące hooki działają równolegle, z deduplikacją identycznych handlerów, a $CLAUDE_PROJECT_DIR wskazuje skryptom katalog główny projektu.

Weryfikację zapewnia /hooks: przeglądarka tylko do odczytu pokazująca każde zdarzenie, skonfigurowane dla niego hooki i plik ustawień, z którego każdy z nich pochodzi. Aby cokolwiek zmienić, należy edytować JSON (lub poprosić o to Claude’a). Aby tymczasowo wyłączyć wszystko, ustawia się "disableAllHooks": true.6

Pięć wzorców

Wersje uogólnione i minimalne. Tutorial hooków buduje pełniejsze, produkcyjne warianty kilku z nich, a Hooki w programowaniu dla platform Apple stosuje je do toolchaina iOS.

1. Automatyczne formatowanie po edycjach (PostToolUse)

Prosto z oficjalnego przewodnika — każdy plik, którego Claude dotknie, 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 podmienić na ruff format, gofmt czy swiftformat, zależnie od wymagań stosu technologicznego.

2. Blokowanie niebezpiecznych poleceń (PreToolUse, exit 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 wyjścia 2 blokuje wywołanie i przekazuje stderr z powrotem do Claude’a, który koryguje kurs, zamiast ślepo ponawiać próbę. Odpowiednik w JSON-ie — permissionDecision: "deny" z uzasadnieniem — robi to samo, zostawiając przestrzeń na rozwój w stronę "ask" (eskalacja do człowieka) lub updatedInput (przepisanie polecenia).5

3. Wstrzykiwanie kontekstu na starcie sesji (SessionStart)

Zwykły stdout z hooka SessionStart staje się kontekstem widocznym dla Claude’a — JSON nie jest wymagany: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

Ten wzorzec służy do stanu dynamicznego. Statyczne konwencje należą do CLAUDE.md, który sama dokumentacja rekomenduje dla kontekstu niewymagającego skryptu.1

4. Bramka ukończenia na Stop

Stop odpala się, gdy Claude kończy odpowiadać. 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 twardo ogranicza hook Stop do 8 kolejnych blokad, a bramka, która nigdy nie sprawdza, czy sama już wywołała kontynuację, przepali je wszystkie.7 Do łagodniejszego sterowania służy zwrócenie hookSpecificOutput.additionalContext zamiast decision: "block" — ta sama kontynuacja, ale oznaczona jako informacja zwrotna, a nie błąd hooka. A do warunków jednorazowych wbudowane polecenie /goal jest promptowym hookiem Stop o zasięgu sesji, bez żadnej 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: rejestracja jednego dyspozytora na zdarzenie i routing według konwencji.

#!/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 strażnika to od tej pory chmod +x na nowym pliku w .claude/hooks/PreToolUse/ — settings.json nigdy się nie zmienia, każdy skrypt pozostaje na tyle mały, że da się go testować w izolacji, a pierwszy kod wyjścia 2 się propaguje. Jedno zastrzeżenie: dyspozytor serializuje to, co Claude Code uruchomiłby równolegle, i najlepiej pasuje do hooków opartych na kodach wyjścia — hook emitujący JSON powinien pozostać samodzielny, ponieważ stdout musi zawierać dokładnie jeden obiekt JSON.5

Hook kontra CLAUDE.md kontra skill kontra 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, przywoływana wtedy, gdy jest istotna — to skill
Pamięć Przywoływanie Jeśli to fakt poznany w jednej sesji, a potrzebny przyszłym sesjom — to pamięć

Tryb awarii działa w obu kierunkach. Kodowanie konwencji jako hooków daje kruche skrypty egzekwujące rzeczy, z którymi jedno zdanie wskazówek radzi sobie doskonale. Kodowanie polityki jako prozy w CLAUDE.md daje agenta, który zrobi force-push na main dokładnie tego jednego dnia, kiedy ma to znaczenie. Test: jaki jest koszt, gdy model raz to zignoruje? Irytacja → CLAUDE.md. Incydent → hook.

Czego hooki nie potrafią

Uczciwa lista ograniczeń, wszystkie z oficjalnej dokumentacji:7

  • Hooki nie mogą wywoływać narzędzi ani poleceń slash. Hooki typu command mówią przez stdout, stderr i kody wyjścia — nic więcej. Kontekst zwracany przez additionalContext jest wstrzykiwany jako zwykły tekst.
  • PostToolUse nie potrafi cofnąć. Narzędzie już się wykonało. Prewencja mieszka w PreToolUse.
  • Stop odpala 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 tego StopFailure). Logika bramki musi tolerować zatrzymania w połowie zadania.
  • PermissionRequest nie odpala się w trybie headless (-p). Do zautomatyzowanych decyzji o uprawnieniach służy PreToolUse.
  • PreToolUse nie widzi plików wskazanych przez @. Pliki wciągane przez @ w promptcie nie wiążą się z żadnym wywołaniem narzędzia; do ochrony ścieżek na tej drodze służą reguły deny dla Read.1
  • Równoległe updatedInput jest niedeterministyczne. Gdy kilka hooków PreToolUse przepisuje argumenty tego samego narzędzia, wygrywa ten, który skończy ostatni. Każde przepisywanie powinno mieć jednego właściciela.
  • Przekroczenie limitu czasu anuluje hook. Domyślnie 600 sekund dla hooków typu command (30 dla UserPromptSubmit, 10 dla MessageDisplay); powolna bramka, która przekroczy limit, to bramka, która się nie wykonała.
  • Wyjście jest ograniczone do 10 000 znaków — nadmiar jest zapisywany do pliku i zastępowany podglądem.
  • Hooki działają z pełnymi uprawnieniami użytkownika. Ostrzeżenie z samej dokumentacji referencyjnej: mogą „modyfikować, usuwać lub odczytywać dowolne pliki, do których ma dostęp konto użytkownika. Wszystkie polecenia hooków należy przejrzeć i przetestować przed dodaniem ich do konfiguracji”.8 Warto cytować zmienne, używać ścieżek bezwzględnych i omijać wrażliwe pliki.
  • Zepsuty hook degraduje każdą sesję, dopóki nie zostanie naprawiony. Debugować można widokiem transkryptu (Ctrl+O), przez claude --debug-file /tmp/claude.log lub /debug w trakcie sesji; klasyczna pułapka to profil powłoki, który wypisuje coś na starcie i psuje JSON-owe wyjście hooka.7

FAQ

Czym są hooki Claude Code?

Hooki to zdefiniowane przez użytkownika polecenia — skrypty powłoki, endpointy HTTP, narzędzia MCP lub prompty modelu — które Claude Code wykonuje automatycznie w określonych punktach cyklu życia.3 Otrzymują JSON zdarzenia na stdin i odpowiadają kodami wyjścia lub JSON-em: blokują wywołanie narzędzia, wstrzykują kontekst, przepisują argumenty, utrzymują agenta w pracy. W odróżnieniu od instrukcji w CLAUDE.md wykonują się za każdym razem, niezależnie od zachowania modelu.

Czym różnią się hooki PreToolUse od uprawnień?

Reguły uprawnień są deklaratywne: statyczne wzorce allow/deny/ask, które Claude Code ocenia samodzielnie. Hooki PreToolUse są programowalne: własny kod bada pełne wejście narzędzia i podejmuje decyzję. Hooki odpalają się przed kontrolami trybu uprawnień, więc "deny" z hooka obowiązuje nawet w trybie bypassPermissions — ale "allow" z hooka nie może nadpisać reguły deny z ustawień.4 Reguł uprawnień należy używać do wszystkiego, co da się wyrazić wzorcem; po hook sięga się wtedy, gdy decyzja wymaga logiki, zewnętrznego stanu lub przepisania wejścia.

Czy hooki działają w trybie headless (-p)?

Tak — z jednym udokumentowanym wyjątkiem: hooki PermissionRequest nie odpalają się w trybie nieinteraktywnym, więc zautomatyzowane decyzje o uprawnieniach należą do PreToolUse.7 Tryb headless odblokowuje też jedną opcję, którą sesje interaktywne ignorują: permissionDecision: "defer", która wstrzymuje wywołanie narzędzia, aby proces opakowujący (aplikacja Agent SDK, własny interfejs) mógł zebrać dane wejściowe i później wznowić sesję.5

Dlaczego hook się wykonuje, ale niczego nie blokuje?

Niemal zawsze to naruszenie kontraktu. Kod wyjścia 1 nie blokuje — blokuje wyłącznie kod 2, i to tylko przy zdarzeniach wspierających blokowanie.2 Decyzje JSON są parsowane wyłącznie przy kodzie 0 — skrypt, który wypisuje {"decision": "block"}, a potem kończy się kodem 2, ma swój JSON odrzucony. Do tego matchery rozróżniają wielkość liter — bash nigdy nie dopasuje Bash. Rejestrację potwierdza się przez /hooks, a testuje, przepuszczając przykładowy JSON przez skrypt i sprawdzając echo $?.7

Źródła

Zweryfikowano względem oficjalnej dokumentacji 1 lipca 2026. API hooków zmieniało się istotnie w kolejnych wydaniach Claude Code v2.1.x (nowe zdarzenia, nowe pola, semantyka matcherów), więc szczegóły wrażliwe na wersję należy traktować jako „według stanu na tę datę”.

Powiązane na tej stronie: sekcja o hookach w przewodniku po Claude Code z widokiem całego systemu, w tym hooków typu prompt i agent, tutorial hooków z pięcioma produkcyjnymi konstrukcjami i kompletnymi konfiguracjami, Hooki w programowaniu dla platform Apple ze stosowanymi wzorcami dla iOS oraz quickstart, jeśli Claude Code nie został jeszcze zainstalowany.


  1. Anthropic, “Hooks reference — Hook lifecycle and hook events.” code.claude.com/docs/en/hooks#hook-events 

  2. 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 

  3. Anthropic, “Automate actions with hooks.” code.claude.com/docs/en/hooks-guide 

  4. Anthropic, “Hooks guide — Hooks and permission modes.” code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes 

  5. Anthropic, “Hooks reference — JSON output and decision control.” code.claude.com/docs/en/hooks#json-output 

  6. Anthropic, “Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, the /hooks menu.” code.claude.com/docs/en/hooks#configuration 

  7. Anthropic, “Hooks guide — Limitations and troubleshooting.” code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting 

  8. Anthropic, “Hooks reference — Security considerations.” code.claude.com/docs/en/hooks#security-considerations 

  9. Anthropic, “Claude Code settings.” code.claude.com/docs/en/settings 

Powiązane artykuły

Codex CLI vs Claude Code 2026: architektura, cennik i dostęp z Chin

Porównanie Codex CLI i Claude Code: sandboxing w jądrze vs 26 hooków, benchmarki Opus 4.7 vs GPT-5.4, cennik za token w …

25 min czytania

Hooki Claude Code: dlaczego istnieje każdy z moich 95 hooków

Zbudowałem 95 hooków dla Claude Code. Każdy istnieje, bo coś poszło nie tak. Oto historie ich powstania i architektura, …

8 min czytania