W poście #2 i #3 wspomnieliśmy hooks jako jedną z warstw rozszerzeń Claude Code. W poście #5 omówiliśmy skills — teraz czas na hooks.

Czym jest hook

Hook to polecenie shell, które Claude Code wykonuje w odpowiedzi na zdarzenie: przed lub po wywołaniu narzędzia, przy wysyłaniu notyfikacji, przy zatrzymaniu agenta. W odróżnieniu od skills (wywoływanych jawnie), hooks uruchamiają się bez udziału użytkownika.

Prosty przykład: agent skończył implementację i chce zacommitować — ale hook przechwytuje git commit, uruchamia testy i blokuje operację dopóki nie będą zielone. Zamiast liczyć na to, że agent "pamięta" o testach (instrukcja w CLAUDE.md), hook blokuje commit na poziomie runtime. To jest sedno hooków: twarde, automatyczne reguły zamiast miękkich instrukcji behawioralnych (pełny przykład — wzorzec 4).

Typy hooków

graph TD
    E[Zdarzenie agentyczne] --> PT{PreToolCall}
    E --> N{Notification}
    E --> ST{Stop}
    PT -->|OK| TC[Wykonanie narzędzia]
    PT -->|BLOCKED| X[Zablokowano]
    TC --> PO{PostToolCall}
Typy hooków w Claude Code
TypKiedy się uruchamiaPrzykład zastosowania
PreToolCallPrzed wywołaniem narzędzia. Może zablokować wywołanie (exit code ≠ 0); zmodyfikować parametry lub zalogować operacjęBlokada git push --force lub rm -rf — agent nie wykona destrukcyjnej operacji; nawet jeśli sam zdecyduje; że chce ją uruchomić
PostToolCallPo wywołaniu narzędzia. Może uruchomić dodatkowe operacje (linter; formatter); zalogować wynik lub wysłać notyfikacjęPo każdej edycji pliku .py automatycznie uruchom ruff format — agent pisze kod; hook formatuje; wynik jest zawsze spójny ze stylem projektu
NotificationGdy agent chce zakomunikować coś użytkownikowiPrzekierowanie notyfikacji do Slacka lub powiadomień desktopowych — przydatne gdy agent pracuje w tle; a Ty robisz coś innego
StopGdy agent kończy pracę (sukces lub porażka)Wysłanie wiadomości na Slacka »agent skończył pracę nad repo X« lub zapis podsumowania sesji do loga — wiesz kiedy wrócić do rezultatów

Konfiguracja hooks w settings.json

Hooks konfiguruje się w settings.json:

  • Projektowy: .claude/settings.json
  • Globalny: ~/.claude/settings.json
{
  "hooks": {
    "PreToolCall": [
      {
        "matcher": "Bash(git commit*)",
        "command": "pytest tests/ -x -q || (echo 'BLOCKED: tests failed' && exit 1)"
      }
    ],
    "PostToolCall": [
      {
        "matcher": "Edit(*.go)",
        "command": "gofmt -w $CLAUDE_FILE_PATH"
      }
    ],
    "Notification": [
      {
        "command": "notify-send 'Claude Code' \"$CLAUDE_NOTIFICATION\""
      }
    ],
    "Stop": [
      {
        "command": "echo \"$(date '+%H:%M') Agent finished\" >> ~/.claude/agent.log"
      }
    ]
  }
}

matcher to wzorzec dopasowania zdarzenia — format NarzędzieName(wzorzec). Brak matchera oznacza, że hook reaguje na każde zdarzenie danego typu. Więcej przykładów dla każdego typu — w sekcji Praktyczne wzorce.

Zmienne środowiskowe w hooks

Hooks mają dostęp do zmiennych opisujących kontekst zdarzenia:

Zmienne środowiskowe dostępne w hooks
ZmiennaWartość
$CLAUDE_TOOL_NAMENazwa narzędzia (np. »Edit«; »Bash«)
$CLAUDE_FILE_PATHŚcieżka pliku (dla Edit/Write)
$CLAUDE_COMMANDPolecenie (dla Bash)
$CLAUDE_EXIT_CODEKod wyjścia narzędzia (dla PostToolCall)
$CLAUDE_NOTIFICATIONTreść notyfikacji (dla Notification)

Praktyczne wzorce hooks

Wzorzec 1: Auto-formatting

Po każdej edycji pliku — uruchom formatter:

{
  "PostToolCall": [
    {
      "matcher": "Edit(*.py)",
      "command": "ruff format $CLAUDE_FILE_PATH && ruff check --fix $CLAUDE_FILE_PATH"
    },
    {
      "matcher": "Edit(*.{ts,tsx,js,jsx})",
      "command": "npx prettier --write $CLAUDE_FILE_PATH"
    },
    {
      "matcher": "Edit(*.go)",
      "command": "gofmt -w $CLAUDE_FILE_PATH"
    }
  ]
}

Agent pisze kod, hook automatycznie formatuje. Agent nie musi znać konfiguracji formattera — hook wymusza spójność niezależnie od tego, co agent wygeneruje.

Wzorzec 2: Guardrails bezpieczeństwa

Blokuj niebezpieczne operacje:

{
  "PreToolCall": [
    {
      "matcher": "Bash(git push --force*)",
      "command": "echo 'BLOCKED: force push requires manual approval' && exit 1"
    },
    {
      "matcher": "Bash(git reset --hard*)",
      "command": "echo 'BLOCKED: hard reset requires manual approval' && exit 1"
    },
    {
      "matcher": "Bash(docker rm*)",
      "command": "echo 'BLOCKED: container removal requires manual approval' && exit 1"
    },
    {
      "matcher": "Edit(.env*)",
      "command": "echo 'BLOCKED: editing .env files is forbidden for AI agent' && exit 1"
    }
  ]
}

Nawet w trybie "allow all" agent nie może wykonać destrukcyjnych operacji. Defense in depth — nie polegasz na instrukcji w CLAUDE.md, tylko na twardym bloku runtime.

Wzorzec 3: Audit log

Loguj każde wywołanie narzędzia:

{
  "PreToolCall": [
    {
      "command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') PRE $CLAUDE_TOOL_NAME\" >> ~/.claude/audit.log"
    }
  ],
  "PostToolCall": [
    {
      "command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') POST $CLAUDE_TOOL_NAME exit=$CLAUDE_EXIT_CODE\" >> ~/.claude/audit.log"
    }
  ]
}

Pełna historia operacji agenta. Przydatne do debugowania ("dlaczego agent zrobił X?") i compliance ("co agent modyfikował?").

Wzorzec 4: Testy przed commitem

Zablokuj commit, jeśli testy nie przechodzą:

{
  "PreToolCall": [
    {
      "matcher": "Bash(git commit*)",
      "command": "cd $(git rev-parse --show-toplevel) && python -m pytest tests/ -x -q --tb=short 2>&1 | tail -5; test ${PIPESTATUS[0]} -eq 0 || (echo 'BLOCKED: unit tests failed — fix before committing' && exit 1)"
    }
  ]
}

Agent kończy implementację, wywołuje git commit — hook przechwytuje to zdarzenie i uruchamia testy. Jeśli którykolwiek test nie przejdzie, commit jest blokowany. Agent widzi output z pytest, naprawia kod i próbuje ponownie. Commit przejdzie dopiero gdy wszystkie testy będą zielone.

Jeśli w projekcie są zarówno szybkie testy jednostkowe, jak i wolne testy integracyjne, ogranicz hook do szybkiego podzbioru: pytest tests/unit/ -x -q. Pełny suite odpal ręcznie lub w CI.

Wzorzec 5: Integracja z CI/CD

Wyślij notyfikację po zakończeniu pracy agenta:

{
  "Stop": [
    {
      "command": "curl -s -X POST https://hooks.slack.com/services/T.../B.../... -d '{\"text\": \"Claude Code finished working on $(basename $(pwd))\"}'"
    }
  ]
}

Lub triggeruj pipeline CI po pushu:

{
  "PostToolCall": [
    {
      "matcher": "Bash(git push*)",
      "command": "gh workflow run ci.yml --ref $(git branch --show-current)"
    }
  ]
}

Skills + Hooks = Custom workflow

Najsilniejsze rozszerzenia łączą skills i hooks. Przykład: workflow "safe deploy":

Skill: /deploy

---
name: deploy
description: Użyj gdy użytkownik prosi o deploy na produkcję
---

# deploy

1. Sprawdź czy branch jest aktualny z main (git fetch && git status)
2. Uruchom pełny test suite: pytest && pnpm run typecheck
3. Zbuduj Docker image: docker/build.sh
4. Wypchnij obraz: docker push ghcr.io/...
5. Zaktualizuj deployment: kubectl set image ...
6. Monitoruj rollout: kubectl rollout status ...
7. Pokaż użytkownikowi status

Hooks: guardrails dla deploy

{
  "PreToolCall": [
    {
      "matcher": "Bash(kubectl set image*)",
      "command": "git diff --quiet HEAD main || (echo 'BLOCKED: branch not synced with main' && exit 1)"
    }
  ],
  "PostToolCall": [
    {
      "matcher": "Bash(kubectl rollout status*)",
      "command": "curl -s -X POST $SLACK_WEBHOOK -d '{\"text\": \"Deploy complete: $(kubectl get deployment -o jsonpath={.metadata.name})\"}'"
    }
  ]
}

Skill definiuje co robić. Hooks definiują guardrails i automatyzacje. Razem tworzą powtarzalny, bezpieczny workflow.

Permissions — najsilniejsza warstwa kontroli

Oprócz hooks, Claude Code ma system uprawnień w settings.json:

{
  "permissions": {
    "allow": [
      "Bash(npm test)",
      "Bash(npm run build)",
      "Bash(pytest*)",
      "Bash(git status)",
      "Bash(git diff*)",
      "Bash(git log*)",
      "Edit(src/**)",
      "Edit(tests/**)",
      "Read(**)"
    ],
    "deny": [
      "Edit(.env*)",
      "Edit(*.pem)",
      "Bash(curl*)",
      "Bash(wget*)"
    ]
  }
}

allow — operacje dozwolone bez pytania o zgodę. Zmniejsza liczbę approval prompts.

deny — operacje zawsze zablokowane. Silniejsze niż hooks (nie da się obejść).

Trzy warstwy kontroli w Claude Code:

Warstwy kontroli — od najsilniejszej do najsłabszej
WarstwaMechanizmCharakterystyka
1Permissions (deny)Twarda blokada — nie do obejścia
2Hooks (PreToolCall)Programowalna blokada z logiką
3CLAUDE.mdInstrukcja behawioralna (agent może zignorować)