Użyj PreToolUse Hooks do przechwytywania poleceń git w Bash — blokuj bezpośrednie commity do main, złe formaty wiadomości i niebezpieczne operacje.
Claude Code ma uprawnienia do bezpośredniego uruchamiania git commit, git push i git checkout. Zazwyczaj to wygodne — ale oznacza też, że Claude może:
Hooki pozwalają przechwytywać te operacje zanim zostaną wykonane — weryfikować, egzekwować standardy lub całkowicie blokować niebezpieczne działania.
Wszystkie operacje git w Claude Code są wykonywane przez narzędzie Bash. Punkt przechwycenia to:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [...]
}
]
}
}
Hook otrzymuje pełny ciąg komendy przez stdin. Sprawdzamy, czy zawiera konkretne operacje git, i decydujemy — przepuścić czy zablokować.
Podstawowy wzorzec:
#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')
if echo "$cmd" | grep -qE '^git commit'; then
echo "Nie spełnia wymagań" >&2
exit 2 # Blokujemy i powiadamiamy Claude
fi
Najczęstsze wymaganie. Claude powinien pracować na gałęziach feature, a nie pushować bezpośrednio do main.
#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')
echo "$cmd" | grep -qE '^git (commit|push)' || exit 0
current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
if [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
echo "Bezpośrednie commity do $current_branch są niedozwolone. Najpierw przełącz się na gałąź feature." >&2
exit 2
fi
Po otrzymaniu tej wiadomości Claude samodzielnie przełączy się na odpowiednią gałąź.
Wiele zespołów używa Conventional Commits: feat: ..., fix: ..., docs: .... Ale wiadomości Claude nie muszą pasować do Twojego formatu.
Weryfikujemy przed commitem:
#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')
echo "$cmd" | grep -qE '^git commit' || exit 0
# Wyciągamy wiadomość po -m
msg=$(echo "$cmd" | grep -oP '(?<=-m )["\x27].*?["\x27]' | tr -d '"'"'" || true)
if [[ -z "$msg" ]]; then
exit 0 # Brak flagi -m, prawdopodobnie używany jest edytor — pomijamy
fi
if ! echo "$msg" | grep -qE '^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{3,}'; then
echo "Nieprawidłowy format wiadomości commita." >&2
echo "Wymagany format: <type>(<scope>): <description>" >&2
echo "Przykład: feat(auth): add OAuth login" >&2
echo "Dopuszczalne typy: feat | fix | docs | style | refactor | test | chore | perf | ci | build | revert" >&2
exit 2
fi
Claude otrzyma informację zwrotną i przepisze wiadomość commita w prawidłowym formacie.
Chcesz, żeby każdy commit Claude zawierał informację o autorstwie — do śledzenia udziału AI. Ale nie chcesz wpisywać tego ręcznie za każdym razem.
PostToolUse jest tu lepszym rozwiązaniem — sprawdzamy po zakończeniu commita i prosimy Claude o --amend, jeśli brakuje współautorstwa:
#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')
echo "$cmd" | grep -qE '^git commit' || exit 0
echo "$cmd" | grep -q 'amend' && exit 0 # Unikamy nieskończonej pętli
last_msg=$(git log -1 --format="%B" 2>/dev/null)
if ! echo "$last_msg" | grep -q 'Co-Authored-By'; then
echo "Commit nie zawiera współautorstwa." >&2
echo "Uruchom: git commit --amend -m \"\$(git log -1 --format='%s')\" --trailer 'Co-Authored-By: Claude Sonnet 4.6 <[email protected]>'" >&2
exit 2
fi
Niektóre komendy trudno cofnąć:
#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')
# Blokujemy force push do głównych gałęzi
if echo "$cmd" | grep -qE 'git push.*--force(-with-lease)?.*\b(main|master)\b'; then
echo "Force push do main/master jest niedozwolony." >&2
exit 2
fi
# Blokujemy reset --hard (wymagamy jawnego zezwolenia)
if echo "$cmd" | grep -qE 'git reset --hard'; then
echo "git reset --hard to operacja destrukcyjna — zablokowana. Jeśli naprawdę potrzebujesz, dodaj komentarz # ALLOW: z podaniem powodu." >&2
exit 2
fi
# Blokujemy usuwanie zdalnych gałęzi
if echo "$cmd" | grep -qE 'git push.*--delete|git push.*:'; then
echo "Usuwanie zdalnych gałęzi jest niedozwolone. Zrób to ręcznie na GitHubie, jeśli potrzebujesz." >&2
exit 2
fi
Sztuczka z # ALLOW: dla reset --hard pozwala Claude „odblokować" operację, gdy jest naprawdę potrzebna — wymagając przy tym jawnego uzasadnienia.
Rejestrujemy wszystkie operacje git wykonane przez Claude:
#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')
echo "$cmd" | grep -q '^git' || exit 0
echo "$(date '+%Y-%m-%d %H:%M:%S') GIT: $cmd" >> ~/.claude/git-audit.log
Używaj PostToolUse — rejestrujemy tylko faktycznie wykonane komendy.
.claude/hooks/git-guard.sh:
#!/bin/bash
set -e
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
[[ -z "$cmd" ]] && exit 0
echo "$cmd" | grep -q '^git' || exit 0
current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
# 1. Blokujemy commity i pushe do main/master
if echo "$cmd" | grep -qE '^git (commit|push)'; then
if [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
echo "Aktualna gałąź to $current_branch — bezpośrednie commity i pushe są zablokowane. Przełącz się na gałąź feature." >&2
exit 2
fi
fi
# 2. Weryfikacja formatu wiadomości commita
if echo "$cmd" | grep -qE '^git commit.*-m'; then
msg=$(echo "$cmd" | grep -oP '(?<=-m )["\x27][^\x27"]*["\x27]' | tr -d '"'"'" || true)
if [[ -n "$msg" ]] && ! echo "$msg" | grep -qE '^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{3,}'; then
echo "Nieprawidłowy format wiadomości commita." >&2
echo "Format: <type>(<scope>): <description>" >&2
echo "Przykład: fix(api): handle null response from upstream" >&2
exit 2
fi
fi
# 3. Blokujemy niebezpieczne operacje (jeśli nie ma komentarza ALLOW)
if echo "$cmd" | grep -qE 'git push.*--force|git reset --hard|git push.*--delete'; then
if ! echo "$cmd" | grep -q '# ALLOW:'; then
echo "Niebezpieczna operacja zablokowana: $cmd" >&2
echo "Aby kontynuować, dodaj na końcu komendy # ALLOW: <powód>" >&2
exit 2
fi
fi
settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/git-guard.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'cmd=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); echo \"$cmd\" | grep -q \"^git\" && echo \"$(date +\\'%Y-%m-%d %H:%M:%S\\') $cmd\" >> ~/.claude/git-audit.log || true'"
}
]
}
]
}
}
Użyj Bash jako matcher, nie Git
Claude Code nie ma dedykowanego narzędzia Git — wszystkie komendy git przechodzą przez Bash. Matcher musi być "Bash".
Nie polegaj nadmiernie na wyrażeniach regularnych
Komendy git mają wiele form: git commit -m "msg", git commit --message="msg", GIT_AUTHOR_NAME=xxx git commit. Zbyt restrykcyjny regex pominie przypadki brzegowe. Przechwytuj najpowszechniejsze niebezpieczne wzorce i ufaj osądowi Claude w pozostałych.
Konfiguracja globalna vs. projektowa
Ochrona głównej gałęzi i weryfikacja formatu commitów — do projektowego configa (.claude/settings.json), bo nazwy gałęzi i konwencje commitów różnią się między projektami. Dziennik audytu — do globalnego, zapisujemy do wspólnego pliku ~/.claude/git-audit.log.
Zarządzanie przepływem pracy git za pomocą hooków sprowadza się do: PreToolUse + Bash matcher + wykrywanie komend git.
Trzy reguły warte skonfigurowania w pierwszej kolejności:
1. Zakaz bezpośrednich commitów do main
2. Obowiązkowy format wiadomości commitów
3. Blokowanie force push, reset --hard i innych destrukcyjnych operacji
W połączeniu z dziennikiem audytu każde działanie Claude na git staje się możliwe do śledzenia. Zacznij od najprostszej reguły, upewnij się, że działa, a potem dodawaj kolejne.