PreToolUse
Przed wykonaniem narzędzia - Waliduj, blokuj lub modyfikuj wywołania narzędzi przed ich uruchomieniem. Idealne do kontroli bezpieczeństwa i walidacji wejścia.
System hooks Claude Code (wprowadzony w lipcu 2025) umożliwia potężną automatyzację sterowaną zdarzeniami. Poprzez wykonywanie niestandardowych poleceń w określonych punktach przepływu pracy Claude, możesz egzekwować standardy, automatyzować kontrole jakości i tworzyć zaawansowane przepływy pracy rozwoju - wszystko bez ręcznej interwencji.
Hooks to polecenia shell, które wykonują się automatycznie gdy zachodzą określone zdarzenia w Claude Code. Myśl o nich jak o callback lifecycle, które pozwalają wstrzyknąć niestandardowe zachowanie do operacji Claude.
PreToolUse
Przed wykonaniem narzędzia - Waliduj, blokuj lub modyfikuj wywołania narzędzi przed ich uruchomieniem. Idealne do kontroli bezpieczeństwa i walidacji wejścia.
PostToolUse
Po zakończeniu narzędzia - Reaguj na udane operacje. Idealne do formatowania, powiadomień i działań następczych.
UserPromptSubmit
Gdy użytkownik wysyła prompt - Przetwarzaj lub wzbogacaj prompty zanim Claude je zobaczy. Dodawaj kontekst lub waliduj żądania.
Stop
Gdy Claude kończy - Czyszczenie, raportowanie lub uruchamianie następnych kroków po zakończeniu odpowiedzi Claude.
Dodatkowe zdarzenia:
Otwórz konfigurację hooks
/hooks# Wybierz: PreToolUse
Dodaj matcher dla narzędzia Edit
Matcher: Edit
Dodaj hook formatowania
prettier --write "$CLAUDE_FILE_PATHS" 2>/dev/null || true
Zapisz do ustawień projektu
Wybierz .claude/settings.json
dla udostępnienia zespołowi
Teraz każdy plik edytowany przez Claude będzie automatycznie sformatowany!
// .claude/settings.json (udostępniane z zespołem){ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "prettier --write \"$CLAUDE_FILE_PATHS\"", "timeout": 5000 } ] } ], "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "/path/to/security-check.sh" } ] } ] }}
// ~/.claude/settings.json (osobiste){ "hooks": { "Notification": [ { "hooks": [ { "type": "command", "command": "osascript -e 'display notification \"Claude potrzebuje uwagi\" with title \"Claude Code\"'" } ] } ] }}
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "/opt/security/audit-command.sh" } ] } ] }}
Matchery określają które narzędzia uruchamiają twoje hooks:
Wzorzec | Opis | Przykład |
---|---|---|
Edit | Dokładne dopasowanie | Tylko narzędzie Edit |
Edit|Write | Wiele narzędzi | Edit LUB Write |
Notebook.* | Wzorzec regex | NotebookEdit, NotebookRead |
"" lub pominięte | Wszystkie narzędzia | Każde wywołanie narzędzia |
mcp__.*__.* | Narzędzia MCP | Wszystkie narzędzia serwera MCP |
Hooks otrzymują JSON przez stdin:
{ "session_id": "abc123", "transcript_path": "/path/to/conversation.jsonl", "cwd": "/current/working/directory", "hook_event_name": "PreToolUse", "tool_name": "Edit", "tool_input": { "file_path": "/src/index.js", "content": "// Zawartość pliku tutaj" }}
Hooks komunikują się przez kody wyjścia i output:
Kod wyjścia | Efekt | Przypadek użycia |
---|---|---|
0 | Sukces, kontynuuj | Normalny przepływ |
2 | Blokuj z informacją zwrotną | Walidacja nie powiodła się |
Inne | Błąd nie blokujący | Tylko ostrzeżenie |
#!/bin/bashif [[ "$1" =~ rm\ -rf ]]; then echo "Niebezpieczne polecenie zablokowane" >&2 exit 2fiexit 0
#!/bin/bashINPUT=$(cat)TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
if [[ "$TOOL_NAME" == "Edit" ]]; then cat <<EOF{ "decision": "approve", "reason": "Edit zatwierdzony po walidacji", "suppressOutput": true}EOFelse cat <<EOF{ "decision": "block", "reason": "Tylko narzędzie Edit dozwolone w tym kontekście"}EOFfi
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "sh -c 'if [[ \"$CLAUDE_FILE_PATHS\" =~ \\.(js|ts|jsx|tsx)$ ]]; then prettier --write \"$CLAUDE_FILE_PATHS\" && eslint --fix \"$CLAUDE_FILE_PATHS\"; fi'" } ] }, { "matcher": "Edit", "hooks": [ { "type": "command", "command": "sh -c 'if [[ \"$CLAUDE_FILE_PATHS\" =~ \\.(py)$ ]]; then black \"$CLAUDE_FILE_PATHS\" && ruff check --fix \"$CLAUDE_FILE_PATHS\"; fi'" } ] } ] }}
#!/bin/bashset -e
INPUT=$(cat)FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path')
# Zapobiegaj edycji wrażliwych plikówFORBIDDEN_PATTERNS=( "*.env" "*.key" "*.pem" "**/secrets/*" "**/config/production.json")
for pattern in "${FORBIDDEN_PATTERNS[@]}"; do if [[ "$FILE_PATH" == $pattern ]]; then echo "Polityka bezpieczeństwa zapobiega edycji $FILE_PATH" >&2 exit 2 fidone
# Sprawdź sekrety w zawartościCONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // empty')if echo "$CONTENT" | grep -qE '(api_key|password|secret).*=.*["\x27]'; then echo "Wykryto potencjalny sekret w kodzie" >&2 exit 2fi
exit 0
#!/bin/bash# Kompleksowy przepływ pracy TypeScript
set -eINPUT=$(cat)FILES=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
for FILE in $FILES; do if [[ "$FILE" =~ \.(ts|tsx)$ ]]; then # Formatuj prettier --write "$FILE"
# Sprawdź typy npx tsc --noEmit --skipLibCheck "$FILE" || { echo "Błędy TypeScript w $FILE - proszę przejrzyj" # Nie failuj, tylko ostrzeż }
# Zaktualizuj testy jeśli potrzeba TEST_FILE="${FILE%.ts}.test.ts" if [[ -f "$TEST_FILE" ]]; then echo "Pamiętaj o aktualizacji testów: $TEST_FILE" fi
# Generuj/aktualizuj dokumentację npx typedoc --emit none --validation "$FILE" 2>/dev/null || true fidone
#!/bin/bash# Powiadomienia świadome platformy
INPUT=$(cat)MESSAGE=$(echo "$INPUT" | jq -r '.message // "Claude potrzebuje uwagi"')
# macOSif command -v osascript &> /dev/null; then osascript -e "display notification \"$MESSAGE\" with title \"Claude Code\" sound name \"Glass\""
# Linux z notify-sendelif command -v notify-send &> /dev/null; then notify-send "Claude Code" "$MESSAGE" --urgency=normal --icon=dialog-information
# Windows WSLelif command -v powershell.exe &> /dev/null; then powershell.exe -Command " Add-Type -AssemblyName System.Windows.Forms \$notification = New-Object System.Windows.Forms.NotifyIcon \$notification.Icon = [System.Drawing.SystemIcons]::Information \$notification.BalloonTipIcon = 'Info' \$notification.BalloonTipTitle = 'Claude Code' \$notification.BalloonTipText = '$MESSAGE' \$notification.Visible = \$true \$notification.ShowBalloonTip(5000) "fi
# Również loguj do plikuecho "[$(date)] $MESSAGE" >> ~/.claude/notifications.log
#!/bin/bash# Dodaj kontekst do promptów użytkownika
INPUT=$(cat)PROMPT=$(echo "$INPUT" | jq -r '.prompt')
# Dodaj kontekst gitGIT_BRANCH=$(git branch --show-current 2>/dev/null || echo "no-git")GIT_STATUS=$(git status --porcelain | wc -l)
# Dodaj kontekst systemowyNODE_VERSION=$(node -v 2>/dev/null || echo "no-node")
# Wypisz wzbogacony promptcat <<EOF{ "prompt": "$PROMPT\n\nKontekst: Gałąź=$GIT_BRANCH, Niezacommitowane=$GIT_STATUS plików, Node=$NODE_VERSION", "continue": true}EOF
{ "hooks": { "PostToolUse": [ { "matcher": "Edit", "hooks": [ { "type": "command", "command": "sh -c 'if [ \"$CI\" = \"true\" ]; then echo \"Pomijanie formatowania w CI\"; else prettier --write \"$CLAUDE_FILE_PATHS\"; fi'" } ] } ] }}
{ "hooks": { "PreToolUse": [ { "matcher": "mcp__database__.*", "hooks": [ { "type": "command", "command": "/opt/audit/log-database-access.sh" } ] } ], "PostToolUse": [ { "matcher": "mcp__github__create_pr", "hooks": [ { "type": "command", "command": "slack-cli send --channel #dev-prs --text 'Nowy PR utworzony przez Claude'" } ] } ] }}
#!/bin/bash# Solidny szablon hook
set -euo pipefailtrap 'echo "Hook failed: $?" >&2' ERR
# Bezpieczne parsowanie JSONINPUT=$(cat)if ! echo "$INPUT" | jq empty 2>/dev/null; then echo "Nieprawidłowe wejście JSON" >&2 exit 1fi
# Główna logika z fallbackami{ # Twoja logika hook tutaj :} || { echo "Niekrytyczny błąd, kontynuuję" >&2 exit 0 # Nie blokuj Claude}
Ustaw timeouty aby zapobiec zawieszeniu:
{ "type": "command", "command": "your-command", "timeout": 5000 // 5 sekund}
Uruchamiaj async gdy możliwe:
# Nie blokuj Claude dla niekrytycznych zadań{ sleep 2 curl -X POST https://webhook.site/...} &
Cachuj kosztowne operacje:
CACHE_FILE="/tmp/claude-hook-cache-$(date +%Y%m%d)"if [[ ! -f "$CACHE_FILE" ]]; then expensive_operation > "$CACHE_FILE"ficat "$CACHE_FILE"
# Bezpieczne praktyki hooks
# 1. Waliduj i sanizuj wejścieFILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path' | sed 's/[^a-zA-Z0-9./_-]//g')
# 2. Używaj ścieżek absolutnychSAFE_DIR="/home/user/project"if [[ ! "$FILE_PATH" =~ ^"$SAFE_DIR" ]]; then echo "Dostęp odmówiony: poza katalogiem projektu" >&2 exit 2fi
# 3. Cytuj wszystkie zmiennecommand "$FILE_PATH" # Źlecommand "$FILE_PATH" # Dobrze
# 4. Unikaj eval i injectioneval "$USER_INPUT" # Nigdy tego nie rób
# Uruchom z wyjściem debugclaude --debug
# Sprawdź wykonanie hooks[DEBUG] Executing hooks for PostToolUse:Edit[DEBUG] Hook command completed with status 0
# Testuj swój hook z przykładowym wejściemecho '{ "hook_event_name": "PreToolUse", "tool_name": "Edit", "tool_input": { "file_path": "test.js" }}' | ./my-hook.sh
Problem | Rozwiązanie |
---|---|
Hook się nie uruchamia | Sprawdź wzorzec matcher i składnię JSON |
Odmowa dostępu | Spraw by skrypt był wykonywalny: chmod +x hook.sh |
Polecenie nie znalezione | Używaj ścieżek absolutnych lub sprawdź PATH |
Błędy timeout | Zwiększ timeout lub zoptymalizuj skrypt |
{ "hooks": { "PreToolUse": [ { "matcher": "Edit", "hooks": [ { "type": "command", "command": ".claude/hooks/pre-edit-checks.sh" } ] } ], "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": ".claude/hooks/format-and-lint.sh" }, { "type": "command", "command": ".claude/hooks/run-affected-tests.sh" } ] } ], "Stop": [ { "hooks": [ { "type": "command", "command": ".claude/hooks/generate-commit-message.sh" } ] } ] }}
Udostępnij hooks z zespołem:
# W twoim repogit add .claude/hooks/ .claude/settings.jsongit commit -m "Add Claude Code automation hooks"git push
# Członkowie zespołu otrzymują automatyzację automatycznie!
Opanuj te zaawansowane wzorce hooks: