Przejdź do głównej zawartości

Mistrzostwo systemu hooks

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:

  • Notification - Gdy Claude potrzebuje uprawnienia lub jest bezczynny
  • SubagentStop - Gdy zadania subagenta są kompletne
  • PreCompact - Przed kompresją konwersacji
  1. Otwórz konfigurację hooks

    Okno terminala
    /hooks
    # Wybierz: PreToolUse
  2. Dodaj matcher dla narzędzia Edit

    Matcher: Edit
  3. Dodaj hook formatowania

    Okno terminala
    prettier --write "$CLAUDE_FILE_PATHS" 2>/dev/null || true
  4. 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"
}
]
}
]
}
}

Matchery określają które narzędzia uruchamiają twoje hooks:

WzorzecOpisPrzykład
EditDokładne dopasowanieTylko narzędzie Edit
Edit|WriteWiele narzędziEdit LUB Write
Notebook.*Wzorzec regexNotebookEdit, NotebookRead
"" lub pominięteWszystkie narzędziaKażde wywołanie narzędzia
mcp__.*__.*Narzędzia MCPWszystkie 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ściaEfektPrzypadek użycia
0Sukces, kontynuujNormalny przepływ
2Blokuj z informacją zwrotnąWalidacja nie powiodła się
InneBłąd nie blokującyTylko ostrzeżenie
security-check.sh
#!/bin/bash
if [[ "$1" =~ rm\ -rf ]]; then
echo "Niebezpieczne polecenie zablokowane" >&2
exit 2
fi
exit 0
{
"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'"
}
]
}
]
}
}
pre-edit-security.sh
#!/bin/bash
set -e
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path')
# Zapobiegaj edycji wrażliwych plików
FORBIDDEN_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
fi
done
# Sprawdź sekrety w zawartości
CONTENT=$(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 2
fi
exit 0
  • Folder.claude/
    • Folderhooks/
      • post-edit-typescript.sh
      • pre-bash-validate.sh
      • notification-handler.sh
      • stop-cleanup.sh
post-edit-typescript.sh
#!/bin/bash
# Kompleksowy przepływ pracy TypeScript
set -e
INPUT=$(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
fi
done
notification-handler.sh
#!/bin/bash
# Powiadomienia świadome platformy
INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Claude potrzebuje uwagi"')
# macOS
if command -v osascript &> /dev/null; then
osascript -e "display notification \"$MESSAGE\" with title \"Claude Code\" sound name \"Glass\""
# Linux z notify-send
elif command -v notify-send &> /dev/null; then
notify-send "Claude Code" "$MESSAGE" --urgency=normal --icon=dialog-information
# Windows WSL
elif 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 pliku
echo "[$(date)] $MESSAGE" >> ~/.claude/notifications.log
enhance-prompt.sh
#!/bin/bash
# Dodaj kontekst do promptów użytkownika
INPUT=$(cat)
PROMPT=$(echo "$INPUT" | jq -r '.prompt')
# Dodaj kontekst git
GIT_BRANCH=$(git branch --show-current 2>/dev/null || echo "no-git")
GIT_STATUS=$(git status --porcelain | wc -l)
# Dodaj kontekst systemowy
NODE_VERSION=$(node -v 2>/dev/null || echo "no-node")
# Wypisz wzbogacony prompt
cat <<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 pipefail
trap 'echo "Hook failed: $?" >&2' ERR
# Bezpieczne parsowanie JSON
INPUT=$(cat)
if ! echo "$INPUT" | jq empty 2>/dev/null; then
echo "Nieprawidłowe wejście JSON" >&2
exit 1
fi
# 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:

    Okno terminala
    # Nie blokuj Claude dla niekrytycznych zadań
    {
    sleep 2
    curl -X POST https://webhook.site/...
    } &
  • Cachuj kosztowne operacje:

    Okno terminala
    CACHE_FILE="/tmp/claude-hook-cache-$(date +%Y%m%d)"
    if [[ ! -f "$CACHE_FILE" ]]; then
    expensive_operation > "$CACHE_FILE"
    fi
    cat "$CACHE_FILE"
Okno terminala
# Bezpieczne praktyki hooks
# 1. Waliduj i sanizuj wejście
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path' | sed 's/[^a-zA-Z0-9./_-]//g')
# 2. Używaj ścieżek absolutnych
SAFE_DIR="/home/user/project"
if [[ ! "$FILE_PATH" =~ ^"$SAFE_DIR" ]]; then
echo "Dostęp odmówiony: poza katalogiem projektu" >&2
exit 2
fi
# 3. Cytuj wszystkie zmienne
command "$FILE_PATH" # Źle
command "$FILE_PATH" # Dobrze
# 4. Unikaj eval i injection
eval "$USER_INPUT" # Nigdy tego nie rób
Okno terminala
# Uruchom z wyjściem debug
claude --debug
# Sprawdź wykonanie hooks
[DEBUG] Executing hooks for PostToolUse:Edit
[DEBUG] Hook command completed with status 0
Okno terminala
# Testuj swój hook z przykładowym wejściem
echo '{
"hook_event_name": "PreToolUse",
"tool_name": "Edit",
"tool_input": {
"file_path": "test.js"
}
}' | ./my-hook.sh
ProblemRozwiązanie
Hook się nie uruchamiaSprawdź wzorzec matcher i składnię JSON
Odmowa dostępuSpraw by skrypt był wykonywalny: chmod +x hook.sh
Polecenie nie znalezioneUżywaj ścieżek absolutnych lub sprawdź PATH
Błędy timeoutZwię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:

Okno terminala
# W twoim repo
git add .claude/hooks/ .claude/settings.json
git commit -m "Add Claude Code automation hooks"
git push
# Członkowie zespołu otrzymują automatyzację automatycznie!

Opanuj te zaawansowane wzorce hooks:

  1. Połącz z poleceniami niestandardowymi dla potężnych makr
  2. Integruj z zarządzaniem pamięcią dla hooks świadomych kontekstu
  3. Skonfiguruj konfigurację przedsiębiorstwa dla automatyzacji w całej organizacji