05 - Hooks : automatiser ce qui doit toujours arriver
Un hook, c'est une commande shell que Claude Code lance automatiquement a un moment precis. Deterministe, pas negociable.
Temps de lecture : 14 min | Niveau : Avance
Ce que tu sauras faire apres
- Distinguer un hook d'une instruction
CLAUDE.mdet savoir quand utiliser lequel. - Choisir le bon evenement parmi ceux disponibles.
- Ecrire une configuration de hook correcte, avec matcher et timeout.
- Formater automatiquement ton code, bloquer une commande dangereuse, etre notifie.
- Comprendre les risques de securite avant de deployer un hook.
1. Pourquoi les hooks existent
Une instruction dans CLAUDE.md est un conseil. Claude la suit la plupart du temps. Parfois il l'oublie, surtout quand le contexte se remplit.
Un hook est une garantie. Claude Code lance la commande, point.
| Tu veux | Utilise |
|---|---|
| Orienter le comportement de Claude | CLAUDE.md |
| Fournir une procedure a la demande | Une skill |
| Qu'une action arrive a coup sur, sans exception | Un hook |
| Bloquer un outil quoi qu'il arrive | Une regle deny ou un hook PreToolUse |
Exemple concret : "formate le code apres chaque edition". Dans CLAUDE.md, Claude le fera huit fois sur dix. En hook PostToolUse, c'est dix fois sur dix.
2. Les evenements disponibles
Il y a une trentaine d'evenements. Voici ceux que tu utiliseras vraiment, groupes par moment.
Autour d'un appel d'outil
| Evenement | Quand il se declenche |
|---|---|
PreToolUse | Avant l'execution d'un appel d'outil |
PostToolUse | Apres qu'un appel d'outil a reussi |
PostToolUseFailure | Apres l'echec d'un appel d'outil |
PostToolBatch | Apres la resolution d'appels d'outils paralleles |
PermissionRequest | Un outil a besoin d'une decision de permission |
PermissionDenied | Le mode auto a refuse un appel d'outil |
Autour d'un tour de conversation
| Evenement | Quand il se declenche |
|---|---|
UserPromptSubmit | Avant que Claude ne traite ton prompt |
Stop | Quand Claude a fini de repondre |
StopFailure | Le tour se termine sur une erreur d'API |
Notification | Claude Code envoie une notification |
Autour de la session
| Evenement | Quand il se declenche |
|---|---|
SessionStart | Au debut ou a la reprise d'une session |
SessionEnd | A la fin de la session, utile pour archiver le transcript |
Setup | Preparation unique : au demarrage avec --init-only, ou avec --init / --maintenance en mode -p |
PreCompact / PostCompact | Avant / apres la compaction du contexte |
InstructionsLoaded | Un CLAUDE.md ou des regles viennent d'etre charges |
ConfigChange | Un fichier de configuration a change |
FileChanged | Un fichier surveille a change sur le disque |
CwdChanged | Le dossier de travail a change |
Autour des sous-agents et des worktrees
| Evenement | Quand il se declenche |
|---|---|
SubagentStart / SubagentStop | Un sous-agent demarre / se termine |
TaskCreated / TaskCompleted | Une tache est creee / marquee terminee |
WorktreeCreate / WorktreeRemove | Un worktree est cree / supprime |
3. Ou mettre la configuration
Un hook vit dans un fichier de reglages, sous la cle hooks.
| Emplacement | Portee | Partageable |
|---|---|---|
~/.claude/settings.json | Tous tes projets | Non, local a ta machine |
.claude/settings.json | Un projet | Oui, commitable |
.claude/settings.local.json | Un projet | Non, gitignore |
| Reglages geres (managed) | Toute l'organisation | Oui, controle par l'admin |
hooks/hooks.json d'un plugin | Quand le plugin est actif | Oui, dans le plugin |
| Frontmatter d'une skill | Le reste de la session une fois la skill invoquee | Oui |
| Frontmatter d'un sous-agent | Pendant l'execution du sous-agent | Oui |
Pour voir ce qui est configure :
/hooksCe menu est en lecture seule. Pour ajouter ou modifier, edite le JSON directement ou demande a Claude de le faire.
4. La structure de configuration
{
"hooks": {
"NomDeLEvenement": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "ma-commande",
"args": ["arg1"],
"timeout": 30,
"if": "Bash(rm *)"
}
]
}
]
}
}Le champ if est un filtre supplementaire, disponible sur les evenements d'outil. Il prend une regle de permission, par exemple Bash(rm *) ou Edit(*.sql). Le hook ne tourne que si la regle correspond a l'appel. C'est plus precis que le matcher, qui ne regarde que le nom de l'outil.
Le matcher
| Valeur du matcher | Interprete comme |
|---|---|
"*", "", ou absent | Tout |
Seulement lettres, chiffres, _, -, espaces, ,, | | Chaine exacte ou liste separee par | ou , |
| Autre caractere present | Expression reguliere JavaScript, non ancree |
Sur quoi il matche depend de l'evenement :
| Evenement | Matche sur | Exemples |
|---|---|---|
| Evenements d'outil | Le nom de l'outil | Bash, Edit|Write, mcp__.* |
SessionStart | Le mode de demarrage | startup, resume, clear, compact, fork |
Notification | Le type de notification | permission_prompt, idle_prompt, agent_completed |
PreCompact / PostCompact | Le declencheur | manual, auto |
Certains evenements n'acceptent pas de matcher, dont UserPromptSubmit, Stop, PostToolBatch et CwdChanged.
Les types de hook
type | Ce qu'il fait |
|---|---|
command | Lance un executable ou une ligne de shell |
http | Envoie le JSON du hook en POST vers une URL |
mcp_tool | Appelle un outil sur un serveur MCP connecte |
prompt | Envoie le prompt et les donnees du hook a un modele Claude (Haiku par defaut) |
agent | Confie l'evaluation a un agent qui peut lire des fichiers |
Forme "exec" contre forme "shell"
Si tu mets args, il n'y a pas de shell : command est l'executable, et chaque element de args est un argument exact. Les caracteres speciaux passent tels quels.
{
"type": "command",
"command": "node",
"args": ["${CLAUDE_PROJECT_DIR}/scripts/format.js", "--fix"]
}Si tu omets args, la commande passe par un shell : sh -c sur macOS et Linux, Git Bash sur Windows, ou PowerShell. Tu peux forcer avec "shell": "bash" ou "shell": "powershell".
Les variables d'environnement disponibles
| Variable | Contenu |
|---|---|
CLAUDE_PROJECT_DIR | La racine du projet |
CLAUDE_PLUGIN_ROOT | Le dossier d'installation du plugin |
CLAUDE_EFFORT | Le niveau d'effort courant |
CLAUDE_CODE_REMOTE | true en session web, non defini en local |
Les timeouts par defaut
command,http,mcp_tool: 600 secondesprompt: 30 secondesagent: 60 secondes
Ces valeurs sont reduites sur certains evenements : 30 s sur UserPromptSubmit, 10 s sur MessageDisplay. Un hook avec "async": true tourne en arriere-plan sans bloquer, et son timeout n'est pas applique.
Les codes de sortie
| Code | Signification |
|---|---|
0 | Succes. La sortie JSON est analysee pour des champs de decision |
2 | Erreur bloquante. Sur PreToolUse, l'appel d'outil est bloque |
| Autre | Non bloquant par defaut : l'action continue, une note d'erreur apparait |
Sur Stop, un code 2 empeche l'arret et la conversation continue. Sur PostToolUse, l'outil a deja tourne, donc il n'y a rien a bloquer.
5. Trois cas d'usage reels
Cas 1 : formater apres chaque edition
Le plus utile au quotidien. Ici avec ruff pour Python. Dans .claude/settings.json :
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs -r ruff format",
"timeout": 30
}
]
}
]
}
}Comment ca marche :
matcher: "Edit|Write": ne se declenche qu'apres les outils qui modifient des fichiers.- Claude Code envoie le JSON du hook sur l'entree standard de la commande.
jq -r '.tool_input.file_path'extrait le chemin du fichier edite.xargs -r ruff formatle passe au formateur.
jqn'est pas installe par defaut. C'est l'outil qui lit du JSON en ligne de commande. Deux options sur Windows :
- L'installer :
winget install jqlang.jq, la commande donnee par la page de telechargement de jq.- Ne pas l'installer, et ecrire le hook en PowerShell avec
ConvertFrom-Json, comme au cas 2 ci-dessous.La doc Claude Code cite
brew install jqsur macOS etapt-get install jqsur Debian et Ubuntu, puis renvoie vers la meme page jq.
Quand le hook reussit, Claude Code n'affiche rien dans la conversation. Pour verifier, regarde simplement que le fichier a ete reformate.
Cas 2 : bloquer une commande dangereuse
En data, le risque n'est pas rm -rf mais plutot un bq rm ou un dbt run --target prod.
Etape 1. Cree .claude/hooks/bloque-prod.ps1 :
$callInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
$command = $callInput.tool_input.command
$motifsInterdits = @(
'bq rm',
'bq mk --force',
'--target\s+prod',
'gcloud .* delete',
'DROP\s+TABLE'
)
foreach ($motif in $motifsInterdits) {
if ($command -match $motif) {
@{
hookSpecificOutput = @{
hookEventName = "PreToolUse"
permissionDecision = "deny"
permissionDecisionReason = "Commande bloquee par le hook projet : motif '$motif'. Utilise --target dev."
}
} | ConvertTo-Json -Depth 5
exit 0
}
}
exit 0Etape 2. Enregistre le hook dans .claude/settings.json :
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|PowerShell",
"hooks": [
{
"type": "command",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy", "Bypass",
"-File", "${CLAUDE_PROJECT_DIR}/.claude/hooks/bloque-prod.ps1"
],
"timeout": 15
}
]
}
]
}
}La version macOS ou Linux du meme principe, en Bash, sort avec le code 2 et ecrit la raison sur la sortie d'erreur :
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -qE 'bq rm|--target[[:space:]]+prod'; then
echo "Bloque : commande visant la production. Utilise --target dev." >&2
exit 2
fi
exit 0Sur macOS et Linux, n'oublie pas chmod +x .claude/hooks/bloque-prod.sh.
Claude recoit le message de blocage et peut ajuster son approche.
Cas 3 : etre notifie quand Claude t'attend
Tu lances une grosse tache, tu vas faire autre chose, et tu veux savoir quand Claude a besoin de toi. Dans ~/.claude/settings.json, version Windows :
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code a besoin de ton attention', 'Claude Code')\""
}
]
}
]
}
}Cette commande ouvre une boite de dialogue, pas une notification de coin d'ecran. La boite peut s'ouvrir derriere ta fenetre de terminal.
Un matcher vide se declenche sur tous les types de notification. Pour cibler :
| Matcher | Se declenche quand |
|---|---|
permission_prompt | Claude attend une approbation depuis environ six secondes |
idle_prompt | Claude a fini il y a environ 60 secondes et tu n'as rien tape |
auth_success | L'authentification vient de reussir |
agent_completed | Une session d'arriere-plan s'est terminee ou a echoue |
6. Exemple de configuration complete et commentee
Voici une configuration .claude/settings.json realiste pour un projet data. Les commentaires sont hors du JSON, parce que Claude Code refuse les commentaires dans un fichier de reglages.
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "cat ${CLAUDE_PROJECT_DIR}/.claude/rappel-apres-compaction.md"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash|PowerShell",
"hooks": [
{
"type": "command",
"command": "powershell.exe",
"args": ["-NoProfile", "-File", "${CLAUDE_PROJECT_DIR}/.claude/hooks/bloque-prod.ps1"],
"timeout": 15
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs -r ruff format",
"timeout": 30
}
]
}
]
}
}Ligne par ligne :
SessionStartavec le matchercompact: apres chaque compaction du contexte, Claude Code reinjecte le contenu d'un fichier de rappel. Le texte ecrit sur la sortie standard est ajoute au contexte de Claude. Tres utile pour que Claude n'oublie pas--target devapres une compaction.PreToolUseavec le matcherBash|PowerShell: avant chaque commande shell, le script de garde-fou tourne. Il peut refuser.PostToolUseavec le matcherEdit|Write: apres chaque edition, le formateur passe.
Pour desactiver tous les hooks ponctuellement, mets "disableAllHooks": true dans un fichier de reglages.
Template pret a copier
Colle ce bloc dans .claude/settings.json et remplace uniquement ce qui est entre crochets.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "[TA_COMMANDE_DE_FORMATAGE]",
"timeout": 30
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash|PowerShell",
"hooks": [
{
"type": "command",
"command": "[LE_PROGRAMME_QUI_LANCE_TON_SCRIPT]",
"args": ["[ARGUMENT_1]", "[CHEMIN_DE_TON_SCRIPT_DE_GARDE_FOU]"],
"timeout": 15
}
]
}
]
}
}Exemple rempli, pour un projet Python sous Windows :
[TA_COMMANDE_DE_FORMATAGE]devientjq -r '.tool_input.file_path' | xargs -r ruff format[LE_PROGRAMME_QUI_LANCE_TON_SCRIPT]devientpowershell.exe[ARGUMENT_1]devient-NoProfile[CHEMIN_DE_TON_SCRIPT_DE_GARDE_FOU]devient${CLAUDE_PROJECT_DIR}/.claude/hooks/bloque-prod.ps1
Et le template du prompt a donner a Claude pour qu'il ecrive le hook a ta place :
Ecris-moi un hook Claude Code.
EVENEMENT : [PreToolUse / PostToolUse / Notification / SessionStart]
DECLENCHEUR : [QUAND_CA_DOIT_SE_PRODUIRE, par exemple "apres chaque edition d'un .sql"]
ACTION ATTENDUE : [CE_QUE_LA_COMMANDE_DOIT_FAIRE]
SHELL : [powershell / bash]
FICHIER DE REGLAGES : [.claude/settings.json / ~/.claude/settings.json]
Montre-moi le JSON complet, puis explique-moi chaque ligne, puis dis-moi comment le tester
a la main avant de l'activer.7. Avertissement securite
Un hook execute des commandes shell arbitraires sur ta machine, automatiquement, avec tes droits utilisateur. Tu es entierement responsable de ce que tu configures.
Consequences pratiques :
- Ne copie jamais un hook d'Internet sans le lire ligne par ligne. Un hook
PostToolUsepeut exfiltrer tes fichiers aussi facilement qu'il peut les formater. - Mefie-toi d'un
.claude/settings.jsondans un depot que tu clones. Les hooks d'un projet ne tournent pas tant que tu n'as pas declare faire confiance a ce dossier. Claude Code te pose la question au premier demarrage. Ne reponds pas oui a l'aveugle : ouvre d'abord le fichier et lis les hooks. - Cite tes variables. Dans un hook shell,
$VARnon protege peut casser sur un chemin avec un espace, ou pire, executer autre chose que prevu. Prefere la formeargs, qui ne passe pas par un shell. - Ne mets pas de secret en clair dans la commande. Pour un hook
http, utiliseallowedEnvVarspour n'interpoler que les variables listees. - Un hook
PreToolUsequi sort en code 2 bloque avant l'evaluation des permissions. Il est prioritaire meme sur une regleallow. C'est puissant, et c'est dangereux si le script a un bug. - Teste ton hook a part avant de l'activer. Lance le script a la main, avec un JSON d'exemple sur l'entree standard.
Cote administration, une organisation peut mettre "allowManagedHooksOnly": true dans les reglages geres : seuls les hooks geres tournent alors.
A retenir
- Un hook est deterministe : c'est la difference avec une instruction
CLAUDE.md. - Les trois evenements les plus utiles :
PostToolUse(formater),PreToolUse(bloquer),Notification(etre prevenu). - Le matcher filtre par nom d'outil :
Edit|Write,Bash,mcp__.*. - Le JSON du hook arrive sur l'entree standard ; extrais les champs avec
jqouConvertFrom-Json. - Code de sortie
2surPreToolUse= l'appel est bloque, avec la raison renvoyee a Claude. /hooksaffiche la configuration, en lecture seule.- Un hook lance des commandes arbitraires avec tes droits : lis toujours avant d'activer.
- Tu n'es pas oblige d'ecrire le JSON toi-meme : donne le template de la section 6 a Claude.
Sources
- Claude Code - Automate actions with hooks
- Claude Code - Hooks reference
- Claude Code - Settings files and precedence
- Claude Code - Example settings files
- Claude Code - Configure permissions
- Claude Code - Best practices
- anthropics/claude-code - le hook officiel de validation des commandes Bash :
examples/hooks/bash_command_validator_example.py - disler/claude-code-hooks-mastery - un script Python par evenement de hook, dans
.claude/hooks/ - jq - Telechargement et installation
Liens verifies le 26 septembre 2026.