Przejdź do głównej zawartości

Hooki agentów to skrypty, które Claude Code, Codex i Cursor uruchamiają w stałych punktach pętli agenta, niezależnie od decyzji modelu. Deterministyczny guardrail składa się z czterech warstw: blokady edycji chronionych ścieżek, blokady komend produkcyjnych, lintowania każdej edycji i odmowy zakończenia pracy, dopóki w zmianach jest sekret albo nieprzechodzący test. Każdy hook musi mieć zestaw przypadków testowych (fixtures), które przechodzą.

Ta strona jest dla dewelopera, który odpowiada za konfigurację agenta w jednym repozytorium, oraz dla tech leada, który chce mieć te same kontrole na maszynie każdego członka zespołu. Sytuacja, której ma zapobiec: twój CLAUDE.md mówi „nigdy nie edytuj src/generated/” i „uruchom testy przed zakończeniem”, agent przestrzega obu reguł przez tydzień, a potem w długiej sesji edytuje wygenerowanego klienta, melduje „gotowe” i zostawia w diffie nieprzechodzący test oraz klucz API. Instrukcje to rada, którą model może zgubić w długim kontekście. Hook uruchamia się za każdym razem.

Pytanie 12 scorecardu: Jak używasz hooków działających w trakcie budowania jako deterministycznych guardraili?

Odpowiedź na maksymalną ocenę: Wielowarstwowe guardraile: blokada chronionych ścieżek, formatowanie, ochrona przed wyciekiem sekretów i weryfikacja polityk.

  • Cztery skrypty hooków do skopiowania od razu: protect-paths.sh, guard-commands.sh, post-edit-lint.sh i stop-gate.sh
  • Rejestrację każdego z nich w Claude Code i w Codex oraz zasadę dla Cursora, dopóki jego adapter nie zostanie sprawdzony
  • Komendę z przypadkami testowymi, która dowodzi, że każdy hook przepuszcza, blokuje i zamyka się przy błędzie, więc nikt nie musi czytać skryptu, żeby mu zaufać
  • Trzy prompty do skopiowania: zaprojektuj hook, wygeneruj jego przypadki testowe i zrób audyt tego, co już jest skonfigurowane
  • Tryby awarii, które po cichu wyłączają hook, i sposób wyjścia z każdego z nich

Ta strona opisuje, co jeden deweloper wkłada do hooków. Jak organizacja przegląda, dystrybuuje i wycofuje wspólne hooki, opisuje strona zarządzanie wspólnymi hookami. Hooki są jedną z warstw harnessu, obok uprawnień i sandboksów.

Hook ma sens, gdy reguła jest mechaniczna, tania do sprawdzenia i kosztowna, gdy się o niej zapomni. Umieść każde ryzyko przy najwcześniejszym zdarzeniu, które wciąż może je zatrzymać:

RyzykoHook i zdarzenieCo robi hookCo go dodatkowo zabezpiecza
Agent edytuje pliki generowane, vendorowane albo z sekretamiprotect-paths.sh na PreToolUse dla edycji plikówBlokuje edycję i wskazuje źródło, które należy zmienićReguła deny w uprawnieniach dla tych samych ścieżek
Agent uruchamia komendę produkcyjną albo destrukcyjnąguard-commands.sh na PreToolUse dla narzędzia powłokiBlokuje komendę i prosi, by uruchomił ją człowiekSandbox, brak produkcyjnych poświadczeń w sesji, wdrożenia z CI
Edycja psuje formatowanie albo lintpost-edit-lint.sh na PostToolUse dla edycji plikówFormatuje plik, a błędy lintera oddaje agentowiBramka lint w CI
Diff zawiera poświadczeniestop-gate.sh na StopNie pozwala zakończyć tury, dopóki sekret nie zniknieSkanowanie sekretów w CI i na hostingu Gita
Agent mówi „gotowe” mimo nieprzechodzących kontrolistop-gate.sh na StopRaz uruchamia typecheck i testy, a błędy odsyła agentowiTe same komendy jako wymagane checki CI

Oddziel naprawę od bezpieczeństwa. Formatter, który się wywróci, powinien przepuścić pracę, bo pominięte formatowanie poprawi następne uruchomienie. Strażnik ścieżek albo skaner sekretów, który się wywróci, powinien blokować, bo pominiętej blokady nic potem nie naprawi.

Każda reguła na tej stronie opiera się na jednym kontrakcie: kod wyjścia 2 z powodem na stderr. Skutek zależy od zdarzenia. Tabela pokazuje zachowanie obu CLI sprawdzone 2026-09-26 w dokumentacji hooków Claude Code (v2.1.283) i w kodzie źródłowym Codex z tagu rust-v0.157.1.

ZdarzenieExit 2 w Claude CodeExit 2 w Codex
PreToolUseBlokuje wywołanie narzędzia; stderr trafia do agentaBlokuje wywołanie narzędzia; stderr jest powodem
PostToolUsePokazuje stderr agentowi; narzędzie już się wykonałoOddaje stderr agentowi; narzędzie już się wykonało
StopNie pozwala zakończyć; agent pracuje dalej ze stderr jako informacją zwrotnąKontynuuje turę ze stderr jako promptem kontynuacji
Exit 1, crash, brak skryptu, timeoutBłąd nieblokujący: akcja przechodziHook oznaczony jako nieudany: akcja przechodzi

Najważniejszy jest ostatni wiersz. Żadne narzędzie nie zamyka się przy błędzie za ciebie: hook jest typu fail-closed tylko wtedy, gdy jego własny kod wyłapuje każdą wykrywalną awarię i kończy się kodem 2. Każdy skrypt poniżej tak robi. Timeout nadal przepuszcza akcję, więc prawdziwa granica dla wszystkiego, co niebezpieczne, zostaje w uprawnieniach i sandboksach.

Skrypty zakładają repozytorium TypeScript z zainstalowanymi jq, gitleaks, Prettierem i ESLintem. Dopasuj wzorce ścieżek, komendy i skrypty testowe do swojego projektu; zachowaj strukturę.

  1. Chroń ścieżki przed edycją. Claude Code przekazuje bezwzględną ścieżkę każdego wywołania Edit i Write w tool_input.file_path. Dopasowuj segment ścieżki, a nie początek napisu.

    #!/usr/bin/env bash
    # .claude/hooks/protect-paths.sh — PreToolUse · Edit|Write · blocks edits to protected files
    set -uo pipefail
    block() { printf 'protect-paths: %s\n' "$1" >&2; exit 2; }
    command -v jq >/dev/null 2>&1 || block "jq is missing, so the path cannot be checked."
    path=$(jq -er '.tool_input.file_path // empty' 2>/dev/null) || block "the hook input has no file_path."
    path=${path//\\//} # normalize Windows separators
    case "$path" in
    */src/generated/*|*/vendor/*|*/.env|*/.env.*)
    block "$path is protected. Edit the source it is generated from, or ask a human." ;;
    esac
    exit 0
  2. Blokuj komendy produkcyjne, zanim się wykonają. Claude Code i Codex przekazują komendę powłoki narzędzia Bash w tool_input.command, więc jeden skrypt obsługuje oba narzędzia.

    #!/usr/bin/env bash
    # .agent-hooks/guard-commands.sh — PreToolUse · Bash · Claude Code and Codex
    set -uo pipefail
    block() { printf 'guard-commands: %s\n' "$1" >&2; exit 2; }
    command -v jq >/dev/null 2>&1 || block "jq is missing, so the command cannot be checked."
    cmd=$(jq -er '.tool_input.command // empty' 2>/dev/null) || block "the hook input has no command."
    if printf '%s' "$cmd" | grep -Eq \
    '(terraform|tofu)[[:space:]]+apply|kubectl[[:space:]].*prod|git[[:space:]]+push.*[[:space:]](--force|-f)([[:space:]]|$)|--remote([[:space:]]|$)'; then
    block "this command can change production. Write it in your summary for a human to run."
    fi
    exit 0

    Wzorzec widzi jedną pisownię. Obejdzie go sh -c '…', prefiks ze zmienną albo plik skryptu. Traktuj ten hook jak łapacz literówek, który zapisuje intencję, a prawdziwą granicę trzymaj w sandboksie i w braku produkcyjnych poświadczeń.

  3. Lintuj każdą edycję i oddawaj błędy agentowi. Na PostToolUse edycja już się odbyła, więc exit 2 jej nie cofa: stawia wynik lintera przed agentem, który poprawia go w następnym kroku.

    #!/usr/bin/env bash
    # .claude/hooks/post-edit-lint.sh — PostToolUse · Edit|Write · format, then lint the edited file
    set -uo pipefail
    file=$(jq -r '.tool_input.file_path // empty' 2>/dev/null) || exit 0
    case "$file" in *.ts|*.tsx|*.js|*.jsx) ;; *) exit 0 ;; esac
    bin="$CLAUDE_PROJECT_DIR/node_modules/.bin"
    "$bin/prettier" --write "$file" >/dev/null 2>&1 || true # repair: fail open
    if ! out=$("$bin/eslint" --max-warnings=0 "$file" 2>&1); then
    printf 'ESLint failed on %s. Fix it before continuing:\n%s\n' "$file" "$out" | head -c 4000 >&2
    exit 2
    fi
    exit 0

    Ten hook działa tylko w Claude Code. Codex raportuje edycje plików jako narzędzie apply_patch, którego wejściem jest patch, a nie jedna ścieżka, więc w Codex lint zmienionych plików uruchamia bramka stop.

  4. Nie pozwól zakończyć pracy, dopóki zostały sekrety albo błędy. Zdarzenie Stop odpala się, gdy agent ma zakończyć turę. Exit 2 odsyła go do pracy z twoim komunikatem. Oba narzędzia przekazują stop_hook_active: true, gdy agent pracuje dalej już z powodu hooka stop; użyj tego pola, żeby kontrola, której agent nie umie naprawić, nie wpadła w pętlę. Claude Code dodatkowo unieważnia blokadę po ośmiu kolejnych kontynuacjach (limit podniesiesz zmienną CLAUDE_CODE_STOP_HOOK_BLOCK_CAP; sprawdzone na 2.1.283). Bramka skanuje tylko niezacommitowane zmiany, więc sekret, który agent zapisze w commicie w trakcie tury, jej umyka: jeśli agent może robić commity, hook gitleaks pre-commit z sekcji o ekosystemie poniżej jest obowiązkowy, a nie opcjonalny.

    #!/usr/bin/env bash
    # .agent-hooks/stop-gate.sh — Stop · Claude Code and Codex
    # Secrets always block. Typecheck and tests block once, then hand over to a human.
    set -uo pipefail
    block() { printf 'stop-gate: %s\n' "$1" >&2; exit 2; }
    again=$(jq -r '.stop_hook_active // false' 2>/dev/null || echo false)
    root=$(git rev-parse --show-toplevel 2>/dev/null) || block "not inside a Git repository."
    cd "$root" || block "cannot enter $root."
    command -v gitleaks >/dev/null 2>&1 || block "gitleaks is missing, so the diff cannot be scanned."
    # Tracked changes plus new untracked files. gitleaks exits 1 on a leak or an error: both block.
    if ! { git diff HEAD 2>/dev/null; git ls-files -z --others --exclude-standard | xargs -0 -r cat; } \
    | gitleaks stdin --redact --no-banner >/dev/null 2>&1; then
    block "gitleaks found a credential in your changes. Remove it and read the value from the environment."
    fi
    [ "$again" = "true" ] && exit 0
    if ! out=$( { npm run --silent typecheck && npm run --silent lint && npm test --silent; } 2>&1 ); then
    block "typecheck, lint or tests fail. Fix them before you finish. Last output:
    $(printf '%s' "$out" | tail -n 40)"
    fi
    exit 0
  5. Nadaj skryptom prawo wykonania poleceniem chmod +x .claude/hooks/*.sh .agent-hooks/*.sh i zrób commit. Skrypt bez prawa wykonania to hook, który po cichu nic nie robi.

Skrypty są wspólne, rejestracja nie. Nazwy zdarzeń, matchery i lokalizacje plików się różnią, więc napisz osobną rejestrację dla każdego narzędzia według jego schematu, zamiast kopiować jeden plik między nimi.

Hooki projektu umieść w .claude/settings.json. ${CLAUDE_PROJECT_DIR} wskazuje katalog główny projektu, w którym zaczęła się sesja, więc ścieżki działają z każdego podkatalogu. Timeouty podaje się w sekundach.

{
"hooks": {
"PreToolUse": [
{ "matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-paths.sh", "timeout": 10 }] },
{ "matcher": "Bash",
"hooks": [{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.agent-hooks/guard-commands.sh", "timeout": 10 }] }
],
"PostToolUse": [
{ "matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/post-edit-lint.sh", "timeout": 60 }] }
],
"Stop": [
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.agent-hooks/stop-gate.sh", "timeout": 600 }] }
]
}
}

Wpisz /hooks w sesji, żeby zobaczyć każdy skonfigurowany hook i plik ustawień, z którego pochodzi. Aby zobaczyć, jak hooki się odpalają, uruchom claude --debug-file /tmp/hooks.log (ta flaga sama włącza tryb debugowania) i przeczytaj log; --debug hooks zawęża wyjście debugowania do kategorii hooków (sprawdzone w 2.1.283).

Hook, którego nie widziałeś w akcji, to hook, na który tylko liczysz. Podaj każdemu skryptowi JSON, który wysłałby agent, i porównaj kod wyjścia z kontraktem:

Okno terminala
# Terminal, z katalogu głównego repozytorium. Oczekiwane: 0, 2, 0, 2, 2, 0, 0, 2, 2, 0.
p=.claude/hooks/protect-paths.sh; g=.agent-hooks/guard-commands.sh; c=.agent-hooks/protect-paths-codex.sh
echo '{"tool_input":{"file_path":"/repo/src/app.ts"}}' | $p; echo $?
echo '{"tool_input":{"file_path":"/repo/src/generated/api.ts"}}' | $p; echo $?
echo '{"tool_input":{"file_path":"/repo/src/generator/api.ts"}}' | $p; echo $?
echo 'not json' | $p; echo $?
echo '{"tool_input":{"command":"terraform apply -auto-approve"}}' | $g; echo $?
echo '{"tool_input":{"command":"terraform plan"}}' | $g; echo $?
echo '{"tool_input":{"command":"cat docs/terraform-apply-notes.md"}}' | $g; echo $?
echo '{"tool_input":{}}' | $g; echo $?
echo '{"tool_input":{"command":"*** Begin Patch\n*** Update File: src/generated/api.ts\n*** End Patch"}}' | $c; echo $?
echo '{"tool_input":{"command":"*** Begin Patch\n*** Update File: src/app.ts\n*** End Patch"}}' | $c; echo $?

Potem sprawdź rejestrację, bo poprawny skrypt za złym matcherem nigdy się nie uruchomi. Na roboczej gałęzi poproś agenta o edycję src/generated/api.ts i o uruchomienie terraform apply, i potwierdź, że obie akcje zostały odrzucone z twoim komunikatem. Dla bramki stop zapisz syntetyczny token, który wykrywają twoje reguły gitleaks (nigdy prawdziwy), w niezacommitowanym pliku na gałęzi do wyrzucenia, poproś agenta o zakończenie i potwierdź, że został odesłany do pracy. Bramka stop nie widzi sekretów, które agent już zapisał w commicie. Ten przypadek pokrywają hook gitleaks pre-commit i skanowanie w CI.

Zapisz linie echo | skrypt jako skrypt z przypadkami testowymi i uruchamiaj go w CI obok hooków. To jest dowód, który sprawdza reviewer zamiast skryptu: każdy hook ścieżek i komend ma przypadek przepuszczenia, przypadek blokady, przypadek bliskiego trafienia, który musi przejść, i przypadek z błędnym wejściem kończący się kodem 2. Hook lintujący i bramka stop zależą od stanu repozytorium, a nie od jednej linii JSON, więc ich dowodem jest zapisana sesja: poproś agenta o edycję łamiącą regułę lintera i potwierdź, że błąd wraca do agenta, a dla bramki stop podłóż syntetyczny token jak wyżej.

Dwa pluginy Anthropic z oficjalnego marketplace’u Claude Code zawierają hooki, które inaczej musiałbyś napisać sam. Hooki nie kosztują tokenów kontekstu, dopóki się nie odpalą, dlatego plugin złożony z samych hooków niewiele kosztuje, nawet gdy jest stale zainstalowany.

PluginCo dodajeInstalacja (Claude Code)
hookifyZapisuje reguły hooków jako pliki Markdown na podstawie zwykłej prośby, np. /hookify:hookify Block any rm -rf outside /tmp, z akcją warn albo blockclaude plugin install hookify@claude-plugins-official
security-guidanceOstrzeżenia regex przy edycjach z niebezpiecznymi wzorcami, przegląd diffu przez model na Stop i reviewer przy git commit i git push. Wymaga Claude Code 2.1.144 lub nowszego i Pythona 3.8+claude plugin install security-guidance@claude-plugins-official

Katalog pluginów Anthropic pod adresem claude.com/plugins pokazywał 2026-09-26 241 800 instalacji security-guidance i 60 376 instalacji hookify. Żaden z nich nie zastępuje twoich hooków ścieżek i sekretów: reguły hookify to wzorce z tymi samymi ograniczeniami co guard-commands.sh, a przegląd security-guidance to ocena modelu, a nie deterministyczna kontrola. Dodaj też gitleaks jako hook Git pre-commit. Jest obowiązkowy, jeśli agent może robić commity, bo bramka stop skanuje tylko niezacommitowane zmiany i sekret zapisany w commicie nigdy do niej nie trafia.

Traktuj zmianę hooka jak kod, który działa z twoimi poświadczeniami, bo tak właśnie jest. Autor pisze przypadki testowe; reviewer zatwierdza na podstawie dowodów, a nie czytania każdej linii:

  • Każdy hook odpowiada nazwanemu ryzyku z tabeli powyżej, a jego komunikat mówi, co zrobić zamiast tego.
  • Przypadki testowe przepuszczenia, blokady, bliskiego trafienia i błędnego wejścia przechodzą w CI dla każdego hooka ścieżek i komend, a każda wykrywalna awaria kończy się kodem 2.
  • Hook lintujący i bramka stop mają zapisaną sesję, w której oddają agentowi błąd.
  • Zapisana sesja pokazuje, że każdy blokujący hook odrzuca swój cel w każdym narzędziu, w którym jest zarejestrowany.
  • Żaden hook nie wypisuje sekretów ani pełnej treści komend na stdout, stderr ani do logów.
  • CI egzekwuje te same niezmienniki, więc wyłączony hook kosztuje szybkość, a nie bezpieczeństwo.
  • Zmiany .claude/settings.json i .codex/hooks.json przechodzą review właścicieli kodu, a po każdej zmianie hooki Codex trzeba ponownie oznaczyć jako zaufane.

Bramka jest wyłączona i nikt tego nie zauważył. Literówka w ścieżce, brak jq albo exit 1 dają w obu CLI błąd nieblokujący i praca idzie dalej. Wyjście: uruchom skrypt z przypadkami testowymi, popraw ścieżkę i spraw, by każda wykrywalna awaria kończyła się kodem 2. Po każdej zmianie hooka obserwuj pierwszą sesję, czy nie pojawia się komunikat o błędzie hooka.

Bramka stop wpada w pętlę. Testy padają z powodu, którego agent nie naprawi, np. braku usługi, i każda próba zakończenia jest odsyłana z powrotem. Wyjście: sprawdzaj stop_hook_active tak jak stop-gate.sh, żeby kontrole blokowały raz i oddawały sprawę tobie. Bezwarunkowo blokuj tylko sekrety.

Formatter stał się blokadą. Hook PostToolUse, który kończy się kodem 2 przy awarii Prettiera, zatrzymuje każdą edycję. Wyjście: kroki naprawcze przepuszczają przy błędzie (|| true); kodem 2 kończą się tylko uwagi lintera, na które agent może zareagować.

Hook ujawnia to, czego pilnuje. Linia debugowa wypisuje komendę albo znaleziony sekret do transkryptu lub logu. Wyjście: wypisuj regułę i plik, nigdy wartość; używaj gitleaks --redact i zrotuj każde poświadczenie, które trafiło do logu.

Hook Codex przestał działać po pobraniu zmian (pull). Ktoś z zespołu zmienił .codex/hooks.json, definicja nie zgadza się już z tą, której zaufałeś, i hook jest pomijany, dopóki nie zaufasz mu ponownie. Wyjście: otwórz /hooks, przejrzyj zmianę i oznacz ją jako zaufaną. Jeśli hook musi działać zawsze, poproś zespół platformowy, by dostarczył go jako hook zarządzany, jak opisuje zarządzanie wspólnymi hookami.

CI uruchomiło hooki kontrybutora z twoimi sekretami. Job z agentem pobrał pull request, którego .claude/settings.json dodawał hook, i hook uruchomił się z tokenami joba. Wyjście: pobierz zaufane skrypty z domyślnej gałęzi, commit kontrybutora trzymaj w osobnym katalogu tylko do odczytu i uruchamiaj claude --bare --setting-sources "" --strict-mcp-config (Claude Code 2.1.283). Potem stosuj wzorzec z AI w CI/CD.

Hooki działają wewnątrz granic uprawnień ustalonych w governance i autonomii, a bramka stop to lokalna połowa dowodów, których wymagają etapy build i test. Przepisy zdarzenie po zdarzeniu dla jednego narzędzia znajdziesz w hookach Claude Code w praktyce. Następna strona w sekcji harnessu, skills agentów, opisuje procedury, których hook nie wyegzekwuje. Jak zablokować pliki testów, których agent nie może osłabić, opisuje ochrona wyroczni.