Zarządzanie wspólnymi hookami: kontrole runtime po review
Zarządzanie wspólnymi hookami oznacza traktowanie każdego hooka agenta, który organizacja rozdaje zespołom, jak kodu produkcyjnego: jeden wąski cel, spisany kontrakt, źródło po review przypięte do wersji, przypadki testowe (fixtures) dowodzące zachowania przy przepuszczeniu, blokadzie i awarii, dystrybucja zarządzana, wdrożenie etapowe, logi audytu i przećwiczony rollback. Sama dystrybucja nie jest dojrzałością, bo automatycznie instalowany hook rozniesie defekt równie szybko jak kontrolę.
Ta strona jest dla tech leada lub CTO, który odpowiada za pytanie 18 w CTO Scorecard, i dla inżyniera platformy, który te hooki dostarcza. Sytuacja, której ma zapobiec: zespół platformowy wypchnął na każdy laptop hook chroniący sekrety, połowa zespołu nie miała jq, hook wywracał się przy każdym wywołaniu, agent traktował to jako błąd nieblokujący, a „egzekwowana” kontrola przez trzy tygodnie niczego nie egzekwowała. Nikt nie zauważył, bo hook, który się wywraca, wygląda dokładnie jak hook, który milczy.
Q18 · Przygotowanie organizacji: Jak zarządzacie wspólnymi hookami agentów i ich adapterami per narzędzie?
Odpowiedź na maksymalny wynik: wersjonowane hooki po review, z minimalnymi uprawnieniami, przypadkami testowymi, wdrożeniem etapowym, logami audytu i przetestowanym rollbackiem.
Co daje ci zarządzany program hooków
Dział zatytułowany „Co daje ci zarządzany program hooków”- Szablon kontraktu hooka, który recenzent zatwierdza, zanim powstanie jakikolwiek kod
- Szkielet hooka typu fail-closed, który działa bez zmian w Claude Code i w Codeksie
- Skrypt uruchamiający przypadki testowe, który w CI dowodzi zachowania przy przepuszczeniu, blokadzie i awarii, bez czytania skryptu
- Dystrybucję zarządzaną w każdym narzędziu, dzięki której uruchamia się tylko przypięta wersja
- Etapy wdrożenia, cztery metryki z właścicielami i ćwiczenie rollbacku do przeprowadzenia w tym kwartale
- Trzy prompty do skopiowania: napisz kontrakt, zaatakuj implementację i wygeneruj przypadki testowe
Perspektywę dewelopera, czyli co w ogóle wkładać do hooka, opisuje strona hooki jako deterministyczne zabezpieczenia (guardrails). Ta strona dotyczy wyłącznie warstwy organizacyjnej: kto dostarcza hooki, jak się ich działanie udowadnia i jak się je wycofuje.
Dlaczego wspólne hooki wymagają ostrzejszego nadzoru niż reguły czy skills?
Dział zatytułowany „Dlaczego wspólne hooki wymagają ostrzejszego nadzoru niż reguły czy skills?”Reguła albo skill to rada, którą czyta model. Hook to program, który uruchamia się z uprawnieniami dewelopera w stałym punkcie pętli agenta, niezależnie od tego, co zdecyduje model. Wspólny hook staje się więc częścią granicy wykonania: hook z defektem blokuje pracę wszystkim naraz, a przejęty hook uruchamia się na każdej maszynie, która go zainstalowała.
Drugi powód jest mniej oczywisty. W obu CLI większość sposobów, w jakie hook może zawieść, przepuszcza akcję. Tabela pokazuje zachowanie obu narzędzi sprawdzone 2026-09-26.
| Zachowanie | Claude Code 2.1.283 (kanał latest, sprawdzone 2026-09-26) | Codex 0.157.1 |
|---|---|---|
Co blokuje wywołanie PreToolUse | Kod wyjścia 2 (stderr staje się powodem) albo JSON z permissionDecision: "deny" | Kod wyjścia 2 z niepustym powodem na stderr albo decyzja blokująca w JSON |
Kod wyjścia 1 lub inny niezerowy | Błąd nieblokujący; wywołanie narzędzia przechodzi | Hook oznaczony jako nieudany; wywołanie przechodzi |
| Brak skryptu lub brak prawa wykonania | Błąd nieblokujący; wywołanie przechodzi, więc, jak ostrzega dokumentacja Anthropic, literówka w ścieżce w settings.json po cichu wyłącza bramkę | Ten sam skutek: uruchomienie kończy się błędem i niczego nie blokuje |
| Timeout | Wynik odrzucony, brak decyzji; na zawieszonym hooku nie da się oprzeć bramki | Hook oznaczony jako nieudany; brak blokady |
| Domyślny timeout komendy | 600 sekund | 600 sekund |
| Zmieniona definicja hooka | Nie zapisuje zaufania dla pojedynczych hooków; o tym, co się uruchamia, decydują scalone ustawienia | Niezarządzany hook ze zmienioną definicją dostaje status zmodyfikowanego i nie uruchamia się, dopóki ktoś ponownie mu nie zaufa |
Źródła: dokumentacja hooków Claude Code oraz kod Codex w tagu rust-v0.157.1 (codex-rs/hooks/src/events/pre_tool_use.rs, codex-rs/hooks/src/engine/discovery.rs). Cursor uruchamia hooki jako „spawned processes that communicate over stdio using JSON in both directions”, które „can observe, block, or modify behavior” (dokumentacja hooków Cursora, sprawdzone 2026-08-28). Jego obecnej semantyki błędów nie dało się ponownie zweryfikować na potrzeby tej strony, więc sprawdź ją opisanym niżej skryptem z przypadkami testowymi, zanim zaczniesz na niej polegać.
„Fail closed” nie jest więc ustawieniem. To coś, co musi zrobić kod samego hooka: przechwycić każdą awarię, którą potrafi wykryć, i zakończyć się kodem 2 z powodem. A ponieważ timeout czy crash i tak przepuszczają akcję, hook jest zabezpieczeniem, a nie granicą bezpieczeństwa. Tą granicą jest warstwa uprawnień i sandboxa opisana w uprawnieniach i sandboxingu.
Napisz kontrakt hooka, zanim napiszesz hook
Dział zatytułowany „Napisz kontrakt hooka, zanim napiszesz hook”Każdy wspólny hook zaczyna się od kontraktu po review. Commitujesz go obok kodu i scalasz w tym samym review co hook. Skopiuj ten szablon bez zmian.
# guard-env-files — contract
Owner: platform team (#platform-hooks). Version: 3. Status: blocking.Mode switch: GUARD_MODE=advisory logs would-be blocks and exits 0.
## Purpose (one sentence)Stop agent shell commands that read or write `.env` files.
## TriggerEvent: PreToolUse. Matcher: the shell tool ("Bash" in Claude Code and Codex).
## InputReads only `tool_input.command`. Treats the whole input as untrusted text:never evaluates it, never interpolates it into a shell.
## AuthorityNo network. No credentials. Reads nothing but stdin.Writes one log line per run to a local file, never to stdout.
## DecisionBlock (exit 2, reason on stderr) when the command references a `.env` file.Allow (exit 0, no output) otherwise.
## Failure mode: fail closedMissing jq, unreadable input, or a missing command field → exit 2 with a reason.Timeout: 10 s. A timeout fails open in every supported tool, so thepermission deny rule on `.env` files stays the real boundary.
## User-visible messageNames the hook, its version, why it blocked, and the doc to read.
## LogsOne JSON line per run in $HOOK_LOG (default~/.local/state/company-hooks/hooks.jsonl): hook, version, event, mode,decision, reason_code, session_id, duration_ms. Never the command text. Retention: 30 days.
## RollbackManaged settings point back to v2. Drill: quarterly. Last drill: (date).Kontrakt odpowiada na pytania, które recenzent musiałby inaczej wyczytywać ze skryptu: czego hook może dotknąć, co się dzieje, gdy się zepsuje, i jak go wyłączyć. Szablon zostaje po angielsku, bo trafia do repozytorium obok kodu.
Zbuduj jeden rdzeń, który przy awarii blokuje (fail-closed)
Dział zatytułowany „Zbuduj jeden rdzeń, który przy awarii blokuje (fail-closed)”Oba CLI przekazują na stdin ten sam JSON dla wywołania powłoki w PreToolUse, z komendą w tool_input.command, i oba blokują przy kodzie 2 z powodem na stderr. Jeden skrypt obsługuje więc oba narzędzia, a różni się tylko rejestracja. To ten sam wzorzec co we wspólnych regułach agentów: jeden rdzeń i cienkie, przetestowane adaptery.
#!/usr/bin/env bash# hooks/guard-env-files/hook.sh — v3 · PreToolUse · shell tool · owner: platform teamset -uo pipefailMODE=${GUARD_MODE:-blocking} # "advisory" logs would-be blocks and exits 0LOG=${HOOK_LOG:-$HOME/.local/state/company-hooks/hooks.jsonl}ms() { local t=${EPOCHREALTIME:-}; if [ -n "$t" ]; then t=${t/[.,]/}; echo $((t / 1000)); else echo $(($(date +%s) * 1000)); fi; }start=$(ms); sid=unknown
# One JSON line per run, appended to a file: never stdout, never the command text.log() { mkdir -p "${LOG%/*}" 2>/dev/null printf '{"hook":"guard-env-files","version":3,"event":"PreToolUse","mode":"%s","decision":"%s","reason_code":"%s","session_id":"%s","duration_ms":%s}\n' \ "$MODE" "$1" "$2" "$sid" "$(($(ms) - start))" >>"$LOG" 2>/dev/null || true}block() { log block "$1" printf 'guard-env-files v3: %s See docs/hooks/guard-env-files.md\n' "$2" >&2 [ "$MODE" = advisory ] && exit 0 exit 2}
command -v jq >/dev/null 2>&1 || block missing_jq "jq is missing, so the check cannot run."input=$(cat)sid=$(jq -r '.session_id // "unknown"' <<<"$input" 2>/dev/null | tr -cd '[:alnum:]_-'); sid=${sid:-unknown}jq -e . >/dev/null 2>&1 <<<"$input" || block malformed_input "the hook input is not valid JSON."cmd=$(jq -er '.tool_input.command // empty' <<<"$input" 2>/dev/null) || block no_command "the hook input has no command field."
env_re='(^|[[:space:]/="'\''(<>;|&])\.env(\.[[:alnum:]_-]+)?([[:space:]"'\'';|&)<>]|$)'if printf '%s' "$cmd" | grep -Eq "$env_re"; then block env_file "a shell command touching a .env file was blocked. Read secrets through the secrets manager instead."filog allow noneexit 0Cztery cechy odróżniają ten hook od skryptu, który ktoś kiedyś napisał. Każda wykrywalna awaria kończy się kodem 2 z komunikatem, więc laptop bez jq blokuje głośno, zamiast po cichu przepuszczać. Wejście jest zawsze czytane jako dane: nic z niego nie trafia do eval ani do niecytowanego rozwinięcia, a wzorzec łapie też ścieżkę .env, po której stoi separator powłoki, więc cat .env; ls i cat .env&&ls również są blokowane. Każde uruchomienie dopisuje jedną linię JSON do lokalnego pliku logu, nigdy na stdout i nigdy z treścią komendy, i z tych linii liczy się metryki wdrożenia opisane niżej. A komunikat mówi deweloperowi, co się stało i dokąd pójść, i właśnie to powstrzymuje ludzi przed szukaniem obejścia. GUARD_MODE=advisory zachowuje logowanie i komunikat, ale kończy się kodem 0; to pierwszy etap wdrożenia.
Udowodnij działanie hooka przypadkami testowymi, a nie czytaniem kodu
Dział zatytułowany „Udowodnij działanie hooka przypadkami testowymi, a nie czytaniem kodu”Recenzent nie powinien musieć czytać hook.sh, żeby wiedzieć, że hook działa. Każdy przypadek testowy to katalog z wejściowym JSON-em, oczekiwanym kodem wyjścia, opcjonalnie frazą, którą musi zawierać powód na stderr, i opcjonalnie znacznikiem expect-stdout-empty dla przypadków, które nie mogą niczego wypisać. Skrypt podaje każde wejście do hooka dokładnie tak, jak zrobiłby to agent, przerywa go po 10 sekundach, czyli po timeoucie z kontraktu, i kieruje log do pliku tymczasowego, żeby CI nigdy nie pisało do katalogu domowego.
#!/usr/bin/env bash# hooks/run-fixtures.sh — usage: hooks/run-fixtures.sh hooks/guard-env-files# Each fixture dir holds input.json, expect-exit, and optionally expect-stderr# (a phrase the reason must contain) and expect-stdout-empty (a marker file).set -uhook_dir=$1; failed=0tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXITexport GUARD_MODE=blocking HOOK_LOG="$tmp/hooks.jsonl"for f in "$hook_dir"/fixtures/*/; do name=$(basename "$f") # 10 s is the contract timeout: a hook that needs longer fails here with exit 124. out=$(timeout 10 "$hook_dir/hook.sh" < "$f/input.json" 2>"$tmp/stderr"); code=$? want=$(cat "$f/expect-exit") if [ "$code" != "$want" ]; then echo "FAIL $name: exit $code, expected $want"; failed=1; continue fi if [ -f "$f/expect-stderr" ] && ! grep -qF "$(cat "$f/expect-stderr")" "$tmp/stderr"; then echo "FAIL $name: stderr did not contain the expected reason"; failed=1; continue fi if [ -f "$f/expect-stdout-empty" ] && [ -n "$out" ]; then echo "FAIL $name: the hook wrote to stdout"; failed=1; continue fi echo "ok $name"doneexit $failedUruchamiaj go jako wymagany check CI w repozytorium hooków. Minimalny zestaw przypadków testowych dla każdego hooka blokującego:
| Przypadek | Wejście | Oczekiwany wynik |
|---|---|---|
allow-common | Zwykła komenda, np. ls -la src | Kod 0, brak wyjścia (expect-stdout-empty) |
deny-target | Przypadek, dla którego hook istnieje, np. cat .env.production | Kod 2, stderr podaje nazwę hooka i powód |
deny-chained | Przypadek docelowy, po którym stoi separator powłoki, np. cat .env && ls | Kod 2 |
allow-near-miss | Coś podobnego, ale dozwolonego, np. cat .envrc | Kod 0 (łapie nadmierne blokowanie) |
malformed-input | not json | Kod 2 (dowód fail-closed) |
missing-field | {"tool_input":{}} | Kod 2 |
injection | Komenda z $(…), backtickami i cudzysłowami | Ta sama decyzja co dla prostej formy; nic się nie wykonuje |
slow-dependency | Uruchomienie z zależnością podmienioną na zawieszającą się | Kończy się w 10-sekundowym timeoucie z kontraktu; jeśli nie, skrypt zgłasza kod 124 |
Skrypt dowodzi działania rdzenia. Żeby udowodnić działanie adaptera, przepuść te same przypadki testowe raz przez każde narzędzie w roboczym repozytorium i potwierdź, że blokada dociera do agenta. Tylko tak złapiesz błędny matcher albo rejestrację, która nigdy się nie ładuje.
Dystrybuuj hooki tak, żeby uruchamiała się tylko przypięta wersja
Dział zatytułowany „Dystrybuuj hooki tak, żeby uruchamiała się tylko przypięta wersja”Miejsce instalacji hooka decyduje o tym, kto może go zmienić. Hook w pliku ustawień repozytorium zmienia się za każdym razem, gdy ktoś scali zmianę tego pliku; hook zarządzany zmienia się tylko wtedy, gdy zespół platformowy opublikuje nową wersję. Dla kontroli, które muszą działać, używaj kanału zarządzanego i wersjonowanej ścieżki. Przejście z v2 na v3 jest wtedy jednolinijkową zmianą do review, a powrót to ta sama linia.
Wdrażaj przez ustawienia zarządzane (managed settings): plik managed-settings.json (/etc/claude-code/ na Linuksie i WSL, /Library/Application Support/ClaudeCode/ na macOS, C:\Program Files\ClaudeCode\ na Windowsie), MDM albo ustawienia zarządzane z serwera w konsoli claude.ai w planach Team i Enterprise.
{ "allowManagedHooksOnly": true, "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "/opt/company-hooks/guard-env-files/v3/hook.sh", "timeout": 10 } ] } ] }}allowManagedHooksOnly blokuje hooki użytkownika, projektu, lokalne i z pluginów, z wyjątkiem pluginów wymuszonych w ustawieniach zarządzanych. disableAllHooks ustawione w ustawieniach użytkownika lub projektu nie wyłączy hooków zarządzanych; mogą to zrobić tylko ustawienia zarządzane. Zanim zatwierdzisz plugin, który dostarcza hooki, sprawdź jego zawartość przez claude plugin details <name>. Żeby zobaczyć, jak hooki się ładują i uruchamiają, startuj sesję z claude --debug hooks.
Codex czyta hooki z pliku hooks.json w katalogu warstwy konfiguracji (na przykład ~/.codex/ albo .codex/ w zaufanym projekcie) lub z tabeli [hooks] w config.toml. Struktura JSON jest taka sama jak w Claude Code, więc działa ta sama rejestracja:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "/opt/company-hooks/guard-env-files/v3/hook.sh", "timeout": 10 } ] } ] }}Niezarządzane hooki wymagają zapisanego zaufania, zanim się uruchomią, a zaufanie jest powiązane z hashem definicji hooka. To ma dwie konsekwencje, które musisz zaplanować. Podbicie wersji w hooks.json repozytorium wyłącza hook na każdej maszynie, dopóki każdy deweloper nie zaufa mu ponownie. A edycja skryptu, na który wskazuje zaufany hook, nie zmienia definicji, więc zmieniony skrypt uruchamia się bez ponownego zaufania; przypinaj wersjonowane ścieżki, żeby każda zmiana treści była zmianą definicji. Dla kontroli, które muszą działać, używaj zarządzanego przez administratorów requirements.toml: managed_hooks deklaruje hooki, które mają status zarządzanych i zaufanych, a allow_managed_hooks_only = true ignoruje hooki użytkownika, projektu, sesji i z pluginów (sprawdzone w Codex 0.157.1). To ustawienie działa tylko w requirements.toml, nie w config.toml. Sprawdzaj, co jest załadowane, poleceniem /hooks w sesji. --dangerously-bypass-hook-trust istnieje dla automatyzacji, która sama weryfikuje źródła hooków; nigdy nie używaj go na checkoucie, którego nie wytworzyłeś.
Cursor obsługuje hooki i dystrybuuje je w pluginach, które „package rules, skills, agents, commands, MCP servers, and hooks into distributable bundles” (dokumentacja pluginów Cursora, sprawdzone 2026-08-28). Nazw zdarzeń, pliku konfiguracji, egzekwowania przez administratora ani zachowania przy błędach nie dało się ponownie zweryfikować we wrześniu 2026, więc ta strona ich nie podaje.
Zachowaj ten sam rdzeń i kontrakt, napisz cienki adapter dla Cursora według aktualnej dokumentacji hooków Cursora i nie oznaczaj go jako blokującego, dopóki powyższy zestaw przypadków testowych nie przejdzie przez prawdziwą sesję Cursora. Do tego czasu uruchamiaj go w trybie doradczym.
Wdrażaj etapami i mierz cztery rzeczy
Dział zatytułowany „Wdrażaj etapami i mierz cztery rzeczy”Hook, który od pierwszego dnia blokuje pracę całej firmie, uczy ludzi, jak go obchodzić. Promuj go przez kolejne etapy i pozwól, żeby o każdej promocji decydowały metryki, a nie kalendarz.
- Tryb doradczy, jeden zespół. Uruchamiaj hook z
GUARD_MODE=advisory, na przykład przez dwulinijkowy wrapper pod wersjonowaną ścieżką, który eksportuje tę zmienną i wywołujehook.sh, więc działa tak samo w każdym narzędziu. Hook loguje to, co by zablokował, i kończy się kodem0. Działa tydzień lub dwa, a ty czytasz każdą niedoszłą blokadę. - Blokowanie, jeden zespół. Przełącz na kod
2dla zespołu ochotników. Kanał zgłaszania wyjątków opublikuj przed przełączeniem. - Blokowanie, pół organizacji. Rozszerz, gdy odsetek fałszywych blokad i opóźnienie przez dwa kolejne tygodnie mieszczą się w ustalonych progach.
- Blokowanie, wszyscy. Włącz
allowManagedHooksOnly(Claude Code) luballow_managed_hooks_only(Codex), gdy wszystkie potrzebne kontrole są zarządzane, żeby lokalnie dodane hooki nie mogły ich zasłonić ani zdublować. - Ćwiczenie rollbacku. Przed udostępnieniem wszystkim, a potem co kwartał, przestaw ustawienie zarządzane na poprzednią wersję i potwierdź, że klienci ją przejęli.
Każda metryka ma właściciela i próg, który sam wybierasz; to definicje startowe, nie benchmarki.
| Metryka | Definicja | Sygnał problemu |
|---|---|---|
| Odsetek fałszywych blokad | Blokady uchylone zatwierdzonym wyjątkiem ÷ wszystkie blokady, tygodniowo | Rosnący odsetek oznacza zbyt szeroką regułę; ludzie zaczną ją obchodzić |
| Odsetek błędów hooka | Uruchomienia zakończone kodem niezerowym innym niż 2 lub timeoutem ÷ wszystkie uruchomienia | Utrzymujące się błędy oznaczają, że kontrola gdzieś po cichu nie działa |
| Opóźnienie p95 | 95. percentyl duration_ms dla zdarzenia | Hook uruchamia się przy każdym pasującym wywołaniu narzędzia, więc opóźnienie mnoży się w całej sesji |
| Znalezione obejścia | Naruszenia wykryte później przez CI, review lub skanowanie sekretów, które hook powinien był zablokować | Każde z nich to brakujący przypadek testowy; dodaj go, zanim zmienisz regułę |
Linia logu z kontraktu pozwala policzyć te metryki bez czytania transkryptów: hook, version, event, mode, decision, reason_code, session_id i duration_ms, bez treści komendy.
Kto zatwierdza hook i co jest dowodem?
Dział zatytułowany „Kto zatwierdza hook i co jest dowodem?”Nikt nie zatwierdza hooka na podstawie samego czytania kodu. Zatwierdzenie opiera się na artefaktach, które recenzent sprawdzi w kilka minut:
- Kontrakt jest scalony, z właścicielem i wersją.
- Skrypt z przypadkami testowymi przechodzi w CI dla rdzenia, a każdy adapter ma zapisany przebieg przez swoje prawdziwe narzędzie.
- Konfiguracja zarządzana przypina wersjonowaną ścieżkę, a zmiana w niej przeszła review code ownera.
- Log z etapu doradczego zawiera listę niedoszłych blokad, przeczytaną i zatwierdzoną przez zespół-właściciela.
- Ostatnie ćwiczenie rollbacku ma datę, a czas od decyzji do przywrócenia klientów jest zapisany.
Właściciel hooka zatwierdza kontrakt i przypadki testowe; lider bezpieczeństwa lub platformy zatwierdza promocję do blokowania i do trybu tylko zarządzanych hooków. Ten podział odpowiada modelowi nadzoru i autonomii agentów: hook jest zabezpieczeniem w pętli agenta, a CI pozostaje bramką, bez której nic nie trafia do gałęzi głównej.
Co się psuje, gdy zespół dzieli hooki?
Dział zatytułowany „Co się psuje, gdy zespół dzieli hooki?”Bramka po cichu nie działa. Zła ścieżka, brakująca zależność albo kod wyjścia 1 dają w obu CLI błąd nieblokujący, a praca toczy się dalej. Naprawa: dodaj przypadki testowe malformed-input i missing-field, niech własne awarie hooka kończą się kodem 2, i alarmuj na podstawie odsetka błędów hooka, zamiast czekać, aż ktoś to zauważy.
Wadliwe wydanie blokuje całą firmę. Wydanie v4 poszerza wzorzec tak, że łapie też src/environment.ts, i każda sesja agenta staje. Naprawa: przestaw ustawienie zarządzane z powrotem na v3, i właśnie dlatego ścieżka jest wersjonowana. W Claude Code disableAllHooks we własnych ustawieniach dewelopera nie wyłączy hooka zarządzanego, więc kanał zarządzany jest jedyną szybką drogą rollbacku; przećwicz ją, zanim będzie potrzebna.
Hook w Codeksie zgasł po aktualizacji. Podbicie wersji w hooks.json repozytorium zmieniło hash definicji i nikt nie zaufał hookowi ponownie. Naprawa: przenieś hooki, które muszą działać, do managed_hooks w requirements.toml; hooki w repozytorium zostaw dla udogodnień, których brak nic nie kosztuje.
Hook uruchomił niezaufany kod w CI. Job z agentem zrobił checkout gałęzi kontrybutora, a hooki z jej .claude/settings.json uruchomiły się z sekretami joba. Naprawa: zaufaną konfigurację pobieraj z gałęzi domyślnej, kod kontrybutora podawaj agentowi wyłącznie do odczytu, a Claude Code uruchamiaj w tym przebiegu z --bare --setting-sources "" --strict-mcp-config, żeby nie załadowały się żadne hooki, ustawienia ani serwery MCP z checkoutu. Samo --settings '{"disableAllHooks": true}' nie wystarczy: serwery MCP i reguły uprawnień z checkoutu nadal działają. Pełny wzorzec opisuje strona AI w CI/CD.
Jeden hook rośnie, aż robi wszystko. Pojedynczy hook „polityki” obrasta w sprawdzanie sekretów, formatowanie i wyszukiwanie ticketów, dostaje dostęp do sieci i token, i nie da się go już testować ani wycofywać po kawałku. Naprawa: podziel go według celu, jeden kontrakt na hook, a wszystko, co wymaga stałego połączenia lub poświadczeń, przenieś do wewnętrznego serwera MCP po review.
Dokąd dalej ze wspólnymi hookami
Dział zatytułowany „Dokąd dalej ze wspólnymi hookami”Umieść hooki w kontekście reszty wspólnego środowiska agenta (harness): wspólne reguły agentów dla instrukcji i wspólne skills dla procedur. Jedną politykę dla wszystkich uruchamianych agentów opisuje strona polityka zarządzana dla agentów kodujących, a zagrożenia, które hooki pomagają ograniczać, model zagrożeń agenta.