Adrienne Vermorel

Automatiser les tâches répétitives : Hooks et commandes personnalisées pour les analytics engineers

Il y a ce moment dans chaque projet dbt où vous vous surprenez à taper la même commande pour la quinzième fois de la journée. dbt build --select +my_model. Attendre. Vérifier l’output. Lancer sqlfluff. Corriger le formatage. Relancer. Rien de compliqué, mais ça s’accumule.

J’utilise le système de hooks et les commandes slash personnalisées de Claude Code depuis quelques semaines, et le workflow quotidien est devenu nettement plus fluide. Les tâches répétitives s’exécutent automatiquement. Les commandes dangereuses sont interceptées avant de s’exécuter.

Ce guide s’adresse aux analytics engineers déjà à l’aise avec dbt qui veulent éliminer ces frictions. On va voir comment les hooks peuvent automatiser le formatage et la validation, et comment les commandes slash personnalisées peuvent transformer des workflows en plusieurs étapes en une seule invocation.

C’est quoi les hooks Claude Code ?

Si vous avez déjà utilisé les git hooks, le concept est similaire. Les hooks Claude Code sont des scripts qui se déclenchent automatiquement à des moments précis : avant l’exécution d’un outil, après sa complétion, au démarrage d’une session, quand Claude finit de répondre. Vous les configurez une fois, et ils fonctionnent en arrière-plan.

Les hooks les plus utiles pour le travail dbt :

PreToolUse se déclenche avant que Claude exécute quelque chose. C’est votre filet de sécurité. Vous pouvez inspecter la commande, vérifier si elle s’apprête à faire quelque chose de dangereux, et la bloquer si nécessaire. On peut l’utiliser pour empêcher un --full-refresh accidentel en production.

PostToolUse se déclenche après la complétion d’un outil. Idéal pour le formatage automatique. Chaque fois que Claude modifie un fichier SQL, mon formateur s’exécute automatiquement.

Stop se déclenche quand Claude finit de répondre (la “fin du tour”). C’est là que vivent les contrôles qualité. Exécuter dbt compile pour détecter les erreurs de syntaxe. Linter les fichiers modifiés. Si quelque chose échoue, Claude voit l’erreur et peut la corriger immédiatement.

SessionStart se déclenche au début d’une session. Je l’utilise pour afficher le contexte : sur quelle branche je suis, quels modèles j’ai modifiés, est-ce que quelque chose a échoué au dernier run.

La configuration se trouve dans .claude/settings.json (niveau projet, versionné) ou ~/.claude.json (personnel). La structure de base :

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/format-sql.sh"
}
]
}
]
}
}

Le matcher filtre quels outils déclenchent le hook. Edit|Write signifie “ne se déclencher que quand Claude modifie ou crée des fichiers”. On peut être plus précis : Bash(dbt:*) ne matche que les commandes bash commençant par dbt.

Formatage automatique du SQL

C’est le hook que j’installerais en premier. Simple, rapide, et vous remarquerez le bénéfice immédiatement.

Chaque fois que Claude écrit ou modifie un fichier SQL, ce hook exécute sqlfluff (ou le formateur de votre choix) automatiquement. Plus besoin de changer de contexte pour formater, plus de style incohérent dans les PRs.

Créez .claude/hooks/format-sql.sh :

#!/usr/bin/env bash
set -euo pipefail
# Le hook reçoit du JSON sur stdin avec les détails de ce qui vient de se passer
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
# Ne traiter que les fichiers SQL
if [[ "$file_path" == *.sql ]]; then
sqlfluff fix "$file_path" --dialect bigquery --force 2>/dev/null || true
echo "✨ Formatted: $file_path" >&2
fi

Et la configuration :

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/format-sql.sh"
}
]
}
]
}
}

Le formatage s’exécute maintenant à chaque modification de fichier. Une chose de moins à retenir.

Bloquer les commandes dangereuses

On a tous vécu ce moment. Vous pensiez exécuter sur dev, mais la commande est partie en production. Ou vous avez accidentellement déclenché un full refresh sur une table qui prend quatre heures à reconstruire.

Les hooks PreToolUse permettent d’intercepter ces situations avant qu’elles ne se produisent. Le script inspecte la commande, et si elle semble dangereuse, il bloque l’exécution avec le code de sortie 2.

Créez .claude/hooks/dbt-safety.sh :

#!/usr/bin/env bash
set -euo pipefail
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')
# Est-ce une commande dbt ciblant la production ?
if echo "$command" | grep -q "dbt" && echo "$command" | grep -q "\-\-target.*prod"; then
# Bloquer full-refresh en production
if echo "$command" | grep -q "\-\-full-refresh"; then
echo "🛑 Blocked: --full-refresh on production. Run this manually if you're sure." >&2
exit 2
fi
# Bloquer run/build sans sélecteurs explicites
if echo "$command" | grep -qE "dbt (run|build)" && ! echo "$command" | grep -qE "\-\-(select|models)"; then
echo "🛑 Blocked: dbt run on production needs explicit --select" >&2
exit 2
fi
fi
exit 0

Le code de sortie 2 bloque l’action et affiche votre message d’erreur à Claude. Le code 0 laisse passer. Les autres codes loguent des avertissements mais ne bloquent pas.

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/dbt-safety.sh"
}
]
}
]
}
}

Contrôles qualité en fin de tour

Le hook Stop exécute des vérifications après que tous les changements sont faits. C’est une mini-CI qui tourne localement, immédiatement.

Mon hook Stop compile les modèles modifiés pour détecter les erreurs de syntaxe. Si la compilation échoue, Claude voit l’erreur tout de suite et peut la corriger.

Créez .claude/hooks/dbt-quality-check.sh :

#!/usr/bin/env bash
set -euo pipefail
# Vérifier si des fichiers SQL ont été modifiés
modified_sql=$(git diff --name-only HEAD 2>/dev/null | grep "\.sql$" || true)
if [[ -n "$modified_sql" ]]; then
echo "🔍 Checking modified models..." >&2
# Compiler pour détecter les erreurs de syntaxe
if ! dbt compile --select state:modified 2>&1; then
echo "❌ Compilation failed — check the errors above" >&2
exit 2 # Bloquer pour que Claude puisse voir et corriger le problème
fi
# Lint (avertissement seulement, ne pas bloquer)
sqlfluff lint $modified_sql --dialect bigquery 2>&1 || true
echo "✅ All checks passed" >&2
fi
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/dbt-quality-check.sh"
}
]
}
]
}
}

Le matcher vide signifie “se déclencher à chaque stop, peu importe quels outils ont été utilisés”. On peut être plus précis si on veut le déclencher uniquement après des modifications de fichiers.

Charger le contexte au démarrage de session

Les hooks SessionStart s’exécutent une fois au début d’une session Claude Code. Le mien me rappelle où j’en suis dans le projet.

Créez .claude/hooks/dbt-context.sh :

#!/usr/bin/env bash
echo "" >&2
echo "📊 dbt project context" >&2
echo "──────────────────────" >&2
# Branche courante
branch=$(git branch --show-current 2>/dev/null || echo "unknown")
echo "Branch: $branch" >&2
# Modèles modifiés
modified=$(git diff --name-only HEAD 2>/dev/null | grep "models/.*\.sql$" | wc -l | tr -d ' ')
echo "Modified models: $modified" >&2
# Statut du dernier run
if [[ -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" >&2
fi
echo "──────────────────────" >&2
echo "" >&2

Un petit détail, mais utile quand on passe d’un projet à l’autre.

Commandes slash personnalisées

Les hooks se déclenchent automatiquement. Les commandes slash sont l’inverse : des workflows qu’on invoque à la demande avec /nom-commande. On peut les voir comme des prompts sauvegardés qui peuvent aussi exécuter du bash et lire des fichiers.

Les commandes sont des fichiers Markdown. On les place dans .claude/commands/ pour les commandes spécifiques au projet (versionnées, partagées avec l’équipe) ou ~/.claude/commands/ pour les commandes personnelles.

Builder un modèle avec ses dépendances amont

Au lieu de taper dbt build --select +stg_orders, je tape /project:dbt-build +stg_orders.

Créez .claude/commands/dbt-build.md :

---
allowed-tools: Bash(dbt:*), Bash(git:*)
argument-hint: [+]model_name
description: Build a dbt model (prefix with + for upstream deps)
---
## What we're working with
Current branch: !`git branch --show-current`
Recently modified:
!`git diff --name-only HEAD | grep "\.sql$" | head -5`
## Your task
Build the model: **$ARGUMENTS**
Run `dbt build --select $ARGUMENTS` and let me know:
- Did it succeed?
- How long did it take?
- If tests failed, what went wrong and how should I fix it?

Le préfixe ! exécute du bash et inclut la sortie comme contexte. $ARGUMENTS capture ce qu’on tape après le nom de la commande. Le frontmatter spécifie quels outils la commande peut utiliser.

Générer un base model depuis une source

Quand j’ajoute une nouvelle table source, je veux que le base model suive automatiquement nos conventions. Cette commande lit les base models existants pour comprendre les patterns, puis en génère un nouveau.

Créez .claude/commands/dbt-base.md :

---
allowed-tools: Bash(dbt:*), Read, Write, Glob
argument-hint: source_name table_name
description: Generate a base model following project conventions
---
## Our conventions
Here's how our existing base models look:
!`ls models/base/base__*.sql | head -3 | xargs head -30`
## Your task
Create a base model for source **$1**, table **$2**.
1. Create `models/base/base__$1__$2.sql` with:
- A source CTE pulling from `{{ source('$1', '$2') }}`
- A renamed CTE with clean snake_case column names
- Appropriate type casting (especially for dates)
1. Add the model to `models/base/_base__$1__models.yml` with:
- A clear description
- Primary key uniqueness + not_null tests
- Not-null tests on required columns
1. Run `dbt compile --select base__$1__$2` to verify it works

Utilisation : /project:dbt-base ga4 events

Builder uniquement ce qu’on a modifié

Quand on itère sur une feature branch, on veut généralement builder seulement les modèles qu’on a touchés.

Créez .claude/commands/dbt-modified.md :

---
allowed-tools: Bash(dbt:*), Bash(git:*)
description: Build only the models modified in this branch
---
## What's changed
Models modified vs main:
!`git diff --name-only origin/main...HEAD | grep "models/.*\.sql$" || echo "No model changes found"`
## Your task
Build only the modified models using `dbt build --select state:modified`.
If anything fails, tell me what went wrong and suggest a fix.

Auditer un modèle selon les bonnes pratiques

Cette commande vérifie la structure, la documentation, les tests et les dépendances.

Créez .claude/commands/dbt-audit.md :

---
allowed-tools: Read, Glob, Bash(dbt:*)
argument-hint: model_name
description: Audit a model for dbt best practices
---
## Your task
Audit **$ARGUMENTS** and tell me if anything looks off.
Check:
1. **Code quality**: Uses CTEs (not subqueries), has comments where logic is complex, follows naming conventions
2. **Documentation**: Model is described in YAML, key columns are documented
3. **Testing**: Has primary key tests (unique + not_null), critical columns are tested
4. **Dependencies**: Run `dbt ls --select +$ARGUMENTS` — is it referencing base models or raw tables directly?
Be specific. If something's missing, show me exactly what to add.

Nettoyer l’environnement de dev

Créez .claude/commands/dbt-cleanup.md :

---
allowed-tools: Bash(dbt:*), Bash(rm:*)
description: Clean up dbt development artifacts
---
## Your task
Clean up the local environment:
1. Remove `target/` (compiled artifacts)
2. Remove `dbt_packages/` (installed packages)
3. Run `dbt clean`
4. Run `dbt deps` to reinstall packages fresh
Tell me how much space was freed up.

Connecter le serveur MCP dbt

Cette partie est optionnelle mais utile. Le serveur MCP dbt officiel donne à Claude Code une connaissance de la structure du projet (modèles, sources, tests, lineage) sans avoir à l’expliquer à chaque fois.

Installation :

Terminal window
claude mcp add dbt -s project -- uvx dbt-mcp

Configuration dans .claude/settings.json :

{
"mcpServers": {
"dbt": {
"command": "uvx",
"args": ["dbt-mcp"],
"env": {
"DBT_PROJECT_DIR": "/path/to/your/dbt/project",
"DBT_PATH": "/usr/local/bin/dbt"
}
}
}
}

Avec le serveur MCP actif, Claude peut lister les modèles, comprendre les dépendances, et exécuter les commandes dbt avec le contexte complet du projet.

Tout assembler

Un exemple pratique de comment les hooks et les commandes fonctionnent ensemble :

Vous ajoutez un nouveau base model pour une table source GA4. Vous lancez /project:dbt-base ga4 events. Claude crée le fichier du modèle et l’entrée YAML. Le hook PostToolUse formate le SQL. Claude exécute compile pour vérifier. Le hook Stop détecte les erreurs de syntaxe avant que Claude termine. Vous itérez avec une conversation normale. Chaque modification est formatée automatiquement, chaque tour est validé.

Pas de formatage manuel. Pas de vérifications de compilation oubliées.

Quelques notes pratiques

Gardez les hooks rapides. PostToolUse se déclenche à chaque modification de fichier. Si votre formateur prend 3 secondes, ce délai devient perceptible. Réservez les opérations lentes aux hooks Stop.

Les codes de sortie fonctionnent différemment de ce qu’on pourrait attendre. Dans la plupart des outils Unix, tout code de sortie non nul signifie “quelque chose s’est mal passé”. Les hooks Claude Code sont plus exigeants :

  • Exit 0 → Succès, l’action se poursuit normalement
  • Exit 2 → Bloquer l’action et afficher le message d’erreur à Claude
  • Exit 1, 3, ou tout autre code → Logger un avertissement, mais l’action se poursuit quand même

Donc si vous écrivez un hook PreToolUse qui sort avec le code 1 quand il détecte un problème, la commande s’exécutera quand même. Claude verra juste un avertissement dans les logs. Seul le code de sortie 2 arrête vraiment les choses.

Ça m’a piégé au début. J’avais un hook de sécurité qui sortait avec le code 1 sur les commandes dangereuses, et je me demandais pourquoi elles continuaient à s’exécuter.

Les matchers sont sensibles à la casse. bash ne matchera pas Bash. Vérifiez les noms exacts des outils dans la sortie de Claude.

Versionnez vos hooks. Mettez .claude/settings.json et .claude/hooks/ dans git. Quand vous onboardez quelqu’un de nouveau, il récupère toute votre automatisation gratuitement.

Débuguez avec --debug. Lancez claude --debug pour voir exactement quand les hooks se déclenchent et ce qu’ils retournent.

Par où commencer

N’essayez pas d’implémenter tout d’un coup. Choisissez la chose qui vous agace le plus.

Pour la plupart des gens, c’est le formatage. Installez le hook PostToolUse pour le formateur, utilisez-le pendant une semaine, et voyez comment ça se passe. Une fois que ça fonctionne, ajoutez un hook Stop pour les vérifications de compilation. Ensuite, commencez à construire des commandes slash pour vos workflows les plus répétés.

L’objectif est d’éliminer ce qui interrompt votre réflexion. Quand vous n’avez plus à vous souvenir de formater, ou à vous inquiéter de casser la production, ou à taper la même commande de build encore et encore, vous pouvez vous concentrer sur le vrai travail d’analytics.