Hooks w Claude Code — jak wymuszam dyscyplinę na agencie
Hooks to mały kawałek konfiguracji który zamienia Claude Code w narzędzie z prawdziwymi gardami. Pokazuję moje setupy: blokowanie destruktywnych komend, wymuszanie commit policy, telemetria do Telegrama.

Claude Code z hookami to nie ten sam Claude Code. Hooks to skrypty które uruchamiają się na konkretnych zdarzeniach, przed użyciem narzędzia, po użyciu, na zakończenie sesji, na każdy prompt. Nie są frameworkiem. Są punktem integracji który odróżnia "agent w terminalu" od "agent z dyscypliną".
Po trzech miesiącach mam ~10 aktywnych hooków. Pokażę cztery które dają największy zwrot.
Hook #1: blokowanie destruktywnych komend
PreToolUse na tool Bash. Jeśli komenda zawiera określone wzorce, odmawiamy.
#!/usr/bin/env bash
# .claude/hooks/block-destructive.sh
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // ""')
forbidden=(
"rm -rf /"
"git push --force"
"DROP DATABASE"
":(){ :|:& };:"
)
for pattern in "${forbidden[@]}"; do
if [[ "$cmd" == *"$pattern"* ]]; then
echo '{"decision":"block","reason":"Destructive command blocked"}'
exit 0
fi
done
echo '{"decision":"approve"}'Drobiazg w setttings:
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": ".claude/hooks/block-destructive.sh" }]
}]
}
}Co to daje: agent może próbować, hook blokuje. Nie polegam na tym że "model nigdy nie zrobi rm -rf". Polegam na tym że gdyby spróbował, hook go zatrzyma.
Hook #2: commit policy — zawsze, bez pytania
Mam preferencję: po każdej zmianie kodu Claude robi commit. Nie pyta, nie czeka. To jest zapisane w mojej memory, ale memory to soft constraint. Hook to hard constraint.
Stop hook który sprawdza czy są niezacommitowane zmiany i przypomina:
#!/usr/bin/env bash
# .claude/hooks/commit-reminder.sh
cd "$(jq -r '.cwd' <<< "$(cat)")" || exit 0
if git diff --quiet && git diff --cached --quiet; then
exit 0 # czysto
fi
cat <<EOF
{
"decision": "block",
"reason": "Niezacommitowane zmiany. Zacommituj przed zakończeniem sesji."
}
EOFEfekt: jeśli agent kończy z brudnym tree, dostaje feedback "wróć i zacommituj". To eliminuje 90% sytuacji w których po sesji znajduję uncommited zmiany.
Hook #3: telemetria do Telegrama
Każde użycie narzędzia loguje się jako event. Najciekawsze dla mnie: ile tool calli per sesja, jaki rozkład, ile czasu zajęło.
# .claude/hooks/telemetry.sh
event=$(cat)
tool=$(jq -r '.tool_name' <<< "$event")
when=$(date -Iseconds)
echo "$when $tool" >> ~/logs/claude-tools.log
# Co 100 calli — pingnij Telegram
count=$(wc -l < ~/logs/claude-tools.log)
if (( count % 100 == 0 )); then
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
-d "chat_id=${ADMIN_CHAT}" \
-d "text=Claude przekroczył ${count} tool calli dzisiaj 🔧"
fiPo co to? Wcześniej nie miałem intuicji ile agent realnie wykonuje. Teraz widzę: typowa sesja produktywna to ~80 calli, sesja "model się zapętla" to 200+. Mogę reagować.
Hook #4: walidacja przed pushem
PreToolUse na bash z matcherem git push. Sprawdza:
- czy branch nie jest
mainprzy force pushu - czy testy przechodzą lokalnie
- czy nie ma plików
.env*w stage'u
#!/usr/bin/env bash
input=$(cat)
cmd=$(jq -r '.tool_input.command' <<< "$input")
# Force push do main = ZAWSZE blok
if [[ "$cmd" == *"push --force"* && "$cmd" == *"main"* ]]; then
echo '{"decision":"block","reason":"Force push to main is forbidden"}'
exit 0
fi
# Sekrety w stage
if git diff --cached --name-only 2>/dev/null | grep -qE '\.env'; then
echo '{"decision":"block","reason":"Staged .env file detected"}'
exit 0
fi
echo '{"decision":"approve"}'Hook nie testuje wszystkiego. Testuje tylko klasyczne wpadki które mnie kiedyś kosztowały. Jeden hook, trzy reguły, każda warta swojego linijkowego kosztu.
Czego NIE wkładam w hooki
Trzy rzeczy uczyłem się nie wpychać:
1. Logiki biznesowej. Hook ma być filtrem albo telemetrią. Jeśli zaczynam pisać if/else z 5 warunkami biznesowymi, to znak że to powinien być osobny skrypt który agent wywołuje świadomie.
2. Czegoś co wymaga dużej latencji. Hook blokuje narzędzie. Jeśli hook trwa 5 sekund, agent czeka. Mam regułę: hook < 200ms. Większe rzeczy (np. test suite przed pushem) odpalam jako rekomendację a nie blok.
3. Reguł które zmieniam co tydzień. Każdy hook to mała umowa z agentem. Zmieniana często, mylimy się oboje. Memory + CLAUDE.md są lepsze do soft preferences.
Setup w pigułce
.claude/
├── settings.json # rejestr hooków
└── hooks/
├── block-destructive.sh
├── commit-reminder.sh
├── telemetry.sh
└── validate-push.shKażdy hook to ~20 linii bash + jq. Bez frameworka. Bez build stepu. To co lubię w Claude Code: hooki są bash scriptami, nie kodem aplikacyjnym.
Jeśli korzystasz z Claude Code i hookami się jeszcze nie bawiłeś, zacznij od block-destructive.sh. Najmniejszy wysiłek, największy zwrot. Jak sam mówię agentowi: "ostre narzędzia, miękkie ręce".