Przez ostatnie wpisy budowaliśmy coraz bardziej autonomicznego Copilota – instrukcje, skills, agenci, MCP serwery. Ale jest jedno fundamentalne ograniczenie wszystkich tych narzędzi: polegają na tym że AI coś zrobi, bo tak powiedziałeś. Copilot może zapomnieć uruchomić lintera. Agent może pominąć testy. Może – bo opiera się na rozumowaniu probabilistycznym.
Hooks rozwiązują ten problem nie przez lepsze instrukcje, ale przez deterministyczne wymuszenie. Hook nie prosi agenta żeby sformatował kod – hook po prostu uruchamia formatter i koniec, niezależnie od tego co agent myśli. Hook nie instruuje agenta żeby nie wykonywał niebezpiecznych komend – hook blokuje je na poziomie systemu zanim agent zdąży je wykonać.
To jest zmiana jakościowa w podejściu do automatyzacji: od "proszę rób X" do "X zawsze się wykona".
Hook to plik JSON w katalogu .github/hooks/ który mapuje zdarzenia sesji agenta na komendy shellowe. Gdy zdarzenie nastąpi – komenda się wykona. Synchronicznie, deterministycznie, niezależnie od tego co AI sobie myśli.
⚠️ Instrukcja / Skill / Agent
Opiera się na rozumowaniu AI
Probabilistyczny – może zadziałać, może nie
AI może "zapomnieć" lub pominąć krok
Nie może zablokować akcji agenta
Idealne dla złożonej logiki i kontekstu
✅ Hook
Wykonuje się poza modelem AI
Deterministyczny – odpala się zawsze
Nie zależy od "pamięci" agenta
Może blokować akcje agenta (preToolUse)
Idealne dla quality gates i guardrails
🔍 Gdzie działają Hooks – dwa środowiska
Środowisko
Jak czyta konfigurację
Uwagi
Copilot Coding Agent (GitHub.com)
Z pliku .github/hooks/*.json na domyślnej gałęzi
Zmiana hooka = commit na main/master. Wszystkie zmiany przechodzą przez code review.
Copilot CLI (lokalnie w VS Code)
Z .github/hooks/*.json w bieżącym katalogu roboczym
Jeden plik konfiguracyjny działa w obu środowiskach – brak duplikacji.
Konfiguracja repozytorium jako granica polityki – tak jak GitHub Actions. Hooks wchodzą do repozytorium razem z kodem, są reviewowane i rollbackowane przez git revert. To filozofia "infrastructure as code" zastosowana do polityki agenta.
Osiem zdarzeń – kiedy co się odpala
Hooks reagują na osiem zdarzeń z cyklu życia sesji agenta. Zrozumienie kiedy każde z nich odpala się jest kluczem do wyboru właściwego hooka dla danego zadania.
sessionStartMEDIUM
Sesja agenta się zaczyna lub wznawia. Idealne do: inicjalizacji środowiska, logowania startu sesji, walidacji stanu projektu (czy są wymagane narzędzia?), ustawiania zmiennych środowiskowych.
userPromptSubmittedMEDIUM
Użytkownik wysyła prompt do agenta. Treść promptu dostępna przez JSON na stdin. Idealne do: audytu promptów pod kątem compliance, skanowania w poszukiwaniu sekretów lub PII w zapytaniach, logowania dla enterprise governance.
preToolUse🛡 BLOKUJE
Najsilniejszy hook. Odpala się przed każdym wywołaniem narzędzia przez agenta (bash, edit, terminal...). Jeśli hook zwróci kod niezerowy – narzędzie nie zostanie wykonane. Idealne do: blokowania niebezpiecznych komend, ochrony krytycznych plików, wymuszania approval dla operacji destruktywnych (DROP TABLE, rm -rf).
postToolUseHIGH
Po każdym wykonaniu narzędzia przez agenta. Dostępny wynik operacji. Idealne do: automatycznego formatowania kodu po edycji pliku, logowania każdej operacji narzędziowej, alertowania o nieoczekiwanych zmianach.
agentStopHIGH
Główny agent kończy odpowiedź na prompt. Idealne do: uruchomienia pełnego zestawu linterów i formattersów, walidacji kompletności zmian, uruchomienia testów po zakończeniu implementacji.
subagentStopLOW
Subagent kończy pracę i zwraca wyniki do głównego agenta. Idealne do: audytu outputów subagentów, logowania aktywności w złożonych workflows z orkiestracją agentów.
errorOccurredMEDIUM
Podczas wykonania agenta wystąpił błąd. Idealne do: logowania błędów dla debugowania, wysyłania alertów do Slack/Teams, zbierania metryk o awariach agenta.
sessionEndMEDIUM
Sesja agenta kończy się lub jest przerywana. Idealne do: czyszczenia plików tymczasowych, generowania raportów aktywności, wysyłania powiadomień o zakończeniu sesji, archiwizacji logów.
Anatomia pliku hooks.json
Każdy hook to plik JSON umieszczony w katalogu .github/hooks/. Możesz mieć wiele plików – wszystkie są ładowane automatycznie. Nazwa pliku nie ma znaczenia, liczy się zawartość.
.github/ └── hooks/ ├── dotnet-lint.json ← Twój główny hook jakości kodu ├── security-gate.json ← preToolUse blokujący niebezpieczne operacje ├── audit-log.json ← logowanie sesji dla compliance └── scripts/ ├── dotnet-lint.sh ← skrypt bashowy (Linux/macOS) ├── dotnet-lint.ps1 ← skrypt PowerShell (Windows) └── security-check.sh
🔍 Pola konfiguracji każdego wpisu hooka
Pole
Wymagane
Opis
type
Tak
Zawsze "command" – jedyny obsługiwany typ.
bash
Bash lub PS
Komenda lub ścieżka do skryptu na Unix/Linux/macOS.
powershell
Bash lub PS
Komenda lub ścieżka do skryptu na Windows. Podaj oba żeby hook działał cross-platform.
cwd
Nie
Katalog roboczy względem katalogu głównego repo. Domyślnie root repo.
timeoutSec
Nie
Maks. czas wykonania w sekundach (domyślnie 30). Hook jest ubijany po przekroczeniu – agent kontynuuje.
env
Nie
Dodatkowe zmienne środowiskowe dla tego hooka. Mergowane z istniejącym środowiskiem.
Zasada blokowania: hook wychodzi z kodem niezerowym → akcja zablokowana (tylko dla preToolUse). Dla pozostałych eventów niezerowy exit kod jest logowany, ale agent kontynuuje.
Agent jest zawieszony podczas wykonania hooka. Zbyt wolny hook = irytująco długie oczekiwanie przy każdej operacji. Dobra zasada: postToolUse i preToolUse max 15 sekund, agentStop max 60 sekund. Wszystko co dłuższe – rozważ uruchomienie asynchronicznie w tle lub przeniesienie do sessionEnd.
.NET
Gotowy hook dla .NET developera – lintowanie przy każdym commicie
To jest mój główny dodatek do tego wpisu. Zamiast omawiać abstrakcyjne przykłady z Prettier i ESLint (które dla .NET developera są równie użyteczne co instrukcja obsługi kosiarki) – daję Ci gotowy, kompletny hook dla projektu .NET.
Hook uruchamia się po każdym zakończeniu odpowiedzi agenta (agentStop). Sprawdza formatowanie przez dotnet format, uruchamia analizę przez Roslyn Analyzers i – opcjonalnie – szybki build weryfikacyjny. Jeśli cokolwiek nie przejdzie, agent jest blokowany od dalszej pracy do naprawy.
Na Windows ten krok nie jest potrzebny, ale upewnij się że PowerShell execution policy pozwala na uruchomienie skryptów: Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Bonus – hook blokujący niebezpieczne operacje
Drugi gotowy hook – tym razem preToolUse który blokuje agenta przed wykonaniem destruktywnych komend. Szczególnie przydatny gdy agent ma dostęp do terminala i może uruchamiać dowolne polecenia.
#!/usr/bin/env bash
# Blokuje niebezpieczne komendy. Czyta JSON z stdin (opis narzędzia).
set -euo pipefail
INPUT=$(cat)
# Wyciągnij nazwę narzędzia i parametry z JSON
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName // empty' 2>/dev/null || echo "")
TOOL_INPUT=$(echo "$INPUT" | jq -r '.toolInput // empty' 2>/dev/null || echo "")
# Działamy tylko gdy narzędzie to terminal/bash/shell
if [[ "$TOOL_NAME" != "terminal" && "$TOOL_NAME" != "bash" && "$TOOL_NAME" != "run_in_terminal" ]]; then
exit 0
fi
# Lista zabronionych wzorców komend
BLOCKED_PATTERNS=(
"DROP TABLE"
"DROP DATABASE"
"TRUNCATE"
"rm -rf /"
"rm -rf \*"
"del /f /s /q C:\\\\"
"format c:"
"git push --force"
"git push -f"
"> /dev/sd"
"sudo rm"
)
for PATTERN in "${BLOCKED_PATTERNS[@]}"; do
if echo "$TOOL_INPUT" | grep -qi "$PATTERN"; then
echo ""
echo "🚫 SECURITY GATE ZABLOKOWAŁ operację:"
echo " Wzorzec: $PATTERN"
echo " Komenda: $TOOL_INPUT"
echo ""
echo " Jeśli chcesz wykonać tę operację – zrób to ręcznie,"
echo " poza sesją agenta."
echo ""
exit 1
fi
done
# Chronione pliki – agent nie może ich edytować
PROTECTED_FILES=(
"*.sln"
"global.json"
"NuGet.Config"
".github/workflows/*.yml"
)
for PATTERN in "${PROTECTED_FILES[@]}"; do
if echo "$TOOL_INPUT" | grep -qi "$PATTERN"; then
echo ""
echo "🛡️ SECURITY GATE: Plik chroniony – $PATTERN"
echo " Modyfikuj go ręcznie jeśli to naprawdę potrzebne."
echo ""
exit 1
fi
done
exit 0
🔍 Jak hook preToolUse otrzymuje kontekst?
Zanim agent wykona narzędzie, Copilot przekazuje do skryptu hooka JSON przez stdin. Struktura tego JSONa zawiera m.in.:
{
"toolName": "terminal",
"toolInput": "dotnet ef database drop --force",
"sessionId": "abc-123",
"timestamp": "2026-03-11T10:30:00Z"
}
Twój skrypt czyta ten JSON przez cat, parsuje przez jq i podejmuje decyzję: exit 0 = zezwól, exit 1 = zablokuj. Prosto, niezawodnie, bez AI w pętli decyzyjnej.
Najlepsze praktyki
1. Warstwuj hooki – nie rób monolitu
Każdy plik JSON to osobna odpowiedzialność. Linting osobno, security osobno, audit logging osobno. Ułatwia to debugowanie (wiesz który hook się wywalił), code review i selektywne wyłączanie.
2. Używaj set -euo pipefail w skryptach bash
Bez tego skrypt może "przejść" mimo błędu w jednym kroku. -e zatrzymuje przy pierwszym błędzie, -u traktuje niezdefiniowane zmienne jako błąd, -o pipefail propaguje błąd przez pipe.
3. Testuj lokalnie zanim wrzucisz do repo
Uruchom skrypt ręcznie z terminala zanim go scommitujesz. Hook który się crashuje przy każdej operacji agenta to szybka droga do frustracji całego teamu.
4. Ustaw realistyczne timeouty
Domyślne 30 sekund to za mało dla dotnet build na większym projekcie. Za dużo dla prostego sprawdzenia formatowania. Dopasuj do rzeczywistego czasu wykonania + 20% margines.
5. Dostarczaj czytelne komunikaty błędów
Agent widzi output hooka i może na jego podstawie zaproponować naprawę. "❌ Formatowanie niezgodne z .editorconfig – uruchom: dotnet format" jest lepsze niż "Error code 1". Im więcej kontekstu w błędzie tym lepiej agent rozumie co naprawić.
💪 Zadanie dla Ciebie – wdróż quality gate
Stwórz katalogi .github/hooks/ i .github/hooks/scripts/
Wklej trzy pliki: dotnet-quality-gate.json, dotnet-quality-gate.sh, dotnet-quality-gate.ps1
Dostosuj DOTNET_SOLUTION w pliku JSON do nazwy swojego .sln
Na Linux/macOS: chmod +x .github/hooks/scripts/dotnet-quality-gate.sh
Przetestuj skrypt ręcznie z terminala – upewnij się że dotnet format i dotnet build są dostępne
Scommituj i poproś agenta o celowe "pobrudnienie" formatowania – obserwuj czy hook go złapie i zablokuje
Podsumowanie
Hooks to ostatni element który odróżnia "Copilota z ładną konfiguracją" od prawdziwego, enterprise-grade środowiska agentic development – gdzie standardy są wymuszane kodem, a nie apelami do AI.
✅ Hooks są deterministyczne – wykonują się zawsze, niezależnie od rozumowania agenta. Nie proszą, wymuszają.
✅ Osiem zdarzeń – od sessionStart do sessionEnd. Najsilniejszy: preToolUse który może blokować wykonanie narzędzia przez agenta.
✅ Jeden plik JSON – dwa środowiska: Copilot Coding Agent (GitHub.com) i Copilot CLI (lokalnie w VS Code). Commituj do .github/hooks/ i działa wszędzie.
✅ Cross-platform przez bash + powershell – jeden hook działa na Linux/macOS i Windows.
✅ Gotowy Security Gate: preToolUse który blokuje destruktywne komendy i chroni pliki konfiguracyjne przed modyfikacją przez agenta.
W kolejnym wpisie wychodzimy poza VS Code i patrzymy na szerszy obraz: Agentic Workflows – jak Copilot Coding Agent działa autonomicznie na GitHub.com, jak kompilować naturalne języki do GitHub Actions i jak zautomatyzować cały pipeline od issue do PR.