Hooki w Claude Code: konfiguracja i 7 przykładów
MarkdownKonfiguracja hooków w .claude/settings.json: ochrona .env, formatowanie, blokada force-push i inne przykłady. Typy obsługi i zdarzenia.
Przeczytaj cały artykuł
Hooki w Claude Code to polecenia powłoki, wywołania HTTP lub krótkie kontrole LLM, które uruchamiają się automatycznie przed działaniem Claude’a lub po nim. Dodają kontrole określonych zdarzeń, formatują kod po edycji i wstawiają kontekst na starcie. Dopasowany PreToolUse może odrzucić wywołanie; przykłady regex to pomocnicze kontrole, nie granica bezpieczeństwa każdej ścieżki plików lub Git. Poniżej przedstawiam 4 typy obsługi, 31 zdarzeń cyklu życia, test „kiedy reguła powinna znaleźć się w hooku”, 7 gotowych do skopiowania hooków do .claude/settings.json oraz 3 pułapki, na które sam natrafiłem.
Dokumentacja Ailoot: API, klucze i konfiguracja.
Czym są hooki w Claude Code?
Najważniejsze informacje
Hook to polecenie powłoki, wywołanie HTTP, krótki prompt LLM lub subagent, który Claude Code uruchamia w odpowiedzi na konkretne zdarzenie cyklu życia. W przeciwieństwie do instrukcji w CLAUDE.md, które model może zignorować lub zapomnieć, hook za każdym razem działa tak samo: to kod, a nie sugestia.
Przez pół roku trzymałem w CLAUDE.md regułę „nigdy nie edytuj .env bezpośrednio”. Claude łamał ją raz w miesiącu. Nie ze złośliwości; po prostu zapominał o niej pod koniec długiej sesji. Raz w .env pojawił się dodatkowy token, innym razem zmienna o zduplikowanej nazwie nadpisała tę używaną.
Przeniosłem tę regułę do hooka, dzięki czemu nie da się o niej zapomnieć. Gdy dopasowane bezpośrednie narzędzie edycji podaje ścieżkę .env, hook odrzuca wywołanie. Przekierowania Bash, skrypty, MCP i inne ścieżki wymagają osobnych kontroli; przykład nie czyni pliku niezmiennym.
Hooki to zdefiniowane przez ciebie polecenia powłoki, punkty końcowe HTTP lub prompty LLM, które uruchamiają się automatycznie w określonych momentach cyklu życia Claude Code.
Innymi słowy, hook to twoje polecenie, które Claude Code uruchamia po wystąpieniu określonego zdarzenia. Każda edycja pliku, uruchomienie Bash, rozpoczęcie sesji lub zakończenie pracy subagenta to zdarzenie, do którego możesz przypisać hook.
Dlaczego ma to znaczenie dla vibe coderów:
- Chroń pliki zawierające sekrety przed przypadkowymi zmianami.
- Automatycznie uruchamiaj prettier/eslint po każdym wygenerowaniu kodu.
- Blokuj
git push --forcedo main bez wyraźnej zgody. - Wstawiaj świeży kontekst na początku każdej sesji.
- Rejestruj działania Claude’a na potrzeby audytu.
CLAUDE.md odpowiada za kontekst. Hooki odpowiadają za działania. To dwie różne rzeczy, a ich mylenie jest najczęstszym błędem początkujących.
Czym hooki różnią się od CLAUDE.md i skills?
Najważniejsze informacje
CLAUDE.md to tekst, który model czyta na początku sesji i stara się stosować. Hook to polecenie powłoki uruchamiane przez proces Claude Code w reakcji na zdarzenie, niezależnie od modelu. Skill to nazwana paczka instrukcji, którą Claude może wywołać za pomocą narzędzia Skill, podobnie jak funkcję. Każde z tych rozwiązań służy do innego rodzaju zadań.
Dokumentacja Anthropic wyraźnie wyjaśnia różnicę między CLAUDE.md a hookami:
Treść CLAUDE.md jest dostarczana jako wiadomość użytkownika po promptcie systemowym, a nie jako część promptu systemowego. Claude ją czyta i stara się stosować, ale nie ma gwarancji ścisłego przestrzegania.
Innymi słowy, treść CLAUDE.md trafia do Claude’a jako pierwsza wiadomość od użytkownika, a nie jako instrukcja systemowa. Model ją przeczyta i spróbuje się do niej zastosować. Nie ma jednak żadnych gwarancji, zwłaszcza pod koniec długiej sesji, gdy kontekst się rozrasta, a starsze instrukcje wypadają z niego.
Hook działa inaczej. Jest zapisany w settings.json, nie zależy od modelu i jest uruchamiany przez proces Claude Code w reakcji na konkretne zdarzenie: PreToolUse, PostToolUse, SessionStart i inne. Polecenie uruchamia się, zanim Claude otrzyma wynik, i może fizycznie zablokować działanie (exit 2) lub zmodyfikować plik po edycji (exit 0).
Porównanie:
| Cecha | CLAUDE.md | Hook | Skill |
|---|---|---|---|
| Gdzie się znajduje | CLAUDE.md w katalogu głównym projektu | .claude/settings.json | Katalog .claude/skills/ |
| Kto go uruchamia | Model odczytuje go jako część wiadomości użytkownika | Proces Claude Code w reakcji na zdarzenie | Model za pomocą narzędzia Skill |
| Gwarancja wykonania | Nie (najlepsze starania) | Skonfigurowany dopasowany hook polecenia wykonuje kod; awarie i kody wyjścia zależą od zdarzenia | Przez wywołanie modelu (z uprawnieniem, ale nie na poziomie powłoki) |
| Do czego służy | Kontekst projektu, ton, styl, kierowanie | Ograniczenia, formatowanie, kontrole konkretnych plików | Złożone procedury wieloetapowe z parametrami |
| Kiedy się uruchamia | Przy każdej wiadomości modelu w sesji | Przy zdarzeniu cyklu życia | Gdy wywoła go model |
| Czy może zablokować działanie? | Nie | Tak (exit 2) | Nie |
Pełny szablon CLAUDE.md i 6 zasad utrzymywania tego pliku znajdziesz w poradniku Jak skonfigurować CLAUDE.md w 2026 roku: gotowy szablon i 6 zasad. O skills jako nazwanych możliwościach przeczytasz w Claude Code Skills: jak zbudować własną bibliotekę.
Hook działa na najniższym poziomie: bez modelu, bez interpretacji, tylko powłoka.
Hooki to jedno z narzędzi inżynierii kontekstu, obok Plan Mode i /compact.
4 typy hooków: Command, HTTP, Prompt, Agent — który wybrać?
Najważniejsze informacje
Claude Code ma 4 typy obsługi. Command to skrypt powłoki i najczęściej używany typ. HTTP wysyła żądanie POST na twój URL; sprawdza się w zespołach i integracjach. Prompt uruchamia krótkie wywołanie Haiku w celu semantycznej kontroli. Agent tworzy subagenta z dostępem do narzędzi, który przeprowadza dogłębną walidację. Początkującym wystarczą Command i Prompt; obsługują 90% zadań.
| Typ | Co robi | Kiedy go wybrać | Złożoność |
|---|---|---|---|
| Command | Uruchamia polecenie powłoki, odbiera JSON na stdin i odpowiada kodem wyjścia oraz stdout | 90% przypadków: sprawdzanie ścieżek, formatowanie, grep treści, lsof, operacje git | niska |
| HTTP | Wysyła na twój URL żądanie POST z treścią JSON i odbiera odpowiedź JSON | Gdy reguła powinna należeć do zespołu/CI: scentralizowana walidacja, wspólny panel statusu, zdalny audyt | średnia (wymaga serwera) |
| Prompt | Wysyła krótki prompt do Haiku (domyślnie) i otrzymuje yes/no w ramach oceny semantycznej | Gdy grep nie wystarcza: „oceń, czy ten tekst brzmi korporacyjnie” | niska |
| Agent | Tworzy subagenta z dostępem do Read/Grep/Glob do wieloetapowych kontroli | Rozbudowana walidacja: „znajdź wszystkie zmienione pliki i sprawdź, czy mają odpowiadające im testy” | wysoka |
Najczęstszym błędem jest próba użycia Command do zadania, które wymaga Prompt. Na przykład „nie używaj pauz em w plikach .md” można sprawdzić przez grep, więc Command jest idealny. Ale „nie pisz korporacyjnym tonem” dotyczy znaczenia, więc wymaga Prompt.
Jeśli dopiero zaczynasz, używaj Command. Wszystkie 7 gotowych hooków poniżej to Command. HTTP/Prompt/Agent przydadzą się później, gdy będziesz gotowy.
Gdzie znajduje się settings.json i jakie są 4 poziomy konfiguracji?
Najważniejsze informacje
Claude Code szuka konfiguracji w 4 miejscach: przedsiębiorstwo (wdrażane przez MDM), użytkownik (~/.claude/settings.json), projekt (.claude/settings.json w katalogu głównym repozytorium) oraz lokalnie (.claude/settings.local.json, poza gitem). Wszystkie 4 pliki są łączone na początku sesji, a pierwszeństwo ma niższy poziom.
| Poziom | Ścieżka | Kiedy obowiązuje | W git? |
|---|---|---|---|
| Przedsiębiorstwo | /Library/Application Support/ClaudeCode/settings.json (Mac), /etc/claude-code/settings.json (Linux) | W całym zespole przez MDM | nie |
| Użytkownik | ~/.claude/settings.json | Wszystkie projekty jednego użytkownika | nie |
| Projekt | .claude/settings.json w katalogu głównym repozytorium | Ten projekt, dla całego zespołu | tak |
| Lokalnie | .claude/settings.local.json | Tylko ten projekt i tylko ten użytkownik | nie (w .gitignore) |
Trzymam 7 opisanych poniżej hooków w projektowym .claude/settings.json, dzięki czemu są automatycznie uwzględniane po sklonowaniu repozytorium, a Claude stosuje te same reguły w nowym oknie. Osobiste hooki eksperymentalne umieszczam w .claude/settings.local.json, żeby nie przeszkadzały zespołowi.
Wszystkie 4 pliki są łączone na początku sesji. Jeśli reguły są sprzeczne, pierwszeństwo ma niższy poziom (lokalny przed projektem, projekt przed użytkownikiem).
Najszybszy sposób na utworzenie pierwszego hooka to interaktywne menu /hooks w Claude Code. Wybierz zdarzenie z listy, określ matcher (na przykład Bash lub Edit) i wklej polecenie. Claude Code zapisze poprawny JSON w settings.json.
31 zdarzeń cyklu życia: których potrzebują początkujący?
Najważniejsze informacje
Claude Code ma obecnie 31 zdarzeń cyklu życia, podzielonych na 3 częstotliwości: raz na sesję (SessionStart/SessionEnd), raz na turę (UserPromptSubmit/Stop) i przy każdym wywołaniu narzędzia (PreToolUse/PostToolUse). Początkujący potrzebują 4 z 31 zdarzeń: PreToolUse do ograniczeń, PostToolUse do automatycznych zmian, SessionStart do kontekstu i Stop do końcowych kontroli.
Zdarzenia występują z trzema częstotliwościami: raz na sesję (SessionStart, SessionEnd), raz na turę (UserPromptSubmit, Stop, StopFailure) oraz przy każdym wywołaniu narzędzia w pętli agentowej (PreToolUse, PostToolUse).
Warto zrozumieć te trzy częstotliwości, żeby uniknąć nieporozumień:
- Raz na sesję -
SessionStart,SessionEnd,Setup. Uruchamiają się raz przy otwieraniu i zamykaniu Claude Code. Używaj ich do inicjowania kontekstu i usuwania plików tymczasowych. - Raz na turę -
UserPromptSubmit,UserPromptExpansion,Stop,StopFailure. Uruchamiają się przy każdej twojej wiadomości i za każdym razem, gdy model się zatrzymuje. Służą do rejestrowania rozmowy i końcowej kontroli wyników. - Przy każdym wywołaniu narzędzia -
PreToolUse,PostToolUse,PostToolUseFailure,PermissionRequest,PermissionDenied,PostToolBatch,SubagentStart,SubagentStop. Uruchamiają się przy każdej edycji pliku, uruchomieniu Bash, odczycie lub wyszukiwaniu. To najbardziej intensywna warstwa, w której działają ograniczenia, formatowanie i walidacja.
Pełna lista 31 zdarzeń obejmuje asynchroniczne/reaktywne zdarzenia: TeammateIdle, PreCompact, PostCompact, Elicitation, ElicitationResult, InstructionsLoaded, ConfigChange, CwdChanged, FileChanged, WorktreeCreate, WorktreeRemove, Notification. Są przeznaczone do zaawansowanych zastosowań; na razie je pomiń.
Test z pięcioma pytaniami: kiedy reguła powinna trafić do hooka, a kiedy zostać w CLAUDE.md?
Najważniejsze informacje
Jeśli reguła dotyczy działania (a nie kontekstu), odnosi się do bezpieczeństwa/pieniędzy/danych, można ją sprawdzić przez grep/regex, trudno naprawić skutki jej złamania, a Claude już przynajmniej raz ją złamał, powinna trafić do hooka. Jeśli mniej niż trzy z pięciu odpowiedzi brzmią „tak”, zostaje w CLAUDE.md. Zastosowałem ten test do swoich 80 wierszy i przeniosłem 5 reguł do hooków.
Dla mnie to działa jak prawo, a nie sugestia. Wybór między CLAUDE.md a hookiem sprowadziłem do prostego testu z pięcioma pytaniami. Jeśli co najmniej trzy odpowiedzi brzmią „tak”, reguła trafia do hooka. Przy mniej niż trzech zostaje w tekście.
Test „kiedy reguła powinna trafić do hooka?”
- Czy ta reguła dotyczy działania, a nie kontekstu? („nie uruchamiaj
rm -rf” dotyczy działania; „mam projekt TypeScript” to kontekst). - Czy reguła dotyczy bezpieczeństwa, pieniędzy lub danych? („nie dotykaj
.env”, „nie publikuj kluczy”). - Czy można ją sprawdzić przez grep, regex lub kod wyjścia polecenia powłoki? („po edycji
.mdsprawdź, czy nie ma pauzy em”). - Czy skutki złamania reguły byłyby trudne do naprawienia? („force push do main” trudno odwrócić; „brak kropki na końcu” łatwo poprawić).
- Czy zdarzyło ci się już, że Claude przynajmniej raz zapomniał tej reguły?
Przykład: „Nie używaj pauz em, używaj tylko łączników.” Działanie? Tak. Bezpieczeństwo? Nie. Można sprawdzić przez grep? Tak. Trudno naprawić? Nie. Widziałem naruszenie? Tak. 3 z 5: umieść w hooku.
Kontrprzykład: „Jestem vibe coderem, nie programistą; pisz bez slangu.” Działanie? Raczej kontekst. Bezpieczeństwo? Nie. Można sprawdzić regexem? Zbyt trudno. Trudno naprawić? Nie. Widziałem naruszenie? Tak. 1 z 5: zostaw w CLAUDE.md.
Pięć moich reguł przeszło test. Przeniosłem je do hooków. Pozostałe 75% zostało w pliku tekstowym.
7 hooków gotowych do skopiowania
Najważniejsze informacje
Poniżej znajdziesz 7 gotowych hooków do .claude/settings.json: blokowanie edycji .env, automatyczne uruchamianie prettier, zapobieganie force-push do main, sprawdzanie pauz em, zwalnianie portu przed npm run dev, automatyczny commit po pomyślnych testach oraz wstawianie świeżego kontekstu na początku sesji. Skopiuj każdy do pliku, uruchom ponownie Claude Code i gotowe.
Wszystkie 7 hooków znajduje się w jednym pliku .claude/settings.json w katalogu głównym projektu. Każdy wpis zawiera typ zdarzenia, filtr narzędzia (matcher) i polecenie powłoki. Kod 2 odrzuca dopasowane PreToolUse przed wykonaniem. Przy PostToolUse czynność już nastąpiła: hook zgłasza informację zwrotną i nie może jej cofnąć.
W przypadku większości zdarzeń hooków działanie blokuje tylko kod wyjścia 2. Claude Code traktuje kod wyjścia 1 jako nieblokujący błąd i kontynuuje działanie, mimo że w Unixie 1 zwyczajowo oznacza niepowodzenie.
exit 0 oznacza powodzenie. exit 1 oznacza błąd, ale Claude kontynuuje (to pierwszy antywzorzec poniżej). exit 2 zależy od zdarzenia: może blokować PreToolUse, ale PostToolUse tylko zgłasza informację o już wykonanej czynności.
Hook 1. Kontroluj bezpośrednie edycje .env (PreToolUse, exit 2)
Blokuje próby bezpośredniej edycji dowolnego pliku pasującego do wzorca .env, .env.local, .env.production i podobnych.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '(^|/)\\.env(\\.|$)' && { echo 'Do not edit `.env` directly. Tell me which variable to add, and I’ll open the file myself.' >&2; exit 2; } || exit 0"
}
]
}
]
}
}
Co robi: Przed każdym Edit/Write/MultiEdit pobiera ścieżkę pliku i sprawdza przez grep, czy pasuje do wzorca .env. Jeśli tak, zwraca exit 2 z komunikatem. Claude widzi stderr i mówi mi: „Próbowałem edytować .env, ale hook mnie zablokował; powiedz mi, którą zmienną dodać”.
Hook 2. Automatycznie uruchamiaj prettier po każdej edycji kodu (PostToolUse, exit 0)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(ts|tsx|js|jsx|json)$' && npx prettier --write \"$FILE\" 2>/dev/null & exit 0"
}
]
}
]
}
}
Co robi: Po każdym Edit/Write pobiera ścieżkę i jeśli rozszerzenie to .ts/.tsx/.js/.jsx/.json, uruchamia prettier w tle za pomocą &. Zawsze kończy się kodem 0, ponieważ chodzi o formatowanie, a nie ograniczenie. Jeśli prettier zawiedzie, nic wielkiego; plik pozostaje bez zmian.
Różnica w stosunku do hooka 1 jest zasadnicza: PreToolUse + exit 2 to twarda blokada przed działaniem. PostToolUse + exit 0 to miękka edycja po działaniu. To dwa tryby przeznaczone do różnych zadań.
Hook 3. Blokuj git push --force do gałęzi main (PreToolUse, exit 2)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE 'git[[:space:]]+push' && echo \"$CMD\" | grep -qE '(\\-\\-force|\\-\\-force-with-lease|[[:space:]]-f([[:space:]]|$))' && echo \"$CMD\" | grep -qE '([[:space:]/:])main([[:space:]/:]|$)' && { echo 'Force push to main is forbidden. Create a new branch or ask me to do it manually.' >&2; exit 2; } || exit 0"
}
]
}
]
}
}
Co robi: Przy każdym uruchomieniu Bash sprawdza, czy polecenie zawiera push, --force/--force-with-lease/-f i wzmiankę o main. Jeśli pasują wszystkie trzy warunki, blokuje operację i wyświetla jasny komunikat.
Kiedyś miałem tę regułę zapisaną wielkimi literami w CLAUDE.md. Claude i tak łamał ją mniej więcej raz na dwa tygodnie, gdy prosiłem go o „force push, żeby cofnąć mój zły commit”. Hook wychwytuje pokazane dosłowne formy, lecz aliasy, skrypty, domyślna gałąź celu i inny klient Git mogą go ominąć. Egzekwuj ochronę main zdalnie i ogranicz poświadczenia agenta.
Hook 4. Blokuj pauzy em w plikach tekstowych (PostToolUse, exit 2)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(md|mdx|tsx?)$' && grep -q $'\\xe2\\x80\\x94' \"$FILE\" && { echo \"File $FILE contains an em dash (U+2014). Replace it with a regular hyphen (U+002D).\" >&2; exit 2; } || exit 0"
}
]
}
]
}
}
Co robi: PostToolUse z exit 2 zgłasza Claude błąd kontroli po edycji. Nie cofa ani nie zapobiega zapisowi. Jeśli Claude edytuje plik i pozostawia pauzę em, hook kończy się błędem, Claude otrzymuje informację zwrotną i próbuje ponownie. Po 2–3 sesjach Claude przestaje wstawiać ten znak.
Zwróć uwagę, że grep szuka konkretnie znaku pauzy em (U+2014) za pomocą $'\xe2\x80\x94', czyli zapisu w Bash służącego do przedstawienia go jako bajtów UTF-8. Jeśli po prostu wpiszesz grep -q '-', polecenie znajdzie zwykłe łączniki i uruchomi się przy każdym pliku. Niewidoczny znak psuje całą logikę.
Hook 5. Zwolnij port 3000 przed npm run dev (PreToolUse, exit 0)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE '(npm|pnpm|yarn)[[:space:]]+run[[:space:]]+dev' && { lsof -ti:3000 | xargs -r kill -9 2>/dev/null; echo 'Port 3000 freed before launch' >&2; }; exit 0"
}
]
}
]
}
}
Co robi: Przed każdym npm run dev (lub pnpm/yarn) zabija proces korzystający z portu 3000. Zawsze kończy się kodem 0, ponieważ to przygotowanie, a nie ograniczenie. Chroni przed pułapką, w której Claude widzi „port in use”, uznaje, że problem dotyczy kodu i zaczyna go poprawiać, choć tak naprawdę nadal działał tylko stary serwer.
Hook 6. Automatycznie wykonuj commit po pomyślnych testach (PostToolUse, exit 0)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "INPUT=$(cat); CMD=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty') || exit 1; EXIT=$(printf '%s' \"$INPUT\" | jq -r '.tool_response.exit_code // empty') || exit 1; case \"$CMD\" in 'npm test'|'pnpm test'|'yarn test') ;; *) exit 0 ;; esac; [ \"$EXIT\" = \"0\" ] || exit 0; git diff --cached --quiet || { echo 'Existing staged changes: review manually' >&2; exit 0; }; git add -- src/orders.ts tests/orders.test.ts || exit 1; git diff --cached --quiet && exit 0; git commit -m 'tests: checkpoint after successful run'"
}
]
}
]
}
}
Co robi: Czyta JSON stdin raz i akceptuje tylko pokazane dokładne proste polecenia testów. Działa tylko przy jawnie zerowym tool_response.exit_code; jeśli wersja nie dostarcza pola, pomija commit zamiast zgadywać sukces. Zastąp src/orders.ts i tests/orders.test.ts dokładnymi plikami zadania. Wcześniejsze zmiany w indeksie zatrzymują receptę, aby nie dołączyć cudzej pracy. Nieudane add zatrzymuje ją, pusty diff nie tworzy commita, zwykłe hooki commitów pozostają aktywne. Przed włączeniem sprawdź offline wyniki udane, błędne i brakujące.
Antywzorzec: git commit --no-verify przy każdej edycji. Zachowuj kontrole commitów. Przy rekurencyjnym wyzwalaniu automatyzacji zawęź wyzwalacz lub przeprojektuj kontrolę zamiast domyślnie pomijać weryfikację. Przed automatycznymi commitami przejrzyj diff indeksu i własność zadania.
Hook 7. Wstawiaj świeży kontekst na początku sesji (SessionStart z additionalContext)
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo 'no-branch'); STATUS=$(git status --short 2>/dev/null | head -10); echo \"{\\\"additionalContext\\\":\\\"Current branch: $BRANCH. Uncommitted changes:\\n$STATUS\\\"}\""
}
]
}
]
}
}
Co robi: Na początku sesji zbiera bieżącą gałąź Git i listę niezatwierdzonych zmian, a następnie zwraca JSON z polem additionalContext. Claude Code wstawia ten tekst do pierwszej wiadomości systemowej, więc Claude od początku wie, gdzie się znajduje i co pozostało po poprzedniej sesji.
Używaj additionalContext do przekazywania informacji o bieżącym stanie środowiska lub niedawno zakończonej operacji, które Claude powinien znać: stan środowiska (bieżąca gałąź, cel wdrożenia, aktywne flagi funkcji), warunkowe reguły projektu (które polecenie testowe dotyczy właśnie edytowanego pliku), dane zewnętrzne (otwarte zgłoszenia przypisane do ciebie, ostatnie wyniki CI).
Ten hook można rozszerzyć o pobieranie najnowszego commita i jego opisu, statusu GitHub Actions, bieżącego sprintu z Linear/Jira oraz otwartych zgłoszeń w bieżącym repozytorium.
3 pułapki związane z hookami, które przydarzają się co drugiej osobie
Najważniejsze informacje
Pierwsza pułapka: hook kończący się exit 1 przechodzi po cichu, a Claude ignoruje błąd. Druga: ciężka praca wykonywana w hooku zostaje przerwana po przekroczeniu limitu 60 sekund. Trzecia: hook widzi tylko zmienne środowiskowe z ~/.zshrc, a nie z .env projektu. Każdy z tych problemów można rozwiązać jedną linijką, ale bez znajomości tych pułapek można odnieść wrażenie, że „hooki nie działają”.
Pułapka 1. Hook z exit 1 przechodzi po cichu
To najniebezpieczniejsza pułapka. Przez tydzień myślałem, że mój hook chroniący .env działa, ale okazało się, że w hooku użyłem jq -r '.tool_input.file_path' bez sprawdzenia wartości null. Gdy tool_input było puste, jq kończył działanie z kodem 1. Claude traktował exit 1 jako „błąd hooka, kontynuuj”, więc działanie było wykonywane. Nie rozumiałem, dlaczego .env nadal jest modyfikowany. Przyczynę zauważyłem dopiero wtedy, gdy ręcznie uruchomiłem hook w terminalu z pustym wejściem.
Rozwiązanie to jedna zasada: każdy hook zaczynaj od sprawdzenia null, a kończ na || exit 0. Każde nieoczekiwane zakończenie spowoduje wtedy „bezpieczne” wyjście zamiast cichego przepuszczenia operacji.
# Safe start and end template:
FILE=$(jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0
# ... your logic ...
exit 0 # or || exit 0 on one line
Pułapka 2. Wolne hooki są przerywane po przekroczeniu limitu czasu
Miałem hook, który po Edit uruchamiał eslint --fix na całym pliku. Przetworzenie dużych plików (1000+ linii) zajmowało 8–10 sekund. Domyślny limit czasu hooka w Claude Code wynosi 60 sekund, więc wydawało mi się, że mieszczę się w limicie. Gdy jednak w ciągu minuty dochodzi do kilku operacji Edit, hooki gromadzą się i ustawiają w kolejce, a niektóre są po prostu pomijane. Zorientowałem się dopiero wtedy, gdy w logu pojawił się komunikat „hook timed out”.
Rozwiązanie: jeśli hook wymaga ciężkiej pracy, uruchom ją w tle. Użyj eslint --fix "$FILE" & i nie czekaj na wynik. Hook zwróci wtedy exit 0 w kilka milisekund, a właściwa praca będzie wykonywana w tle.
# Heavy operation in background:
npx eslint --fix "$FILE" &
exit 0
Pułapka 3. Hook nie widzi zmiennych środowiskowych projektu
Myślałem, że process.env w hooku będzie dostępne tak jak w aplikacji Node.js. Tak nie jest. Hook działa w powłoce uruchomionej przez proces nadrzędny Claude Code i widzi tylko to, co znajduje się w ~/.zshrc lub ~/.bashrc, oraz to, co Claude Code jawnie przekazuje. Jeśli hook zależy od OPENAI_API_KEY z .env, musisz ręcznie odczytać .env.
Rozwiązanie: jawnie odczytaj .env w poleceniu hooka.
# Passing project env into hook:
cd "$PWD" && source .env && curl -H "Authorization: Bearer $OPENAI_API_KEY" ...
# Or with dotenv:
dotenv -e .env -- node my-validator.js
Jest to szczególnie kłopotliwe w monorepo, gdzie różne pakiety mają własne pliki .env. Rozwiąż ten problem, przekazując PWD i jawnie wykonując cd "$PWD" na początku polecenia.
Jak testować hooki przed commitem?
Najważniejsze informacje
Hook to polecenie powłoki. Możesz uruchomić je ręcznie w terminalu z testowym stdin, sprawdzić kod wyjścia i stderr, a wszystko to zrobić, zanim uruchomi je Claude Code. Pozwala to zaoszczędzić wiele godzin na debugowaniu i chroni przed hookami, które blokują ich własne działanie.
Podstawowy scenariusz testowy. Skopiuj polecenie hooka z JSON, otwórz terminal i przekaż JSON, który wysłałby Claude Code:
# Test hook 1 (protect .env):
echo '{"tool_input":{"file_path":".env"}}' | bash -c '
FILE=$(jq -r ".tool_input.file_path // empty")
[ -z "$FILE" ] && exit 0
echo "$FILE" | grep -qE "(^|/)\.env(\.|$)" && {
echo "BLOCKED" >&2
exit 2
} || exit 0
'
echo "Exit code: $?"
W stderr powinien pojawić się komunikat BLOCKED oraz Exit code: 2. Jeśli otrzymasz exit 0, regex nie działa; popraw grep.
Scenariusze do sprawdzenia:
- Ścieżka pozytywna - plik powinien zostać zablokowany.
.envw przypadku hooka 1,git push --force mainw przypadku hooka 3. - Ścieżka negatywna - plik nie powinien zostać zablokowany.
README.mdw przypadku hooka 1,git push origin feature-branchw przypadku hooka 3. - Przypadki brzegowe - puste wejście, wartości null, nieoczekiwane znaki. Hook powinien zwrócić exit 0 albo zakończyć się czytelnym błędem, a nie po cichu przepuszczać operację.
Gdzie umieszczać eksperymentalne hooki. Nie w .claude/settings.json, lecz w .claude/settings.local.json (poza gitem). Dodaj tam nowe hooki w sekcji PostToolUse i użyj echo zamiast właściwego działania. Sprawdź, czy się uruchamiają, a następnie przenieś je do produkcyjnego settings.json.
// Experiment in settings.local.json - writes to /tmp/hook-debug.log:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "echo \"[hook fired] $(date) $(jq -r '.tool_input.file_path // \"?\"')\" >> /tmp/hook-debug.log"
}
]
}
]
}
}
Po 5–10 edycjach w Claude Code otwórz /tmp/hook-debug.log i sprawdź, które pliki wywołały hook. Jeśli plik jest pusty, matcher nie pasuje; popraw regex.
Co dalej?
Najważniejsze informacje
Zacznij od hooków PostToolUse kończących się exit 0; są najbezpieczniejsze i niczego nie blokują. Po tygodniu dodaj pierwszy hook PreToolUse z exit 2, aby chronić .env lub zapobiegać force-push. Po dwóch tygodniach CLAUDE.md skróci się z 200-wierszowego kontraktu do krótkiego, 50–80-wierszowego opisu kontekstu, z wybranymi kontrolami zdarzeń w hookach i twardymi granicami dostępu egzekwowanymi osobno przez uprawnienia, sandbox i ochronę zdalną.
5 kroków na nadchodzący tydzień:
- Wyszukaj w
CLAUDE.mdprzez Grep słowa takie jak „never”, „forbidden”, „always” i „must”. Każda taka reguła może trafić do hooka. Jeśli reguła ma formę nakazu, plik tekstowy jej nie wymusi. - Zacznij od PostToolUse + exit 0. To najbezpieczniejsze rozwiązanie: wprowadza zmianę, ale niczego nie blokuje. Prettier (hook 2), zakończenie serwera deweloperskiego (hook 5), automatyczny commit po testach (hook 6).
- Dopiero gdy nabierzesz pewności, dodaj pierwszy PreToolUse + exit 2. Na przykład chroń
.env(hook 1) albo zapobiegaj force-push (hook 3). To twarda blokada, a błąd w poleceniu grep może zablokować również ciebie. - Ręcznie testuj hooki przed commitem (zobacz sekcję powyżej). Oszczędzi ci to wielu godzin debugowania.
- Eksperymentuj w
settings.local.json. Umieszczaj tam nowe hooki, używając echo zamiast właściwego działania.
Po skonfigurowaniu hooków kolejnym krokiem w tym klastrze jest jak cofnąć zmiany w Claude Code, na wypadek gdyby hook przepuścił coś niebezpiecznego. Jeśli chcesz, by Claude pamiętał strukturę projektu między sesjami, zbuduj Drugi Mózg w Claude Code (folder business/ + połączona baza wiedzy). A jeśli chcesz nadać skills projektu nazwy, aby można było ich ponownie używać, zobacz Claude Code Skills.
Źródła
Jeśli potrzebujesz dostępu do sieci neuronowych przez Ailoot, zajrzyj na stronę Ailoot.
Więcej szczegółów
Pytania i odpowiedzi
Dlaczego hook musi czytać JSON wejścia tylko raz?
Stdin jest strumieniem: pierwszy jq zużywa go, zostawiając późniejszym odczytom EOF. Zachowaj wejście raz, potem wyodrębniaj polecenie i wynik. Brakujący lub nieznany wynik traktuj jako niesprawdzony i pomiń auto-commit.
Czy hooki regex tworzą granicę bezpieczeństwa?
Nie. Sprawdzają konkretne zdarzenia i rozpoznawalne ciągi. Przekierowania, aliasy, skrypty, MCP lub inna ścieżka zapisu mogą je ominąć; dla twardych granic używaj ograniczonych poświadczeń i plików, sandboxa i chronionych gałęzi.