DEV Community

Cover image for Git hooks : tout n'est pas bon à bloquer
Jérémy Chauvin
Jérémy Chauvin

Posted on

Git hooks : tout n'est pas bon à bloquer

Les git hooks, c’est une pratique qu’on voit de plus en plus dans les projets. On l’adopte souvent parce qu’on l’a vu ailleurs, ou parce que ça semble être une bonne idée pour garantir la qualité du code. Mais est-ce que c’est vraiment le cas ?

Avant d’aller plus loin, il faut expliquer ce qu’est un hook git. Avec git on peut exécuter des scripts lorsqu’on veut créer un commit, push etc (pre-commit, pre-push etc).

💡 Husky est un très bon outil pour découvrir et faire ses premiers hooks

En utilisant un pre-commit hook, lorsque l’on entre la commande “git commit”, un script est joué. Si celui-ci échoue, le commit est annulé. On est alors obligé de résoudre le problème afin de pouvoir commit.

Pour faciliter la collaboration dans l'équipe, on pourrait alors se dire "Super, je vais pouvoir vérifier que" :

  • que tous mes tests passent
  • que mon linter passe
  • que mon formateur passe
  • que le nom de mon commit respecte bien les conventions

C’est là que les problèmes commencent. Parce que oui, le feedback sera plus rapide, mais à quel prix ?

Le commit n'est pas une livraison

Avec les hooks, on lance les tests, le linter, le formateur et la vérification du nom de commit à chaque fois qu'on veut commit. Faire un commit devient lent et frustrant. On est dans notre flow, on a avancé sur notre feature, et on doit attendre que tout passe avant de pouvoir partager notre travail.

Et au final qu’est-ce qui se passe ? Si on finit par contourner les hooks avec --no-verify, c’est pour retrouver un flow de dev rapide.

💡 Git a une option --no-verify sur ses commandes pour ignorer les hooks

L’autre problème c’est que les hooks nous poussent vers du code “parfait” à chaque commit. Mais un commit ce n’est pas une livraison. Prenons un exemple concret : on travaille en TDD, on vient d’écrire un test rouge qui décrit le comportement qu’on veut implémenter. On veut commit ce test avant de passer à l’implémentation. Sauf que le hook pre-commit lance le build ou le linter ou le type check, ça échoue, et le commit est bloqué. On est forcé soit d’implémenter dans la foulée, soit de faire --no-verify. Le cycle red-green-refactor, qui repose justement sur des états intermédiaires, devient pénible.

C’est le principe même du commit : sauvegarder un état pour pouvoir revenir en arrière si besoin. Avec des hooks trop stricts, on nous demande du code parfait à chaque sauvegarde. On perd cette liberté.

Et le problème ne s’arrête pas au commit. Même quand le hook pre-commit se limite au lint et au build, on retrouve souvent les tests dans un hook pre-push. Résultat : on peut commit en local, mais impossible de push tant que les tests ne passent pas. Si on fait du pair ou du mob programming et qu’on veut partager un état de travail en cours, c’est bloqué. Si on a besoin d’un avis d’un collègue sur une approche avant de continuer, c’est bloqué. On retombe dans la même logique : du code “parfait” pour pouvoir collaborer.

Et ce n'est pas le seul problème…

Double pain, zero gain

L’autre problématique qu’on crée avec les hooks, c’est la duplication. Les mêmes vérifications tournent en local via les hooks, puis une deuxième fois dans la pipeline (Github Actions, Gitlab CI etc). On attend que les hooks passent, puis on attend que la CI relance exactement la même chose. On ne gagne pas de temps, on en perd.

Et ce n’est pas juste une question de temps. On se retrouve à maintenir les checks à deux endroits : la config des hooks et la pipeline. On met à jour une option dans une commande côté CI mais on oublie de le faire côté hooks, ou l’inverse. Les deux divergent, et on ne s’en rend compte que quand ça casse.

De plus, on risque de retomber dans le piège du "ça marche sur ma machine", ou justement l’inverse. L’environnement local et la CI ce n’est pas la même chose. Les versions des outils, les dépendances, la config... il y a de nombreuses raisons pour que le résultat soit différent. On se rajoute une charge cognitive en plus pour comprendre pourquoi ça passe chez nous et pas dans la pipeline, ou l’inverse.

Finalement, c’est la CI qui décide si on peut merge ou pas. C’est elle la source de vérité. Si on lui fait confiance pour bloquer un merge, pourquoi dupliquer ce travail en local ?

Mais alors, si les hooks posent autant de problèmes, pourquoi on continue à les utiliser ?

Ce qu'on compense vraiment

Le vrai problème c’est la feedback loop de notre CI. La pipeline prend 20 minutes, on ne reçoit aucune notification en cas d’échec, et surtout, tout reste très obscur. Car on a une grosse étape qui mélange plein de trucs et pour savoir ce qui a cassé on doit aller fouiller dans les logs. C’est frustrant.

Les hooks c’est une réponse à cette frustration. On veut un feedback plus rapide, plus clair, plus proche de nous. Et ça se comprend.

Dans tous les cas, les hooks c’est un pansement, pas une solution. On traite le symptôme mais pas la cause. Le vrai sujet c’est pas "comment avoir un feedback plus vite en local", c’est "pourquoi mon feedback est lent et pas clair à la base".

Si la pipeline met 20 minutes, rajouter des hooks en local ça ne rend pas la pipeline plus rapide. On a juste déplacé le problème. Le jour où le hook passe mais la CI échoue, on est au même point qu’avant, sauf qu’on a perdu du temps en plus.

Et si le feedback de la CI n’est pas clair, c’est rarement un problème d’outillage. C’est souvent qu’on a une grosse étape qui fait trop de choses, que les erreurs sont noyées dans les logs, que personne n’a pris le temps de rendre ça lisible. Les hooks ne résolvent rien de tout ça.

La vraie problématique c’est : comment réduire le temps du feedback loop et le rendre plus clair quand ça échoue. Et la réponse, c’est rarement "rajouter une couche en local". Alors on fait quoi ?

Réparer la source

Plutôt que de compenser en local, on devrait investir dans la Developer eXperience de notre pipeline.

Déjà, découper la CI en étapes claires et parallélisées. Une étape pour le lint, une pour les tests, une pour le build. Si le lint échoue, on le voit tout de suite, on n’a pas besoin d’aller fouiller dans les logs d’une étape monolithique. Et si les étapes tournent en parallèle, on réduit le temps total.

Ensuite, avoir des notifications quand la CI échoue. On ne devrait pas avoir à aller vérifier manuellement si la pipeline est passée. Un message Slack, un mail, une notif Github... peu importe, mais on doit le savoir rapidement.

Et enfin, rendre les erreurs lisibles. Quand ça échoue, on doit comprendre pourquoi en quelques secondes, pas en 5 minutes de lecture de logs. C’est un effort qui se fait une fois et qui profite à toute l’équipe, contrairement aux hooks qui restent une solution individuelle.

💡 Si on veut quand même un hook en attendant d’améliorer la CI, lint-staged permet de ne lancer le formatage et le lint que sur les fichiers stagés, avec correction automatique. Ça ne bloque pas le flow, c’est un compromis acceptable le temps de la transition.

Mais attention à ne pas tout mettre dans le hook. Le formatage et le lint fonctionnent bien en pre-commit parce qu’ils sont rapides et auto-corrigeables : on lance, ça corrige, on continue. La détection de secrets c’est un autre cas légitime : si la CI l’attrape, c’est déjà trop tard, le secret est dans l’historique git. Le hook pre-commit est le seul endroit où le bloquer a du sens. Les tests, c’est différent. Ils sont lents, ils échouent pour des raisons légitimes (un test rouge en TDD par exemple), et ils n’ont rien à corriger automatiquement. Les mettre dans un hook, que ce soit en pre-commit ou en pre-push, c’est retomber dans le problème qu’on essaie de résoudre : bloquer le flow pour forcer du code "parfait" avant de pouvoir collaborer. Les tests, c’est le rôle de la CI.

Mais avec l’arrivée des agents IA, le problème prend une autre dimension.

L’IA amplifie le problème, le commit change de rôle

Avec l’IA, le commit n’est plus juste une sauvegarde, c’est un mécanisme de supervision. L’IA va vite, elle touche beaucoup de fichiers, et on ne lit pas chaque ligne en temps réel. On a besoin de petits incréments pour garder le contrôle : relire un diff lisible plutôt que 20 fichiers changés d’un coup, faire un revert précis si l’IA part dans la mauvaise direction. Plus l’IA va vite, plus on a besoin de commiter souvent. Et plus on commit souvent, plus les hooks deviennent un frein. C’est un paradoxe : l’outil censé "garantir la qualité" empêche justement le mécanisme qui nous permet de contrôler la qualité du travail de l’IA.

Et quand un développeur fait --no-verify, on peut se dire qu’il est pressé ou négligent. Mais quand une IA — sans ego, sans impatience, sans flemme — contourne aussi les hooks, ça dit autre chose. Exemple concret : on demande à l’IA d’ajouter un champ dans un formulaire. Un test sans rapport échoue dans le hook pre-commit. L’IA ne peut pas commiter. On lui a dit de ne pas toucher aux tests. Mais le hook la bloque. Alors elle fait ce qu’un dev ferait : elle contourne. Elle rajoute du code pour faire passer le test sans le modifier directement, ou elle fait --no-verify. Le résultat : l’IA part en roue libre, le code se dégrade, et on finit par devoir tout revert parce qu’on n’a pas pu commiter avant que ça dérape. Si même un agent sans émotion contourne les hooks, c’est que le mécanisme est le problème, pas les gens.

Pourtant, on veut quand même que l’IA lance les tests, qu’elle vérifie le lint. Le problème c’est pas les vérifications, c’est que les git hooks sont un mécanisme unique pour deux acteurs aux besoins opposés. L’humain veut de la liberté : commiter un état intermédiaire, essayer un truc, revenir en arrière. L’IA a besoin de vérifications, mais adaptées à elle, avec un feedback qu’on contrôle.

Encore faut-il que l’IA lance les vérifications. Avec Claude Code par exemple, on peut écrire dans le CLAUDE.md du projet des instructions que l’agent suit à chaque session, comme "lance les tests après chaque modification". Et si le contexte de conversation est compacté et que les instructions risquent d’être perdues, on peut les mettre dans un skill dédié pour être sûr qu’elles sont toujours là.

Reste la question : que se passe-t-il quand ça échoue ? C’est là que ça devient intéressant. Des outils comme Claude Code proposent des hooks de cycle de vie internes à l’agent : on peut configurer un script qui s’exécute quand une commande échoue (par exemple les tests), et qui injecte du contexte dans la conversation de l’agent. Concrètement, quand les tests échouent, au lieu de bloquer le commit, le hook guide l’IA : "Analyse si cet échec est lié à tes changements. Si oui, corrige. Si non, informe le développeur." Ce n’est pas une règle rigide, c’est du guidage. L’IA reçoit le contexte et juge. Si le test qui échoue est directement lié à ce qu’elle vient de modifier, elle le corrige toute seule et continue. Si c’est un test sans rapport, elle commit pour sauvegarder l’état actuel et nous informe : "Ce test a échoué mais ça n’a rien à voir avec mes changements. Tu veux qu’on regarde ensemble ?"

Le git hook applique une règle aveugle — ça passe ou ça bloque, pour tout le monde, sans distinction. Le hook d’agent donne du contexte et laisse le jugement à l’IA, tout en rendant le contrôle au développeur. Et ça ne s’arrête pas au local : l’agent peut aussi réagir aux événements de la CI, recevoir une notification d’échec et analyser ou corriger l’erreur automatiquement.

Concrètement, avec Claude Code, ça ressemble à ça dans le .claude/settings.json du projet :

{
  "hooks": {
    "PostToolUseFailure": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(npm run test|vitest)",
            "command": "echo '{\"additionalContext\": \"Des tests ont échoué. Analyse si ces échecs sont liés à tes changements. Si oui, corrige-les. Si non, commit tes changements et informe le développeur des tests qui échouent sans rapport avec ton travail.\"}'"
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Le hook ne se déclenche que quand npm run test ou vitest échoue. Il n'injecte pas une règle, il injecte du contexte que l'IA utilise pour juger de la marche à suivre.

💡 Pour aller plus loin sur les hooks Claude Code (PostToolUseFailure, additionalContext, matcher, etc.), la documentation complète est disponible ici : https://code.claude.com/docs/fr/hooks-guide

Un outil, plusieurs contextes

Ce que je décris ici s’applique à un contexte précis : une équipe en entreprise, avec une CI, et des développeurs qui travaillent ensemble au quotidien. Dans ce cadre, les hooks compensent un problème qu’on a les moyens de résoudre autrement.

Mais les git hooks ne viennent pas de nulle part. Ils ont été pensés pour des projets où les contributeurs sont nombreux, de niveaux très différents, et où personne ne maîtrise l’ensemble du projet. C’est exactement le cas de l’open source.

Sur un projet open source, les contraintes sont autres. Les contributeurs ne connaissent pas forcément les conventions du projet, ils ont des setups locaux très différents, et ils ne contribuent parfois qu’une seule fois. Un contributeur occasionnel qui ouvre une PR ne va pas passer 20 minutes à comprendre pourquoi la pipeline a échoué. Si un hook lui corrige le formatage et lui signale une erreur de lint avant même de push, c’est du temps gagné pour tout le monde : pour lui et pour les mainteneurs qui relisent.

Dans ce contexte, les hooks ne compensent pas une mauvaise CI. Ils servent de filet de sécurité accessible, qui réduit le bruit dans les PRs et accélère le feedback pour des gens qui ne contribuent peut-être qu’une seule fois. C’est un usage légitime, et c’est d’ailleurs pour ça qu’on les retrouve dans beaucoup de projets open source bien maintenus. Next.js, Angular, webpack, Storybook, NestJS, Prisma... tous utilisent des hooks pour lancer le formatage et le lint sur les fichiers stagés avant chaque commit. Pas les tests, pas le build. Juste ce qui est rapide et auto-corrigeable.

Les git hooks ce n’est pas un mauvais outil. En open source, c’est un filet de sécurité qui a fait ses preuves. Mais dans une équipe en entreprise, avec une CI et les moyens d’investir dedans, s’en servir pour compenser un feedback loop lent c’est mettre un pansement. Le problème n’est pas en local, il est dans la pipeline. C’est là qu’il faut agir.

Top comments (0)