La note Hooks Claude Code couvre PreToolUse et PostToolUse — des hooks qui se déclenchent avant et après chaque action individuelle d’un outil. Ces hooks gèrent les préoccupations par opération : formater un fichier SQL après une modification, bloquer une commande dangereuse avant son exécution.
Deux autres points du cycle de vie des hooks servent des objectifs différents. Stop se déclenche lorsque Claude finit de répondre — après que toutes les modifications, commandes et raisonnements sont terminés pour ce tour. SessionStart se déclenche une fois au démarrage d’une session Claude Code. Ensemble, ils gèrent la validation de qualité en fin de tour et l’initialisation de session.
Hooks Stop : portes de qualité après chaque tour
Le hook Stop s’exécute après que la réponse complète de Claude est terminée. Pensez-y comme un mini-CI qui s’exécute localement, immédiatement après chaque tour. C’est le bon endroit pour les vérifications trop lentes ou trop larges pour s’exécuter après chaque modification individuelle de fichier, mais trop importantes pour être ignorées.
Le hook Stop le plus pratique pour le travail dbt : compiler les modèles modifiés pour détecter les erreurs de syntaxe avant que Claude ne passe à la suite.
Créez .claude/hooks/dbt-quality-check.sh :
#!/usr/bin/env bashset -euo pipefail
# Check if any SQL files were modifiedmodified_sql=$(git diff --name-only HEAD 2>/dev/null | grep "\.sql$" || true)
if [[ -n "$modified_sql" ]]; then echo "Checking modified models..." >&2
# Compile to catch syntax errors if ! dbt compile --select state:modified 2>&1; then echo "Compilation failed — check the errors above" >&2 exit 2 # Block so Claude can see and fix the issue fi
# Lint (warning only, don't block) sqlfluff lint $modified_sql --dialect bigquery 2>&1 || true
echo "All checks passed" >&2fiEnregistrez-le avec un matcher vide, ce qui signifie “déclencher à chaque stop, indépendamment des outils utilisés” :
{ "hooks": { "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": ".claude/hooks/dbt-quality-check.sh" } ] } ] }}Lorsque le hook quitte avec le code 2 (compilation échouée), Claude voit l’erreur immédiatement et peut la corriger dans le tour suivant. Lorsqu’il quitte avec le code 0 (toutes les vérifications réussies), Claude continue normalement. Le lint sqlfluff s’exécute comme avertissement — il ne bloque pas, mais Claude voit les problèmes de style dans la sortie.
Pourquoi Stop plutôt que PostToolUse ?
Vous pourriez exécuter dbt compile comme hook PostToolUse après chaque modification de fichier. Le problème est la vitesse. dbt compile --select state:modified prend quelques secondes même sur un petit projet. Si Claude modifie cinq fichiers SQL en un seul tour, cela représente cinq exécutions de compilation — chacune ajoutant de la latence au workflow de Claude.
Le hook Stop s’exécute une fois après que toutes les modifications sont terminées. Une seule vérification de compilation couvre tous les changements du tour entier. Le compromis : vous ne détectez pas les erreurs entre les modifications individuelles dans le même tour. Mais Claude verra les erreurs à la fin du tour et les corrigera, ce qui est généralement suffisant.
La règle générale : les vérifications rapides vont dans PostToolUse, les vérifications lentes vont dans Stop. Formater automatiquement un seul fichier avec sqlfluff est rapide — mettez-le dans PostToolUse. Compiler le projet dbt est plus lent — mettez-le dans Stop.
Extension de la porte de qualité
La vérification de compilation de base est un point de départ. Vous pouvez ajouter des couches de vérifications supplémentaires :
Exécuter des tests dbt sur les modèles modifiés pour détecter les problèmes de données, pas seulement les erreurs de syntaxe :
if [[ -n "$modified_sql" ]]; then dbt test --select state:modified 2>&1 || truefiGardez cela comme avertissement (pas un blocage) car les échecs de tests sur les données de développement peuvent être attendus lors des itérations.
Vérifier la documentation manquante lors de la création de nouveaux modèles :
new_models=$(git diff --name-only HEAD 2>/dev/null | grep "models/.*\.sql$" | while read f; do model=$(basename "$f" .sql) grep -rq "$model" models/ --include='*.yml' || echo "$f"done)
if [[ -n "$new_models" ]]; then echo "Warning: new models without YAML documentation:" >&2 echo "$new_models" >&2fiHooks SessionStart : chargement du contexte
Les hooks SessionStart s’exécutent une fois lorsque vous commencez une session Claude Code. Leur objectif est différent des hooks Stop — ils n’appliquent pas la qualité, ils fournissent du contexte.
Un hook SessionStart pour les projets dbt peut vous montrer où vous en étiez :
#!/usr/bin/env bash
echo "" >&2echo "dbt project context" >&2echo "──────────────────────" >&2
# Current branchbranch=$(git branch --show-current 2>/dev/null || echo "unknown")echo "Branch: $branch" >&2
# Modified modelsmodified=$(git diff --name-only HEAD 2>/dev/null | grep "models/.*\.sql$" | wc -l | tr -d ' ')echo "Modified models: $modified" >&2
# Last run statusif [[ -f "target/run_results.json" ]]; then failures=$(jq '[.results[] | select(.status == "error")] | length' target/run_results.json 2>/dev/null || echo "?") echo "Last run failures: $failures" >&2fi
echo "──────────────────────" >&2echo "" >&2La sortie va sur stderr, que Claude voit comme contexte au début de la conversation. C’est un petit détail, mais utile lorsqu’on passe d’un projet à l’autre ou qu’on reprend le travail le lendemain matin.
Idées pratiques pour SessionStart
Afficher les PR ouvertes pour la branche actuelle :
pr_count=$(gh pr list --head "$(git branch --show-current)" --json number --jq 'length' 2>/dev/null || echo "?")echo "Open PRs for this branch: $pr_count" >&2Afficher les packages dbt obsolètes :
if [[ -f "packages.yml" ]]; then echo "Installed packages:" >&2 dbt deps --dry-run 2>&1 | grep -i "update" || echo "All packages up to date" >&2fiAfficher le statut récent du CI :
last_run=$(gh run list --limit 1 --json status,conclusion --jq '.[0] | "\(.status): \(.conclusion // "in progress")"' 2>/dev/null || echo "unknown")echo "Last CI run: $last_run" >&2Gardez les hooks SessionStart rapides. Ils s’exécutent au début de chaque session, et un hook lent signifie attendre plusieurs secondes avant de pouvoir commencer à travailler. Si une vérification prend plus d’une à deux secondes, elle n’a probablement pas sa place dans SessionStart.
Combiner les quatre types de hooks
Un .claude/settings.json complet pour un projet dbt pourrait utiliser les quatre hooks de cycle de vie ensemble :
{ "hooks": { "SessionStart": [ { "matcher": "", "hooks": [ { "type": "command", "command": ".claude/hooks/dbt-context.sh" } ] } ], "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": ".claude/hooks/dbt-safety.sh" } ] } ], "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": ".claude/hooks/format-sql.sh" } ] } ], "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": ".claude/hooks/dbt-quality-check.sh" } ] } ] }}Chaque type de hook gère une préoccupation différente :
| Hook | Quand | Objectif | Exigence de vitesse |
|---|---|---|---|
| SessionStart | Démarrage de session | Charger le contexte | Rapide (s’exécute une fois) |
| PreToolUse | Avant chaque action | Bloquer les opérations dangereuses | Rapide (s’exécute par outil) |
| PostToolUse | Après chaque action | Auto-formatage, lint | Rapide (s’exécute par outil) |
| Stop | Fin de tour | Compiler, tester, valider | Peut être plus lent (s’exécute une fois par tour) |
La progression est délibérée : SessionStart vous oriente, PreToolUse prévient les erreurs, PostToolUse nettoie, et Stop valide l’ensemble du tour. Chaque couche capture ce que les autres manquent.