Tu lances Claude Code, tu lui dis quoi faire, et tu attends. Cool.
Mais si tu pouvais lui dire comment faire ? Automatiser les tâches répétitives, valider chaque commande avant exécution, et déclencher des actions au bon moment ?
C'est exactement ce que permettent les scripts dans Claude Code. Et crois-moi, une fois que tu y goûtes, tu ne reviens plus en arrière.
Un script Claude Code, c'est un fichier qui s'exécute automatiquement à un moment précis du workflow. Tu les places dans ton projet ou ta config globale, et Claude Code les déclenche tout seul.
Il y a deux grandes catégories :
On va voir les deux.
Les hooks sont le mécanisme le plus puissant de Claude Code. Ils te permettent d'intercepter et de contrôler tout ce que fait l'agent.

Claude Code propose 18 événements de hooks au total, mais voici les plus utilisés :
| Hook | Quand il se déclenche |
|---|---|
PreToolUse | Avant qu'un outil s'exécute (peut le bloquer) |
PostToolUse | Après qu'un outil s'exécute avec succès |
Notification | Quand Claude Code envoie une notification (permission, idle...) |
SessionStart | Au démarrage ou à la reprise d'une session |
UserPromptSubmit | Quand tu envoies un message |
Stop | Quand Claude termine de travailler |
Il existe aussi PostToolUseFailure, PermissionRequest, SubagentStart, SubagentStop, SessionEnd, PreCompact, ConfigChange, et d'autres pour des cas avancés.
Tu configures tes hooks dans .claude/settings.json :
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node ./scripts/validate-command.js"
}
]
}
]
}
}Le matcher filtre sur quel outil le hook s'active. Ici, uniquement sur les commandes Bash. Le matcher est une regex : Edit|Write matche les deux outils, mcp__.* matche tous les outils MCP.
Tu peux aussi utiliser la commande /hooks directement dans Claude Code pour ajouter, voir et supprimer tes hooks de façon interactive sans éditer le JSON à la main.
Avant de parler des hooks, parlons de la gestion des permissions. Par défaut, Claude Code te pose une question pour chaque action. C'est lent et pénible.
La solution : le mode bypass permissions avec une deny list dans ~/.claude/settings.json :
{
"permissions": {
"defaultMode": "bypassPermissions",
"deny": ["Bash(rm -rf *)", "Bash(sudo *)", "Bash(git push --force *)"]
}
}Avec ce setup :
rm -rf, sudo, git push --force) sont automatiquement bloquéesrm -rf, tu verras : "Error: permission denied"C'est le meilleur des deux mondes : rapide ET sécurisé.
Combine ça avec une règle dans ton CLAUDE.md :
## Sécurité
- N'utilise jamais rm -rf, toujours utiliser `trash` pour supprimer des dossiersComme ça, même si la deny list ne catch pas tout, l'IA sait qu'elle doit utiliser trash au lieu de rm -rf.
Le cas d'usage le plus classique. Tu veux empêcher Claude Code de lancer un rm -rf / ou un drop database.
Crée un fichier .claude/hooks/block-dangerous.js :
const input = await Bun.stdin.text();
const { tool_input } = JSON.parse(input);
const command = tool_input?.command || "";
const BLOCKED = [
/rm\s+.*-rf\s*\//i,
/drop\s+database/i,
/truncate\s+table/i,
/git\s+push.*--force\s+origin\s+main/i,
];
const isBlocked = BLOCKED.some((pattern) => pattern.test(command));
if (isBlocked) {
console.error(`BLOCKED: "${command}" is not allowed.`);
process.exit(2); // exit 2 = bloquer l'exécution
}
process.exit(0); // exit 0 = autoriserEt dans .claude/settings.json :
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bun \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous.js"
}
]
}
]
}
}Utilise $CLAUDE_PROJECT_DIR pour référencer tes scripts avec un chemin absolu, quel que soit le dossier courant.
Le code de sortie est la clé :
Simple et efficace.
Tu veux que ton code soit toujours formaté ? Ajoute un hook PostToolUse qui lance Prettier après chaque modification de fichier.
Les hooks reçoivent les données en JSON sur stdin (pas en variables d'environnement). Tu utilises jq pour extraire le chemin du fichier :
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write 2>/dev/null || true"
}
]
}
]
}
}Chaque fois que Claude Code édite ou écrit un fichier, Prettier passe dessus automatiquement. Ton code est toujours clean.
Tu lances une tâche longue et tu veux être notifié quand Claude a besoin de toi ? Le hook Notification est fait pour ça. Il se déclenche quand Claude attend une permission, est idle, ou a besoin de ton attention :
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code a besoin de toi\" with title \"Claude Code\"'"
}
]
}
]
}
}Sur Mac, ça t'envoie une notification native. Sur Linux, utilise notify-send 'Claude Code' 'Claude Code a besoin de toi' à la place.
Le matcher peut filtrer par type de notification : permission_prompt, idle_prompt, auth_success, elicitation_dialog. Laisse-le vide ou "*" pour capter toutes les notifications.
Tu peux aussi utiliser un hook Stop si tu veux être notifié spécifiquement quand Claude termine son travail (pas juste quand il attend) :
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code a terminé !\" with title \"Claude Code\"'"
}
]
}
]
}
}Tu veux que Claude Code ait du contexte dès le démarrage ? Utilise SessionStart :
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo \"Branch: $(git branch --show-current), Last commit: $(git log --oneline -1)\""
}
]
}
]
}
}À chaque nouvelle session, Claude Code reçoit automatiquement le nom de la branche et le dernier commit. Le stdout d'un hook SessionStart est ajouté au contexte de Claude.
Tu peux aussi filtrer avec un matcher : startup (nouvelle session), resume (reprise), clear (après /clear), compact (après compaction du contexte). Par exemple, un matcher compact te permet de re-injecter du contexte important après que Claude a compacté la conversation.
Les hooks reçoivent toutes les données en JSON sur stdin, pas via des variables d'environnement. Quand un hook PreToolUse se déclenche sur un Bash, tu reçois par exemple :
{
"session_id": "abc123",
"cwd": "/Users/toi/mon-projet",
"hook_event_name": "PreToolUse",
"permission_mode": "default",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}Pour lire ces données dans un script bash, utilise jq :
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')Il y a aussi quelques vraies variables d'environnement disponibles :
| Variable | Description |
|---|---|
CLAUDE_PROJECT_DIR | Le dossier racine du projet |
CLAUDE_ENV_FILE | Fichier pour persister des variables (uniquement dans SessionStart) |
CLAUDE_CODE_REMOTE | "true" en environnement web distant |
Utilise $CLAUDE_PROJECT_DIR pour référencer tes scripts avec des chemins absolus, peu importe le dossier courant.
Voici la structure que je recommande (la convention officielle utilise .claude/hooks/) :
.claude/
├── settings.json # Config des hooks
├── hooks/
│ ├── block-dangerous.js # Validation de commandes
│ ├── auto-format.sh # Auto-formatage
│ └── notify.sh # Notifications
Quelques bonnes pratiques :
Les scripts sont encore plus puissants quand tu les combines avec ton CLAUDE.md. Le fichier CLAUDE.md dit à Claude Code quoi faire, les scripts contrôlent comment il le fait.
# CLAUDE.md
## Rules
- Never use rm -rf, use trash instead
- Always run tests after modifying code
- Format code with prettier before committingLe CLAUDE.md guide l'IA, les hooks l'empêchent de dévier.
Les scripts ne sont qu'une partie de l'écosystème Claude Code. Pour une vue complète :
Les scripts Claude Code c'est ce qui transforme un simple assistant en un vrai workflow automatisé. Commence par un hook simple, et tu verras vite à quel point c'est addictif.
Exit 0 autorise l'exécution de l'outil. Exit 2 bloque l'exécution. Si ton script retourne exit 2, Claude Code ne lancera pas la commande et affichera le message que tu as écrit sur stderr (avec console.error en JS ou >&2 en bash). Tout autre code de sortie est une erreur non-bloquante : stderr est logué mais l'action continue.
Tu peux aussi retourner du JSON sur stdout (avec exit 0) pour un contrôle plus fin, par exemple permissionDecision: "deny" dans un PreToolUse. Voir la doc officielle des hooks pour tous les détails.
Oui. Les hooks s'exécutent toujours, même en bypass permissions. C'est justement pour ça qu'ils sont essentiels : ils agissent comme un filet de sécurité quand tu utilises --dangerously-skip-permissions.
Utilise settings.json pour les hooks que tu veux partager avec ton équipe (ils sont committés dans le repo). Utilise settings.local.json pour les hooks personnels (notifications, formatage local). Le fichier local n'est pas versionné.
Teste ton script manuellement dans le terminal d'abord en lui passant du JSON sur stdin :
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./mon-hook.sh
echo $?Ensuite, vérifie que le matcher est correct (il doit correspondre au nom de l'outil : Bash, Edit, Write, etc. -- c'est case-sensitive). Lance Claude Code avec claude --debug pour voir les détails d'exécution des hooks, ou active le mode verbose avec Ctrl+O.

Avec une ligne de commande, récupère ma configuration complète.