SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-28

Claude Code hooks: jak działają i jak je konfigurować

Dowiedz się, jak działają hooki w Claude Code. Sprawdź, dlaczego kod wyjścia 2 przerywa wywołanie narzędzia, jakie zdarzenia obsługuje system oraz jak zapewnić bezpieczeństwo.

Czym jest hook w Claude Code

Hooki w Claude Code to polecenia powłoki, które Claude Code wykonuje samodzielnie w określonych punktach swojego cyklu życia. Na tym polega zasadnicza różnica między hookiem a plikiem reguł. Instrukcja w CLAUDE.md stanowi wskazówkę, którą model rozważa w kontekście wszystkich pozostałych danych. Hook jest kodem i wykonuje się niezależnie od decyzji modelu. Jeśli agent notorycznie pomija formater, o którym wspomniano dwukrotnie, nie jest potrzebna bardziej stanowcza instrukcja. Potrzebny jest hook.

Mechanizm ten jest niewielki. Polecenie rejestruje się w pliku ustawień pod nazwą zdarzenia. Gdy zdarzenie wystąpi, Claude Code uruchamia polecenie i przesyła dane zdarzenia do jego standardowego wejścia (stdin) w formacie JSON (JavaScript object notation). Polecenie odczytuje te dane, wykonuje zadanie i zwraca status wyjścia. Kod wyjścia 2 z hooka PreToolUse anuluje wywołanie narzędzia przed jego uruchomieniem, a treść zapisana przez skrypt do standardowego wyjścia błędów (stderr) jest przekazywana modelowi jako uzasadnienie.

Nazwy zdarzeń oraz pola pochodzą z dokumentacji hooków Claude Code (https://code.claude.com/docs/en/hooks), zweryfikowanej w sierpniu 2026 roku dla wydania 2.1.232. Interfejs ten zmienia się dynamicznie, dlatego przed skopiowaniem kodu JSON z jakiegokolwiek wpisu, w tym z niniejszego, należy sprawdzić dokumentację dla używanej wersji. Własną konfigurację można wyświetlić za pomocą claude --version.

Lokalizacja konfiguracji hooków

Hook jest blokiem JSON w pliku ustawień. Może znajdować się w jednej z sześciu lokalizacji, a zakres pliku wyznacza zakres działania hooka.

  • ~/.claude/settings.json: każdy projekt na danej maszynie, niedostępny dla innych użytkowników.
  • .claude/settings.json: jeden projekt, zapisany w repozytorium, dzięki czemu każdy użytkownik po sklonowaniu otrzymuje hook.
  • .claude/settings.local.json: jeden projekt, tylko na danej maszynie.
  • Ustawienia polityki zarządzanej: w skali całej organizacji, konfigurowane przez administratora.
  • hooks/hooks.json wewnątrz wtyczki, aktywne podczas działania tej wtyczki.
  • Frontmatter umiejętności lub subagenta, aktywne podczas działania danego komponentu.

Wpisy hooków z tych plików łączą się, zamiast nadpisywać nawzajem. Plik ustawień projektu dodaje swoje hooki do tych z ustawień użytkownika, zamiast je zastępować, dzięki czemu jedno zdarzenie może obsługiwać kilka hooków z różnych plików. Ustawienie "disableAllHooks": true wyłącza je, z jednym wyjątkiem: hooki z ustawień polityki zarządzanej działają nadal, chyba że to ustawienie zostanie zastosowane również w ramach polityki zarządzanej.

Uruchom /hooks wewnątrz sesji, aby wyświetlić listę wszystkich aktualnie zarejestrowanych hooków, pogrupowanych według zdarzeń, wraz ze źródłowym plikiem i dopasowaniem dla każdego z nich. Menu jest tylko do odczytu, więc modyfikacja hooka wymaga edycji pliku ustawień. Mechanizm śledzenia plików zazwyczaj wykrywa zmiany bez konieczności restartu.

Dostępne zdarzenia hook w Claude Code

Wydanie 2.1.232 wymienia trzydzieści jeden zdarzeń, od SessionStart do SessionEnd, obejmujących kompaktowanie, subagenty, drzewa robocze oraz pliki konfiguracyjne. Praca serwera wykorzystuje tylko część z nich.

  • PreToolUse: przed wykonaniem wywołania narzędzia. Jest to zdarzenie, które może blokować działanie.
  • PostToolUse: po pomyślnym zakończeniu wywołania narzędzia. PostToolUseFailure uruchamia się w przypadku niepowodzenia, więc hook wymagający monitorowania każdego wyniku musi obsługiwać oba.
  • PermissionRequest: gdy wywołanie narzędzia wymaga decyzji o uprawnieniach; jest to moment, w którym pojawia się monit o zatwierdzenie.
  • UserPromptSubmit: po przesłaniu promptu, przed jego przetworzeniem przez Claude. Wszystko, co ten hook wypisze na stdout, zostaje dodane do kontekstu modelu.
  • SessionStart oraz SessionEnd: na każdym z końców sesji. SessionStart uruchamia się również po kompaktowaniu, z wartością dopasowania compact.
  • Stop: gdy Claude zakończy odpowiadanie. Dzieje się to raz na turę, a nie raz na ukończone zadanie.

Każda grupa zawiera matcher, który decyduje, w jakich przypadkach hook zostanie uruchomiony. W przypadku zdarzeń narzędziowych filtrowanie odbywa się według nazwy narzędzia, więc "Edit|Write" uruchamia się tylko przy edycji plików. Dopasowania są wrażliwe na wielkość liter. Puste dopasowanie uruchamia hook przy każdym wystąpieniu. Narzędzia z serwera MCP (model context protocol) są nazywane mcp__<server>__<tool>, więc dopasowanie "mcp__github__.*" przechwytuje narzędzia jednego serwera, ignorując pozostałe.

Hooki Stop posiadają pułapkę, o której warto wiedzieć przed rozpoczęciem pisania. Hook Stop, który blokuje działanie, odsyła model do ponownej pracy, a Claude Code nadpisuje hook po ośmiu blokadach z rzędu. Należy odczytać pole stop_hook_active z danych wejściowych hooka i zakończyć działanie z kodem 0, gdy jest ono prawdziwe; w przeciwnym razie hook będzie zapętlał się do momentu osiągnięcia tego limitu.

Dane otrzymywane przez hook na stdin

Gdy Claude ma uruchomić npm test, hook PreToolUse na Bash odczytuje następujące dane ze standardowego wejścia (stdin):

{
  "session_id": "abc123",
  "cwd": "/home/deploy/myproject",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

Każde zdarzenie zawiera session_id, cwd, permission_mode, transcript_path oraz hook_event_name. Zdarzenia narzędziowe dodają tool_name, tool_input oraz tool_use_id. Pozostałe zdarzenia zawierają własne pola: UserPromptSubmit otrzymuje tekst prompt, a SessionStart otrzymuje source typu startup, resume, clear, compact lub fork.

jq jest standardowym narzędziem do odczytu tych danych w skryptach powłoki, jednak minimalny obraz serwera go nie zawiera. Należy je zainstalować za pomocą sudo apt install -y jq w systemach Ubuntu i Debian.

Wpływ kodu wyjścia na wykonywane wywołanie narzędzia

Istnieją trzy możliwe rezultaty.

  • Kod wyjścia 0 oznacza, że hook nie zgłasza zastrzeżeń. W przypadku PreToolUse nie jest to równoznaczne z zatwierdzeniem, a standardowy proces weryfikacji uprawnień nadal przebiega. W przypadku UserPromptSubmit oraz SessionStart, zawartość stdout jest dodawana do kontekstu modelu.
  • Kod wyjścia 2 blokuje akcję w zdarzeniach, które podlegają blokowaniu, w tym PreToolUse, a zawartość stderr staje się powodem przekazywanym do modelu. W zdarzeniach, których nie można zablokować, takich jak PostToolUse, blokada jest ignorowana, choć stderr nadal trafia do modelu jako informacja zwrotna.
  • Każdy inny kod wyjścia jest błędem nieblokującym. Akcja jest kontynuowana. W transkrypcie pojawia się powiadomienie o błędzie hooka, zawierające pierwszą linię stderr po tekście Failed with non-blocking status code:.

Aby uzyskać coś więcej niż blokadę lub brak reakcji, należy zwrócić kod wyjścia 0 i wypisać obiekt JSON na stdout. Hook PreToolUse podejmuje decyzję za pomocą permissionDecision:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database drops go through a migration, not through the agent."
  }
}

"allow" pomija interaktywny monit, "deny" anuluje wywołanie i przesyła powód do modelu, a "ask" wyświetla monit w standardowy sposób. Należy wybrać jeden styl dla każdego hooka. Łączenie kodu wyjścia 2 z decyzją JSON na stdout prowadzi do niejednoznacznego wyniku.

Gdy kilka hooków pasuje do jednego zdarzenia, są one uruchamiane równolegle i każdy z nich kończy swoje działanie. Wynik deny z jednego hooka nie zatrzymuje pozostałych, więc hook logujący zapisze swoją linię nawet wtedy, gdy hook zabezpieczający odrzuci to samo wywołanie. Claude Code scala odpowiedzi i stosuje najbardziej restrykcyjną z nich, zgodnie z kolejnością: odrzuć, odrocz, zapytaj, zezwól.

Przykład 1: blokowanie destrukcyjnego polecenia przed jego uruchomieniem

Zapisz poniższą treść jako .claude/hooks/block-destructive.sh w swoim projekcie:

#!/bin/bash
# Deny a Bash tool call whose command matches a banned pattern.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

for pattern in 'rm -rf /' 'mkfs' 'dd if=' 'DROP TABLE'; do
  if printf '%s' "$COMMAND" | grep -qiF -- "$pattern"; then
    echo "Blocked by policy: the command matches '$pattern'. A human runs this one." >&2
    exit 2
  fi
done

exit 0

Nadaj uprawnienia do wykonywania, a następnie zarejestruj skrypt w PreToolUse w pliku .claude/settings.json:

chmod +x .claude/hooks/block-destructive.sh
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive.sh",
            "timeout": 10,
            "statusMessage": "Checking the command against policy"
          }
        ]
      }
    ]
  }
}

Przetestuj skrypt ręcznie, zanim zaczniesz mu ufać, ponieważ hook, który zawiesza się na własnych danych wejściowych, domyślnie zezwala na wykonanie operacji:

echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /var/lib/postgresql"}}' \
  | .claude/hooks/block-destructive.sh
echo $?

Powinieneś zobaczyć linię Blocked by policy: na stderr oraz kod wyjścia 2. Podaj nieszkodliwe polecenie, takie jak ls -la; nie powinieneś otrzymać żadnego wyjścia, a kod wyjścia powinien wynosić 0. W sesji odrzucone wywołanie pojawi się w transkrypcie wraz z Twoim komunikatem jako powodem, a model odczyta tę wiadomość i dostosuje swoje działanie.

Jedna właściwość sprawia, że warto to wdrożyć: hooki PreToolUse uruchamiają się przed sprawdzeniem trybu uprawnień, w każdym trybie uprawnień, więc odmowa obowiązuje nawet w trybie bypassPermissions. To właśnie czyni hook użytecznym w połączeniu z trybem automatycznym Claude Code i jego ustawieniami uprawnień, gdzie monity są ograniczone, ale hook nadal jest wyzwalany.

Bądź świadomy ograniczeń tego rozwiązania. Dopasowywanie wzorców w ciągu polecenia to bariera zabezpieczająca przed nieostrożnością agenta, a nie granica przed celowym działaniem, ponieważ to samo polecenie można zapisać w formie, której Twój grep nigdy nie wykryje. Twarde reguły powinny być egzekwowane w systemie uprawnień oraz na koncie, na którym uruchomiony jest proces.

Przykład 2: formatowanie i linting po każdej edycji

PostToolUse z dopasowaniem Edit|Write uruchamia się po każdym narzędziu edycji plików. Zapisz to jako .claude/hooks/after-edit.sh:

#!/bin/bash
# Format the edited file, then report lint failures back to the model.
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0

case "$FILE" in
  *.py)
    ruff format "$FILE" >/dev/null 2>&1
    if ! ruff check "$FILE" >&2; then
      exit 2
    fi
    ;;
  *.sh)
    if ! shellcheck "$FILE" >&2; then
      exit 2
    fi
    ;;
esac

exit 0
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/after-edit.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

Poproś Claude o dodanie niepoprawnie wciętej funkcji do pliku Python, a następnie otwórz ten plik. Zostanie on sformatowany. Jest to potwierdzenie, że hook zadziałał, ponieważ pomyślne wykonanie hooka nie generuje żadnych komunikatów w konwersacji.

Kod wyjścia 2 w tym miejscu nie cofa żadnych zmian. PostToolUse uruchamia się po wykonaniu narzędzia, więc edycja znajduje się już na dysku. Kod wyjścia 2 sprawia, że wyjście ruff check trafia do modelu jako informacja zwrotna, dzięki czemu model naprawia błąd, który właśnie wprowadził, zamiast przechodzić dalej. Na tym polega różnica między błędem lintingu wykrytym w momencie commitowania a błędem, który agent naprawia w tej samej turze.

W tym przypadku istotne są dwa ograniczenia dopasowania. Edit|Write nie widzi plików zmienionych przez polecenie powłoki, a Claude wystarczająco często zapisuje pliki przez Bash, aby ta luka była odczuwalna. Aby uzyskać pokrycie dla każdego wywołania, dopasuj również Bash i spraw, aby skrypt wyświetlał zmienione pliki za pomocą git status --porcelain. Aby uzyskać pokrycie raz na turę, umieść skanowanie w hooku Stop.

Przykład 3: rejestrowanie każdego wywołania narzędzia do celów audytu

Pusty dopasowywacz w PostToolUse uruchamia się przy każdym narzędziu. Przesyłanie rekordu do dziennika systemowego zamiast do pliku w katalogu domowym chroni go przed dostępem z poziomu powłoki agenta:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{time: now|todate, session: .session_id, cwd: .cwd, tool: .tool_name, input: .tool_input}' | logger -t claude-code -p local0.info"
          }
        ]
      }
    ]
  }
}

Odczytaj dane za pomocą journalctl -t claude-code -o cat | tail -n 5. Powinna pojawić się jedna linia JSON na każde wywołanie narzędzia, z najnowszymi wpisami na końcu. Brak danych oznacza, że hook nie został uruchomiony; kwestię tę wyjaśnia poniższa sekcja rozwiązywania problemów.

Dodaj ten sam blok pod PostToolUseFailure, aby przechwytywać nieudane wywołania, ponieważ PostToolUse uruchamia się tylko w przypadku powodzenia, a nieudana komenda jest zazwyczaj najbardziej istotna. Powodem użycia logger zamiast dopisywania do pliku w katalogu domowym jest kwestia uprawnień: hook działa jako ten sam użytkownik co powłoka agenta, więc wszystko, do czego użytkownik może dopisywać, może również wyczyścić. Dziennik jest zapisywany przez systemd-journald na własnym koncie.

Czas trwania skryptu typu hook

ChartDefault hook timeout in seconds, by hook type and event
The data behind this chart
[
  {
    "label": "command, http or mcp_tool hook",
    "default_timeout_seconds": 600
  },
  {
    "label": "agent hook",
    "default_timeout_seconds": 60
  },
  {
    "label": "prompt hook",
    "default_timeout_seconds": 30
  },
  {
    "label": "command hook on UserPromptSubmit",
    "default_timeout_seconds": 30
  },
  {
    "label": "command hook on MessageDisplay",
    "default_timeout_seconds": 10
  },
  {
    "label": "any hook on SessionEnd",
    "default_timeout_seconds": 1.5
  }
]

Domyślnie skrypt typu hook otrzymuje 600 sekund, co odpowiada dziesięciu minutom. Niektóre zdarzenia drastycznie skracają ten czas. Skrypty typu SessionEnd współdzielą budżet 1.5 sekund, dlatego czyszczenie sesji musi przebiegać szybko. Ustawienie wyższej wartości timeout dla danego hooka zwiększa ten współdzielony budżet do maksymalnie 60 sekund.

Skrypt typu hook, który przekroczy limit czasu, zostaje anulowany i nie zwraca żadnej decyzji. W przypadku mechanizmu zabezpieczającego PreToolUse oznacza to brak blokady: wywołanie narzędzia jest kontynuowane zgodnie ze standardowym przepływem uprawnień. Z tego powodu skrypty zabezpieczające powinny być lekkie. W przypadku zadań długotrwałych, na które nikt nie oczekuje, takich jak wysyłanie logów, należy ustawić "async": true. Wówczas hook uruchomi się w tle, nie blokując wywołania narzędzia.

Hooki, pliki reguł, umiejętności i serwery MCP

Cztery elementy są często ze sobą mylone, ponieważ każdy z nich zmienia sposób działania agenta. Tylko jeden z nich przestaje być sugestią.

Plik reguł (CLAUDE.md lub plik w .claude/rules/) to tekst ładowany do kontekstu modelu. Kształtuje on zachowanie, ale niczego nie wymusza. W obliczu długiej konwersacji, dużego diffa i nowego żądania użytkownika, pojedyncza linijka może zostać zignorowana. Jest to zwykły mechanizm stojący za agentami ignorującymi zapisane instrukcje.

Umiejętność (skill) to folder z instrukcjami i skryptami, które model ładuje, gdy uzna je za istotne. Ta ocena jest istotą umiejętności, ale stanowi też jej ograniczenie: decyzja nadal należy do modelu. Obie strony tego zagadnienia widać w umiejętności takiej jak Ponytail, która nakierowuje agenta na najmniejszą działającą zmianę, ponieważ kształtuje ona podejście do całego zadania w sposób, w jaki nie zrobiłby tego żaden hook, i dzieje się to tylko wtedy, gdy model zdecyduje się ją załadować.

Serwer MCP (model context protocol) dostarcza modelowi nowe narzędzia do wywołania. Rozszerza on zakres dostępnych dla agenta zasobów. Nie zmusza jednak agenta do korzystania z czegokolwiek i jest osobnym procesem, który należy obsługiwać, co stanowi odrębne zadanie: zobacz uruchamianie serwerów MCP na VPS.

Hook jest jedynym z tych czterech elementów, który uruchamia się bez decyzji modelu. Plików reguł używaj do preferencji, a umiejętności do procedur, których model powinien przestrzegać w odpowiednich sytuacjach. Hooka używaj do kroków, które muszą wystąpić za każdym razem, lub do rzeczy, które nigdy nie mogą się wydarzyć. Głębokie porównanie, w tym sytuacje, w których umiejętność przewyższa plik reguł, znajduje się w porównaniu umiejętności, MCP i plików reguł.

Wtyczka (plugin) to raczej sposób pakowania niż piąty mechanizm. Łączy ona hooki wraz z umiejętnościami w jedną instalowalną jednostkę, dzięki czemu zespół może dostarczać te same zabezpieczenia na każdą maszynę: zobacz jak działają wtyczki Claude Code.

Decyzja dotycząca bezpieczeństwa na współdzielonym VPS

Hook to kod wyzwalany przez agenta, który uruchamia się z uprawnieniami użytkownika inicjującego Claude Code. Dziedziczy on środowisko oraz uprawnienia do plików tego użytkownika. Na laptopie jest to kwestia organizacji pracy. Na serwerze VPS, gdzie agent działa bez nadzoru, jest to zagadnienie bezpieczeństwa składające się z czterech praktycznych elementów.

Hook w repozytorium to kod, którego nie jesteś autorem. .claude/settings.json jest zapisywany w repozytorium, więc sklonowanie go i rozpoczęcie sesji wewnątrz może zarejestrować hooki, które były częścią repozytorium. Claude Code blokuje hooki projektowe za oknem dialogowym zaufania do obszaru roboczego dla danego folderu, co oznacza, że akceptacja zaufania jest momentem, w którym decydujesz się na ich uruchomienie. Najpierw przeczytaj blok hooks.

Hook widzi pełne dane wejściowe narzędzia. Hook audytowy, który loguje tool_input, zapisuje każdy argument każdego polecenia do pliku, w tym każdy token, który znalazł się w wierszu poleceń. Taki dziennik wymaga następnie takiej samej ochrony jak sam sekret, co stanowi część szerszego problemu utrzymywania sekretów poza zasięgiem agenta AI.

Hook może zapisywać dane w kontekście modelu. Wszystko, co hook SessionStart lub UserPromptSubmit wypisze na stdout, jest dodawane do konwersacji. Hook, który przesyła tekst z zewnątrz, systemu śledzenia zgłoszeń lub pliku dziennika, przekazuje niezaufany tekst do modelu w taki sposób, jakbyś wpisał go samodzielnie. Hook, który przekazuje notatkę z innej sesji Claude Code na tym samym VPS, robi to samo, a dane wyjściowe jednego agenta nie zasługują na większe zaufanie niż dane z systemu śledzenia zgłoszeń. Traktuj dane z stdout jako dane wejściowe, a nie jako dane wyjściowe.

Uprawnienia to rzeczywista kontrola. Uruchamiaj agenta jako dedykowany użytkownik bez uprawnień, z tylko takimi regułami sudo, jakich potrzebuje. Warto posiadać regułę PreToolUse deny, jednak z założenia jest to mechanizm oparty na najlepszych staraniach: dokumentacja mówi to samo o filtrze if i zaleca korzystanie z systemu uprawnień, gdy wymagana jest twarda blokada. Reguły uprawnień oraz konto, na którym działa proces, to elementy, które zachowują skuteczność pod obciążeniem.

Jedna właściwość obowiązuje w każdej konfiguracji. Hooki PreToolUse uruchamiają się przed sprawdzeniem trybu uprawnień w każdym trybie, więc hook zwracający deny blokuje narzędzie nawet w trybie bypassPermissions. Hooki mogą zaostrzać to, na co pozwalają reguły uprawnień. Nie mogą one jednak tych reguł rozluźniać.

Dlaczego mój hook nie jest wyzwalany?

Należy postępować zgodnie z poniższą kolejnością. Każdy krok wskazuje objaw, który faktycznie występuje.

  • Uruchom /hooks i sprawdź, czy hook pojawia się pod oczekiwanym zdarzeniem. Brak hooka w menu zazwyczaj oznacza błąd składni JSON w pliku ustawień, ponieważ przecinki na końcu listy oraz komentarze są niedozwolone, lub plik nie znajduje się w jednej z sześciu powyższych lokalizacji.
  • Porównaj matcher dokładnie z nazwą narzędzia. Matchery rozróżniają wielkość liter, więc "bash" nigdy nie dopasuje się do narzędzia Bash.
  • Uruchom skrypt ręcznie z przykładowymi danymi wejściowymi, zgodnie z przykładem 1 powyżej. Nieoczekiwany kod wyjścia oznacza błąd w skrypcie, a Claude Code zgłasza go jako błąd hooka, a nie jako decyzję.
  • Komunikat o treści jq: command not found oznacza, że jq nie znajduje się na danej maszynie. Błąd command not found dla własnego skryptu oznacza, że ścieżka nie została rozpoznana, więc należy użyć ${CLAUDE_PROJECT_DIR} lub ścieżki bezwzględnej. Jeśli skrypt w ogóle się nie uruchamia, prawdopodobnie nie posiada uprawnień do wykonywania.
  • Hook wypisuje poprawny JSON, ale nic się nie dzieje. Hook w formie powłoki jest uruchamiany przez sh -c, a jeśli profil powłoki wypisuje baner, jest on dołączany przed kodem JSON. Standardowe wyjście nie zaczyna się wtedy od {, więc Claude Code odczytuje całość jako zwykły tekst i ignoruje decyzję. Przy kodzie wyjścia 0 nic nie jest raportowane poza dziennikiem debugowania. Należy zawinąć wszelkie echo w profilu tak, aby uruchamiały się tylko w powłokach interaktywnych.
  • W przypadku dalszych problemów: rozpocznij sesję za pomocą claude --debug-file /tmp/claude.log i uruchom tail -f /tmp/claude.log w drugim terminalu. Dziennik debugowania rejestruje, które hooki zostały dopasowane, jaki kod wyjścia zwrócił każdy z nich oraz wszystko, co zostało zapisane na standardowe wyjście i standardowe wyjście błędów.

FAQ

Jaka jest różnica między hookiem Claude Code a instrukcją w CLAUDE.md?

Instrukcja CLAUDE.md jest tekstem w kontekście modelu, więc konkuruje o uwagę z konwersacją oraz bieżącym żądaniem, a model może ją ważyć względem nich. Hook to polecenie powłoki, które Claude Code uruchamia w określonym punkcie cyklu życia, więc wykonuje się ono przy każdym wystąpieniu zdarzenia, niezależnie od decyzji modelu. Instrukcji należy używać do określania preferencji. Hooka należy używać do kroków, które muszą zawsze wystąpić, lub działań, które nigdy nie mogą mieć miejsca.

Jak uniemożliwić Claude Code uruchomienie konkretnego polecenia powłoki?

Zarejestruj hook PreToolUse z dopasowaniem Bash, które odczytuje polecenie z .tool_input.command, zapisuje przyczynę do stderr i kończy działanie kodem 2. Claude Code anuluje wywołanie i wyświetla modelowi podaną przyczynę. Dzieje się to przed sprawdzeniem trybu uprawnień, więc odmowa obowiązuje nawet w trybie bypassPermissions. Dopasowywanie wzorców w ciągu polecenia jest mechanizmem ochronnym, a nie barierą bezpieczeństwa, ponieważ to samo polecenie można zapisać w formie, której wzorzec nie wykryje. Dlatego należy wspierać to regułami uprawnień oraz kontem o ograniczonych przywilejach.

Mój hook wypisuje poprawny JSON, ale nic się nie dzieje. Dlaczego?

Najczęstszą przyczyną jest profil powłoki. Hook bez pola args uruchamia się przez sh -c, a niektóre profile wypisują baner przy każdym uruchomieniu powłoki, który trafia do stdout przed kodem JSON. Ponieważ dane wyjściowe nie zaczynają się od {, Claude Code traktuje całość jako zwykły tekst i ignoruje decyzję, a przy kodzie wyjścia 0 w transkrypcji nie pojawia się żaden komunikat. Zabezpiecz wszelkie echo w swoim profilu testem powłoki interaktywnej, a następnie potwierdź poprawkę, odczytując dziennik debugowania z claude --debug-file /tmp/claude.log.

Czy uruchamianie hooków Claude Code na współdzielonym serwerze jest bezpieczne?

Hooki działają jako użytkownik, który uruchomił Claude Code, z uprawnieniami do plików tego użytkownika, więc hook może wykonać wszystko, co może zrobić to konto. Dwa nawyki eliminują większość ryzyka: uruchamiaj agenta jako dedykowane konto o ograniczonych przywilejach z restrykcyjną polityką sudo oraz czytaj blok hooks każdego repozytorium przed zaakceptowaniem okna dialogowego zaufania do obszaru roboczego, ponieważ hooki projektowe są dostarczane wewnątrz .claude/settings.json. Ustaw "disableAllHooks": true w pliku ustawień, jeśli chcesz, aby żaden z nich nie był uruchamiany.