Indépendant · Depuis 2009~750€ / jour

Retour aux actualités

Le hook qui ne se déclenche sur rien : deadweight, deux semaines plus tard

Vous voulez qu'un script tourne avant chaque commande git et avant chaque écriture de fichier. Dans settings.json, vous écrivez le matcher du hook comme vous écrivez déjà vos permissions : Bash(git *)|Write. Claude Code l'accepte sans un mot. Le hook se déclenche sur les écritures, jamais sur git.

Un matcher de hook n'est pas une règle de permission. Dès qu'il contient autre chose que des lettres, des chiffres, _, -, des espaces, , et |, la documentation des hooks le lit comme une expression régulière comparée au seul nom de l'outil. Bash(git *) ne correspond à aucun outil : le filtre sur la commande devait aller dans le champ if.

Le cas voisin est plus sournois. Write|Edit(*.ts) n'est même pas une expression régulière valide, et la documentation ne dit pas ce que Claude Code en fait. Rejoué le 17 octobre sur Claude Code 2.1.302, avec un hook qui crée un fichier-témoin : un hook sur Write|Edit(*.ts) ne s'est pas déclenché quand Claude a créé un fichier avec l'outil Write, alors que le même hook sur Write|Edit s'est déclenché. Le garde-fou posé sur vos fichiers TypeScript ne garde rien.

Voici un settings.json avec ces deux hooks, plus une règle deny sur Read($HOME/.aws/**). De ces trois erreurs, deadweight 0.25 ne signalait que la dernière. La 0.26, sur les mêmes fichiers (sortie réelle, coupes marquées [...]) :

=== ERROR (1) ===
  [35-hooks-matcher] PreToolUse[1] in '.claude/settings.json' has matcher 'Write|Edit(*.ts)',
      which holds the permission rule 'Edit(*.ts)' and is not a valid regular expression. The
      hooks page does not say what Claude Code does with one; measured once, on Claude Code
      2.1.302, such a group fired on none of its tools. Write the matcher 'Write|Edit' and put
      'Edit(*.ts)' in a handler's `if` field (hooks).

=== WARN (1) ===
  [42-permissions-rule] deny rule 'Read($HOME/.aws/**)' in 'settings.json' names `$HOME`, which
      is not expanded in a permission rule [...]. Write Read(~/.aws/**) (permissions).

=== NOTICE (3) ===
  [35-hooks-matcher] PreToolUse[0] in '.claude/settings.json' lists 'Bash(git *)' in matcher
      'Bash(git *)|Write', written as a permission rule. A matcher is compared with the tool
      name only, so that alternative matches no tool; the others still fire. [...]
  [...]

Le $HOME est l'autre piège de l'exemple : la variable n'est pas développée dans une règle de permission. Rejoué le 17 octobre sur la 2.1.302, avec la règle passée en option de ligne de commande (--disallowedTools) : un deny sur Read($HOME/...) a laissé lire le fichier, le même deny écrit avec ~/ l'a bloqué. Une règle écrite avec $HOME ne protège rien, et rien ne vous le dit.

Deux semaines, huit versions

J'ai présenté deadweight le 30 septembre, puis expliqué comment il est construit le 1er octobre. Depuis, huit versions sont sorties, jusqu'à la 0.27.0, publiée le 17 octobre. Voici ce qu'elles changent pour qui s'en sert.

Moins de fausses alertes. Le 8 octobre, une relecture externe de l'auditeur a remonté une série de défauts, chacun soumis à un second relecteur chargé de le réfuter ; 139 ont été corrigés dans la 0.23. Sur 19 dépôts privés, à fichiers identiques, les avertissements sont passés de 87 à 74. La 0.26 en retire un de plus : un SKILL.md qui commence par un BOM UTF-8 était déclaré « sans en-tête YAML », alors que Claude Code le lit normalement.

Plus de réglages ignorés repérés. Les matchers et le $HOME ci-dessus, mais aussi un claude.md en minuscules, que Claude Code ne charge pas sous Linux (mesuré sur la 2.1.291 ; macOS et Windows n'ont pas été testés). Ou TaskOutput, retiré des outils dans la 2.1.277 : resté seul dans les tools: d'un agent, il fait refuser l'agent ; à côté de Read, il n'accorde rien. Autre piège de ponctuation : @docs/b.md: ou @docs/b.md, n'est pas un import. Mesuré sur Claude Code 2.1.302, en une seule exécution : rien ne se charge quand :, ,, ., ", ;, !, ? ou ) suit le chemin, ni quand (, ", ' ou un accent grave isolé précède le @. La 0.27 le signale en NOTICE : pour charger le fichier, écrire une espace après le chemin ; pour seulement le nommer, le mettre entre accents graves. Les autres voisins du chemin n'ont pas été sondés.

Chaque alerte dit ce qu'elle vaut. Depuis la 0.23, le rapport affiche à côté de chaque contrôle sa précision mesurée, sa date et le nombre de dépôts. Pour les matchers : precision 1/1 (100 %, 1 repositories, too few to conclude). Une alerte vérifiée sur un seul dépôt ; l'outil écrit lui-même que c'est trop peu. Ces mesures ont été étiquetées par l'assistant qui a écrit les contrôles, pas en aveugle.

Ce que la session charge hors du dépôt. Une session charge aussi vos skills personnels, vos plugins et vos serveurs MCP. Depuis la 0.25, deadweight --environment les compte par origine. Sur un projet mesuré le 9 octobre, au premier tour : 16 705 tokens venaient du projet, 5 243 des skills et plugins du compte, 4 648 de 17 serveurs MCP. Par défaut, c'est une estimation tirée de la taille des fichiers, qui ignore les serveurs ajoutés avec claude mcp add : leur fichier contient aussi vos secrets, l'outil refuse de l'ouvrir. --measure mesure pour de bon, mais lance claude -p dans le dossier sans la boîte de dialogue de confiance : les hooks du projet tournent, ses serveurs MCP se connectent. À réserver à un dossier que vous connaissez.

Le reste se voit peu à l'usage : 465 cas de test et une veille quotidienne sur les versions de Claude Code, qui en publie à peu près une par jour. Ce que vous en tirez : des listes alignées sur Claude Code jusqu'à la 2.1.302, et run_probes.py, qui rejoue contre votre propre claude les faits que l'outil donne pour mesurés.

Pourquoi la 0.27

Ces corrections s'additionnent. Les imports suivis sur quatre sauts n'existent que dans la 0.27, les matchers et le BOM que depuis la 0.26, le $HOME depuis la 0.24, la relecture externe depuis la 0.23. Une installation restée à la 0.21 n'a rien de tout ça.

Si vous utilisez le seuil de CI, une précaution : la 0.27 change l'auditeur, donc un seuil enregistré avec la 0.26 ne se compare plus. Relancez --set-floor une fois et committez floor.json seul, pour que l'historique distingue le changement d'outil de vos propres corrections.

Ce qu'il ne fait toujours pas

Il n'audite pas votre configuration personnelle. Pointé sur ~/.claude, il le dit et ne contrôle rien (il ne prétend plus, comme avant la 0.24, que vos skills personnels ne se chargent nulle part). --environment compte ce qui s'y trouve sans le juger.

Un @import cassé est signalé dans CLAUDE.md et dans les fichiers qu'il importe, jusqu'aux quatre sauts que Claude Code suit. Le fichier du quatrième saut est chargé mais ses propres imports ne le sont pas ; l'auditeur n'en lit pas les jetons. Un @docs/NOTES sans extension se charge aussi, et l'auditeur ne le lit toujours pas. Et l'erreur sur un matcher invalide repose sur une seule exécution, ce que le message dit en toutes lettres.

L'essayer

claude plugin marketplace add e-xode/deadweight --scope project
claude plugin install deadweight@e-xode --scope project

C'est gratuit, sous licence MIT : https://github.com/e-xode/deadweight

Je travaille en indépendant sur ce type de problème : christophe.bragard@e-xode.net.

Commentaires (0)

Aucun commentaire pour le moment.

Confiez-nous votre budget, on gère tout.

Un devis gratuit, une réponse sous 24 heures, et un premier échange sans engagement. Développement, campagnes, ou les deux : parlons de votre projet.

Me contacter