diffox
Blog

Qu’est-ce qu’un hook et comment l’écrire pour un agent de codage ?

L’agent ne suit pas toujours votre fichier de règles ; un hook, lui, s’exécute à chaque fois. Comment l’écrire dans cinq outils, avec des exemples.

PartagerXLinkedIn

En bref. Un hook est une commande que vous écrivez et que l’agent de codage exécute de lui-même à des moments précis. Qu’il s’exécute ou non ne dépend pas de la décision de l’agent : on s’en sert donc pour les règles qui doivent être respectées sans exception. Claude Code, Codex, Cursor, Gemini CLI et GitHub Copilot en ont. Le 18 octobre 2026, nous avons vérifié chaque information dans la documentation de l’outil lui-même ; les exemples viennent du dépôt que nous utilisons tous les jours. Le 28 octobre 2026, nous avons ajouté Antigravity CLI, de Google.

  • Le fichier de règles ne suffit pas. Une règle écrite dans AGENTS.md ou CLAUDE.md est une information pour l’agent ; rien ne garantit qu’il la respecte. Dans les cinq outils, un hook peut intervenir avant que l’agent exécute une commande.
  • Le code qui bloque est le même partout, pas la réaction à une erreur. Si le hook se termine avec le code 2, les cinq outils arrêtent l’opération. Avec un autre code d’erreur, comme 1, Claude Code, Cursor et Gemini CLI poursuivent l’opération, alors que Copilot l’arrête. La documentation de Codex n’en dit rien.
  • Un hook qui ne répond plus met aussi la règle hors jeu. Si le hook dépasse son délai (timeout), Claude Code, Cursor et Copilot poursuivent l’opération. Vous pouvez changer ce comportement avec l’option onFailure dans Claude Code et failClosed dans Cursor. Celles de Codex et de Gemini CLI n’en disent rien.
  • Nous utilisons trois hooks dans notre dépôt. Le premier décrit l’état du répertoire à l’ouverture de la session, le deuxième bloque un merge lancé sans accord, le troisième avertit l’agent quand la branche a changé.

Pourquoi le fichier de règles ne suffit-il pas ?

5outils étudiés dans leur propre documentation
2code de sortie qui arrête l’opération dans les cinq outils
33événements auxquels un hook peut s’attacher (Claude Code)
3hooks utilisés dans notre propre dépôt

Il y a deux façons de donner une règle à un agent de codage. La première, c’est le fichier de règles : vous écrivez « demande-moi avant tout merge » dans AGENTS.md, CLAUDE.md ou GEMINI.md. La seconde, c’est le hook : vous transformez la même règle en un petit script que l’agent exécute de lui-même à un moment précis.

C’est la documentation de Claude Code qui explique le mieux la différence : « Claude treats them as context, not enforced configuration. To block an action regardless of what Claude decides, use a PreToolUse hook instead. » Autrement dit, le fichier de règles n’est qu’une information pour l’agent, et l’agent ne la prend pas toujours en compte. Si vous voulez bloquer une opération quelle que soit la décision de l’agent, vous utilisez un hook.

Au 18 octobre 2026, cinq outils de codage ont des hooks : Claude Code, Codex d’OpenAI, Cursor, Gemini CLI de Google et GitHub Copilot (la CLI et l’agent cloud). La logique est partout la même : le hook est attaché à un événement, il reçoit le détail de l’événement en JSON et, si besoin, il bloque une commande avant qu’elle s’exécute.

Les exemples viennent du dépôt que nous utilisons tous les jours pour développer diffox. Nous y travaillons avec Claude Code ; ce que nous décrivons n’est pas une fonctionnalité du produit, mais la façon de travailler de l’équipe.

Où définir un hook dans les cinq outils ?

Dans chaque outil, les hooks s’écrivent dans un fichier de configuration ; d’un outil à l’autre, la différence tient surtout au nom du fichier et à celui de l’événement. L’événement le plus utilisé pour poser une règle est celui qui se déclenche juste avant que l’agent utilise un outil (exécuter une commande, modifier un fichier…) :

Les hooks dans cinq outils de codage : fichier de configuration, événement déclenché avant l’utilisation d’un outil et moyen de bloquer l’opération
OutilFichier de configuration (projet · utilisateur)Avant l’utilisation d’un outilPour bloquer
Claude Code.claude/settings.json · ~/.claude/settings.jsonPreToolUseexit 2 ou permissionDecision: "deny"
Codex.codex/hooks.json · ~/.codex/hooks.jsonPreToolUseexit 2 ou permissionDecision: "deny"
Cursor.cursor/hooks.json · ~/.cursor/hooks.jsonpreToolUse ; pour les commandes shell, beforeShellExecutionexit 2 ou permission: "deny"
Gemini CLI.gemini/settings.json · ~/.gemini/settings.jsonBeforeToolexit 2 ou decision: "deny"
GitHub Copilot.github/hooks/*.json · ~/.copilot/hooks/*.jsonpreToolUseexit 2 ou permissionDecision: "deny"

Source : la documentation des hooks de chaque outil, 18 octobre 2026. La décision en JSON passe par ce que le hook écrit à l’écran (stdout). Codex lit aussi la même configuration dans config.toml. Dans Copilot, le fichier utilisateur ne vaut que pour la CLI ; l’agent cloud lit le fichier du dépôt sur la branche par défaut.

Quand l’événement se produit, votre hook en reçoit le détail en JSON : quel outil est appelé, et avec quelle entrée. Dans Claude Code, l’entrée reçue avant une commande Bash ressemble à ceci :

EXEMPLE. Le hook reçoit un objet JSON. Claude Code · stdin · PreToolUse: { /   "session_id": "abc123", /   "cwd": "/home/dev/projet", /   "hook_event_name": "PreToolUse", /   "tool_name": "Bash", /   "tool_input": { /     "command": "gh pr merge 42 --squash" /   } / }. tool_name: Quel outil : le matcher lit ce nom. tool_input.command: La commande à exécuter. Code de sortie: La décision du script : 2 bloque.
Exemple abrégé du JSON que reçoit, dans Claude Code, un hook PreToolUse déclenché avant l’outil Bash. Noms des champs tirés de la documentation des hooks de Claude Code, 18 octobre 2026 ; dans les autres outils, les champs portent d’autres noms.

Les outils savent aussi lire les fichiers des autres : Copilot lit aussi le fichier .claude/settings.json du dépôt, Cursor peut charger les hooks de Claude Code, et Gemini CLI définit la variable CLAUDE_PROJECT_DIR pour que les scripts écrits pour Claude Code continuent de fonctionner. Un hook écrit une fois peut donc servir dans plusieurs outils. Les noms d’événements et les champs de sortie diffèrent pourtant : testez le hook dans chaque outil.

Le 18 juin 2026, Google a cessé de servir Gemini CLI aux utilisateurs gratuits et aux abonnés Google AI Pro et Ultra, et les a orientés vers Antigravity CLI ; avec une clé d’API payante ou une licence d’entreprise, vous pouvez continuer à utiliser Gemini CLI. D’après l’annonce de Google, Antigravity CLI a aussi des hooks ; nous avons lu sa documentation le 28 octobre 2026. Les hooks s’écrivent dans .agents/hooks.json pour un projet et dans ~/.gemini/config/hooks.json pour l’utilisateur. L’événement qui se déclenche avant l’utilisation d’un outil s’appelle PreToolUse, comme dans Claude Code. Pour bloquer l’opération, le hook renvoie decision: "deny" dans son JSON. Le délai (timeout) est de 30 secondes par défaut ; la documentation ne dit pas ce que font les codes de sortie.

Comment un hook bloque-t-il une opération ?

Le hook fait connaître sa décision de deux façons : par son code de sortie, ou par le JSON qu’il écrit à l’écran. Dans les cinq outils, si le hook se termine avec le code 2, l’opération s’arrête et l’explication que le hook a écrite sur sa sortie d’erreur (stderr) est transmise à l’agent. Les outils réagissent différemment quand le hook se termine avec un autre code d’erreur, ou quand il dépasse son délai (timeout) :

Ce qui se passe quand le hook déclenché avant l’utilisation d’un outil se termine avec un code d’erreur autre que 2, ou dépasse son délai
OutilCode d’erreur autre que 2TimeoutOption qui bloque l’opération
Claude CodeL’opération continueL’opération continue"onFailure": "block"
CodexNon précisé dans la documentationNon précisé dans la documentation—
CursorL’opération continueL’opération continue"failClosed": true
Gemini CLIAvertissement, l’opération continueNon précisé dans la documentation—
GitHub CopilotL’opération s’arrêteL’opération continue—

Source : la documentation des hooks de chaque outil, 18 octobre 2026. « Non précisé dans la documentation » : dans les pages que nous avons lues ce jour-là, nous n’avons trouvé aucune phrase qui décrive ce cas. L’option de Claude Code existe depuis la version 2.1.295. Sous Windows sans Git Bash, un hook de commande PreToolUse en forme shell qui échoue peut bloquer l’opération même sans onFailure.

Regardez d’abord la première colonne. Sous Unix, on utilise généralement 1 pour signaler une erreur. Claude Code, Cursor et Gemini CLI traitent ce code comme une erreur qui ne bloque pas l’opération ; Copilot, lui, arrête l’opération avec ce même code. C’est pourquoi la documentation de Claude Code vous conseille d’utiliser exit 2 dans les hooks qui font respecter une règle. Gemini CLI, au contraire, recommande de sortir avec 0 et d’écrire du JSON, même pour bloquer volontairement une opération ; si le hook écrit à l’écran autre chose que du JSON, l’opération est autorisée.

Le problème de la deuxième colonne est plus difficile à repérer : si le chemin du hook est faux ou si le script se fige, tout se passe comme si la règle n’existait pas. Là où l’opération doit absolument être bloquée, activez onFailure dans Claude Code et failClosed dans Cursor.

L’exemple ci-dessous est une version simplifiée, pour Claude Code, du contrôle de merge de notre dépôt. D’abord le fichier de configuration :

.claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/merge-gate.sh"
          }
        ]
      }
    ]
  }
}

Puis le script :

.claude/hooks/merge-gate.sh
#!/bin/bash
# L'entrée arrive en JSON sur stdin. Si elle est illisible, l'opération s'arrête.
COMMAND=$(jq -r '.tool_input.command // empty') || {
  echo "Le hook n'a pas pu lire l'entrée, opération arrêtée." >&2
  exit 2
}

case "$COMMAND" in
  *"MERGE_APPROVED=1 gh pr merge"*) exit 0 ;;  # l'accord est écrit dans la commande
  *"gh pr merge"*)
    echo "Le merge, c'est le propriétaire du projet qui le décide. Demandez d'abord." >&2
    exit 2 ;;                                 # l'opération s'arrête
esac
exit 0                                        # aucune objection

Quand le script sort avec 2, Claude Code n’exécute pas la commande et transmet à l’agent la phrase que vous avez écrite sur la sortie d’erreur. Vous pouvez aussi rendre la même décision en sortant avec 0 et en écrivant du JSON : le champ permissionDecision reçoit deny (refuser), ask (demander) ou allow (autoriser). Les hooks PreToolUse s’exécutent avant que les permissions soient vérifiées ; une décision deny s’applique même en mode bypassPermissions, qui saute toutes les demandes de permission. Codex ne prend pas encore en charge la valeur ask : le hook est considéré comme en échec et l’opération continue.

Quels hooks utilisons-nous dans notre dépôt ?

Le fichier .claude/settings.json de notre dépôt contient 3 hooks. Nous avons écrit chacun d’eux pour qu’une erreur qui nous était arrivée ne se reproduise pas.

Trois hooks dans notre dépôt. Le premier informe, le deuxième bloque, le troisième avertit. Le quatrième, c’est git qui le lance. SessionStart État du répertoire: Indique la branche, les fichiers non commités et les worktrees. PreToolUse · Bash Contrôle du merge: Bloque la commande de merge sans accord. PreToolUse · Edit|Write Alerte de branche: Avertit l’agent si la branche ou le commit a changé. git · pre-push Hook git: Se déclenche avant le push ; c’est git qui le lance, pas l’agent.
Les trois hooks Claude Code de notre dépôt, et un hook git. Le troisième s’exécute avant les outils Edit|Write|NotebookEdit. Source : le fichier de configuration et les scripts de hooks du dépôt.

Ouverture de session. Au démarrage d’une session, le hook SessionStart indique sur quelle branche on se trouve, combien de fichiers ne sont pas commités et quels worktrees sont ouverts, puis il conclut : « tu peux travailler dans ce répertoire » ou « ouvre d’abord un worktree séparé ». Cette règle figurait déjà dans CLAUDE.md. Mais une session a lu la règle sans l’appliquer à sa propre situation, et a prévu des modifications dans un répertoire qui a changé trois fois de branche en quarante minutes. Une règle qui dépend de la mémoire de l’agent ne protège rien ; le hook pose la question à la place de l’agent.

Contrôle du merge. Dans notre dépôt, une pull request n’est fusionnée qu’avec l’accord du propriétaire du projet. Mais un agent peut, une fois les tests passés, vouloir lancer gh pr merge comme étape suivante. Le hook PreToolUse qui s’exécute avant l’outil Bash bloque cette commande avec le code 2 et dit à l’agent de demander d’abord l’accord. Une fois l’accord obtenu, la commande est relancée avec une variable d’environnement ajoutée au début ; ainsi, l’accord figure dans la transcription de la session, juste à côté du merge. Le hook bloque aussi l’opération quand il n’arrive pas à lire son entrée : sa première version laissait passer les commandes qu’elle ne savait pas lire, en sortant avec 0.

Alerte de changement de branche. La conclusion donnée à l’ouverture décrit la situation à cet instant, et elle peut vieillir : si l’une de deux sessions ouvertes sur la même branche passe sur une nouvelle branche, l’endroit où travaille l’autre change. Le second hook PreToolUse, déclenché avant les outils qui modifient des fichiers, compare avant chaque modification la branche et le commit avec ce qui a été dit à la session en dernier. En cas de différence, il envoie à l’agent une note qui commence par cette ligne : « drift: this checkout MOVED since the session was last told what it is ». Il ne bloque pas l’opération ; il transmet la note par le champ additionalContext et sort avec 0.

Un hook git, c’est autre chose. Le même dépôt a aussi un hook pre-push : il empêche les push directs vers les branches protégées. Ce n’est pas l’agent de codage qui l’exécute, mais git ; il s’applique donc à tous ceux qui font un push depuis ce clone, agents compris, quel que soit l’outil utilisé. Les deux ne tournent que sur votre machine : --no-verify permet de sauter le pre-push, qui ne remplace donc pas les contrôles côté serveur.

Quelle différence entre un hook, un skill et le fichier de règles ?

Tous les trois orientent le comportement de l’agent, mais ils n’interviennent pas au même moment. Voici la comparaison que donne la documentation de Claude Code :

CLAUDE.md, skill et hook : quand chacun intervient, s’il s’applique à chaque fois, la place qu’il prend dans le contexte et à quoi il sert
CLAUDE.mdSkillHook
Quand il intervientAu début de chaque sessionQuand vous tapez /nom ou quand Claude le juge adapté à la tâcheChaque fois que son événement se produit
S’applique à chaque foisNon, c’est une information pour l’agentNon, Claude l’interprèteOui, il s’exécute à chaque événement
Place dans le contexteLa totalité, à chaque requêteSa description à chaque session, la totalité quand il sertAucune, tant qu’il ne renvoie rien
À quoi il sertRègles « fais toujours ceci », commandesWorkflows réutilisés, textes de référenceFormatage, blocage des commandes dangereuses, notifications

Source : documentation de Claude Code, page « Extend Claude Code », 18 octobre 2026.

En résumé : si c’est à l’agent de décider comment appliquer les étapes, utilisez un skill ; si l’opération doit se faire chaque fois de la même façon, utilisez un hook. La checklist de publication d’une version est un skill ; formater chaque fichier enregistré est un hook. Comme le dit la même page, une consigne dans le fichier de règles ou dans un skill est une demande, pas une garantie. Les règles dont la violation ne se rattrape pas (un push sur la mauvaise branche, un merge trop tôt) ne doivent pas reposer sur une simple consigne.

Les hooks ne se définissent pas seulement dans le fichier de configuration : les skills et les sous-agents peuvent aussi en porter dans leur propre définition. Dans Claude Code, le hook d’un sous-agent ne vaut que pendant que ce sous-agent s’exécute ; celui d’un skill, une fois le skill appelé, continue de s’exécuter jusqu’à la fin de la session. Dans Copilot, le hook d’un agent personnalisé ne s’exécute que lorsque cet agent est sélectionné.

Toutes les règles n’ont pas besoin d’un hook

Un hook s’exécute à chaque fois ; un hook mal écrit se trompe donc à chaque fois. Avant d’en écrire un, vérifiez ces points :

  • Gardez l’information stable dans le fichier de règles. La documentation de Claude Code recommande CLAUDE.md pour les informations fixes. Un hook convient mieux pour transmettre l’état du moment (la branche où vous êtes, le dernier résultat de la CI).
  • Ne confiez pas à un hook les décisions qui demandent du jugement. Un simple contrôle de texte ne peut pas répondre à « ce changement a-t-il besoin d’un test ? » ; une étape de ce genre s’écrit dans un skill.
  • Ne bloquez pas sans raison. Deux de nos trois hooks ne font qu’informer ; ils ne bloquent pas l’opération. Un hook qui bloque le travail à tort finit vite désactivé plutôt que corrigé.
  • Un hook qui cherche du texte n’est pas une mesure de sécurité. Notre contrôle de merge ne voit pas une commande de merge écrite dans une variable ou dans un eval. Il arrête l’agent qui a oublié de demander, pas celui qui cherche délibérément à contourner la règle. La documentation de Codex décrit elle aussi les hooks comme « a useful guardrail, not a complete enforcement boundary ». Pour les autorisations et les interdictions strictes, la documentation de Claude Code recommande les règles de permission.
  • Relisez comme du code un hook écrit par quelqu’un d’autre. Les hooks de commande s’exécutent avec vos droits d’utilisateur. Codex n’exécute pas un hook venu du dépôt tant que vous ne l’avez pas examiné et approuvé ; Cursor n’exécute les hooks de projet que dans les espaces de travail que vous jugez fiables ; Gemini CLI traite un hook modifié comme un hook nouveau, non approuvé.

Les hooks s’exécutent sur votre machine, avant l’opération ; ils ne vérifient pas si le code écrit est correct. Ça, c’est le travail de la revue de code : quand diffox examine une pull request, il lit aussi les règles à respecter dans le fichier de règles du dépôt (AGENTS.md, ou à défaut CLAUDE.md) et s’en sert comme règles de review.

Questions fréquentes

Qu’est-ce qu’un hook, et quels outils de codage en ont ?

Un hook est une commande que vous écrivez et que l’agent de codage exécute de lui-même à des moments précis. Au 18 octobre 2026, Claude Code, Codex, Cursor, Gemini CLI et GitHub Copilot en ont. Vous indiquez dans un fichier de configuration quelle commande s’exécute pour quel événement. Comme son exécution ne dépend pas de la décision de l’agent, la règle s’applique à chaque fois.

Comment un hook bloque-t-il une commande dans Claude Code ?

Si un hook attaché à l’événement PreToolUse se termine avec le code 2, Claude Code n’exécute pas la commande et transmet à l’agent l’explication que le hook a écrite sur sa sortie d’erreur. Sortir avec 0 veut dire « je n’ai pas d’objection » ; un autre code, comme 1, compte comme une erreur qui ne bloque pas l’opération. Le hook peut aussi sortir avec 0 et écrire du JSON : le champ permissionDecision vaut alors deny, ask ou allow.

Comment utiliser les hooks dans Cursor ?

Les hooks s’écrivent dans .cursor/hooks.json pour un projet, et dans ~/.cursor/hooks.json pour l’utilisateur. preToolUse se déclenche avant que l’agent utilise un outil, beforeShellExecution avant qu’il exécute une commande shell. Si le hook sort avec 2 ou renvoie permission: "deny" en JSON, l’opération s’arrête. Avec un autre code d’erreur, l’opération continue ; failClosed: true permet de changer cela. Les hooks de projet ne s’exécutent que dans les espaces de travail que vous jugez fiables.

Codex et Gemini CLI ont-ils des hooks ?

Oui. Dans Codex, les hooks s’écrivent dans .codex/hooks.json ou dans config.toml, et ils sont activés par défaut ; Codex n’exécute pas un hook venu du dépôt tant que vous ne l’avez pas examiné et approuvé. Dans Gemini CLI, les hooks se trouvent dans settings.json, et l’événement déclenché avant l’utilisation d’un outil s’appelle BeforeTool. Dans les deux, si le hook se termine avec le code 2, l’opération s’arrête.

Comment définir un hook dans GitHub Copilot ?

Vous placez un fichier JSON dans le dossier .github/hooks/ du dépôt ou, pour un usage personnel dans Copilot CLI, dans ~/.copilot/hooks/. Le fichier contient version: 1 et un objet hooks. preToolUse se déclenche avant l’utilisation d’un outil ; si le hook écrit permissionDecision: "deny" à l’écran ou sort avec 2, l’opération s’arrête. L’agent cloud lit le fichier de hooks sur la branche par défaut du dépôt.

Que se passe-t-il si un hook plante ou dépasse son délai ?

Dans la plupart des outils, l’opération continue. Dans Claude Code, un hook qui échoue ou dépasse son délai ne bloque pas l’opération ; dans Cursor aussi, un hook en échec laisse passer l’opération ; dans Copilot, un timeout laisse toujours passer l’opération. Pour changer cela, il existe l’option "onFailure": "block" dans Claude Code et "failClosed": true dans Cursor. Dans Copilot, en revanche, si un hook preToolUse sort avec un code d’erreur autre que 2, l’opération s’arrête.

Quelle est la différence entre un hook et un skill ?

Un hook est une commande que l’outil exécute quand un événement précis se produit, et il s’exécute à chaque fois. Un skill est un fichier d’instructions que l’agent lit et applique (SKILL.md dans Claude Code) : il est chargé quand vous l’appelez par son nom ou quand l’agent le juge adapté à la tâche, et c’est l’agent qui décide quoi faire. Si l’opération doit se faire chaque fois de la même façon, utilisez un hook ; si l’agent doit adapter les étapes à la situation, utilisez un skill.