<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michel Faure </title>
    <description>The latest articles on DEV Community by Michel Faure  (@michelfaure).</description>
    <link>https://dev.to/michelfaure</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3897818%2Fe862356f-3aa1-4b73-91c7-56acc29bc243.png</url>
      <title>DEV Community: Michel Faure </title>
      <link>https://dev.to/michelfaure</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/michelfaure"/>
    <language>en</language>
    <item>
      <title>Après 5 commits sans vous, votre agent a quitté la boucle : l'idée du méta-hook</title>
      <dc:creator>Michel Faure </dc:creator>
      <pubDate>Mon, 08 Jun 2026 11:20:41 +0000</pubDate>
      <link>https://dev.to/michelfaure/apres-5-commits-sans-vous-votre-agent-a-quitte-la-boucle-lidee-du-meta-hook-5cpg</link>
      <guid>https://dev.to/michelfaure/apres-5-commits-sans-vous-votre-agent-a-quitte-la-boucle-lidee-du-meta-hook-5cpg</guid>
      <description>&lt;h2&gt;
  
  
  La nuit où neuf commits sont partis sans moi
&lt;/h2&gt;

&lt;p&gt;Un soir de mai, je laisse une session ouverte sur un projet annexe, le genre qu'on tient le week-end quand on a envie de coder pour rien. La consigne est simple, l'agent enchaîne &lt;code&gt;/goal&lt;/code&gt; sur &lt;code&gt;/goal&lt;/code&gt;. Je dîne, je couche les enfants, je lis vingt pages sur la trace photographique, je m'endors. Au matin, je trouve neuf commits autonomes propres, chacun ancré sur un artefact validé matériellement comme R15 l'exige depuis la v0.6. Aucun travail perdu. Aucun stall. Et, le détail qui m'arrête sur le seuil de mon bureau avec mon café à la main, aucun session log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Neuf commits, zéro log
&lt;/h2&gt;

&lt;p&gt;Le skill &lt;code&gt;/close-session&lt;/code&gt; n'a pas été déclenché parce que son trigger naturel n'est jamais arrivé : un humain qui décide qu'il a fini sa séance et tape la commande. Je n'avais pas fini la séance, je dormais. Le skill &lt;code&gt;/challenger&lt;/code&gt; n'a pas été convoqué non plus, faute de bug à fixer. La discipline doctrinale, dans cette nuit-là, ne tenait à rien parce que les triggers d'invocation supposaient une main humaine sur le clavier, et qu'il n'y en avait plus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce que R15 sauvait, ce qu'elle ne voyait pas
&lt;/h2&gt;

&lt;p&gt;R15 a fait son travail. &lt;em&gt;Commit chaque artefact dès qu'il franchit son oracle matériel, ne pas batcher.&lt;/em&gt; Neuf artefacts, neuf commits. Si la session avait stallé au huitième, j'aurais retrouvé huit unités de travail validées, pas un seul gros patch perdu dans un crash silencieux.&lt;/p&gt;

&lt;p&gt;Pourtant je vois, en relisant le log, deux phénomènes distincts qu'il fallait des règles distinctes pour cadrer. La persistance du travail relève de la cadence de commits, puisqu'un agent qui meurt n'emporte rien dans sa tombe. La dérive de raisonnement relève d'un autre dispositif, celui qui rattrape un agent enchaînant cinq décisions plausibles sans qu'aucune voix extérieure ne lui rappelle de douter. R15 sait sauver. Elle ne sait pas réveiller.&lt;/p&gt;

&lt;p&gt;Le mécanisme de la dérive n'a rien de malicieux. Le &lt;em&gt;reinforcement learning from human feedback&lt;/em&gt; n'entraîne pas un modèle à demander spontanément à être contredit, il l'entraîne à plaire au prompteur. Quand le prompteur dort, plus personne ne pousse à la friction. La complaisance n'apparaît pas, elle remonte simplement à la surface comme une nappe qu'aucun barrage ne retient plus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le prototype méta-hook
&lt;/h2&gt;

&lt;p&gt;Le prototype est sobre. Un hook PostToolUse qui décompte les invocations consécutives d'agents background, de sub-agents délégués, ou de commandes &lt;code&gt;/goal&lt;/code&gt; enchaînées sans message humain intermédiaire substantiel. Au-delà de cinq, le hook déclenche un prompt système qui force l'invocation de &lt;code&gt;falsify-before-fix&lt;/code&gt; ou de &lt;code&gt;close-session&lt;/code&gt; selon le contexte. Le compteur reset sur tout message utilisateur de plus de vingt caractères, hors &lt;em&gt;OK&lt;/em&gt;, &lt;em&gt;yes&lt;/em&gt;, &lt;em&gt;proceed&lt;/em&gt; qui ne corrigent rien et ne questionnent rien.&lt;/p&gt;

&lt;p&gt;Cinq n'est pas mesuré, c'est un choix doctrinal. Trop bas et le hook devient un parasite qui s'allume sur chaque session normale. Trop haut et la dérive a déjà eu lieu quand il s'allume. Cinq couvre empiriquement la fenêtre où, dans mon usage, une session passe de &lt;em&gt;productive&lt;/em&gt; à &lt;em&gt;autonome au-delà du raisonnable&lt;/em&gt;. Je révise si la pratique le démontre. Il vit sur le projet laboratoire, pas sur l'ERP, là où il bénéficie d'une urgence moindre, d'une infrastructure hook user-scope déjà en place, et d'une tolérance haute pour des expérimentations qui rendraient un ERP de production grincheux.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce que la nuit a fermé
&lt;/h2&gt;

&lt;p&gt;Le pattern est sec : la discipline tient quand un humain invoque les triggers, tombe quand l'autonomie prend la main. C'est une asymétrie, et qu'on peut tenter de corriger par un dispositif. Ne pas la nommer reviendrait à coder à l'oreille au moment précis où l'oreille humaine n'écoute plus.&lt;/p&gt;

&lt;p&gt;Si un autre dev solo Claude Code a une expérience comparable de session prolongée sans intervention, ou un dispositif différent qui couvre le même angle, j'écoute. Les commentaires deviennent inputs pour la version suivante du toolkit.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Counterpart Toolkit v0.7, amendement R15. Prototype méta-hook &lt;code&gt;autonomy-detection.sh&lt;/code&gt; sur projet laboratoire. Source : &lt;a href="https://github.com/michelfaure/doctrine-counterpart/blob/main/CLAUDE.md" rel="noopener noreferrer"&gt;github.com/michelfaure/doctrine-counterpart/blob/main/CLAUDE.md&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>After 5 commits without you, your agent has left the loop: the meta-hook idea</title>
      <dc:creator>Michel Faure </dc:creator>
      <pubDate>Mon, 08 Jun 2026 11:20:39 +0000</pubDate>
      <link>https://dev.to/michelfaure/after-5-commits-without-you-your-agent-has-left-the-loop-the-meta-hook-idea-3044</link>
      <guid>https://dev.to/michelfaure/after-5-commits-without-you-your-agent-has-left-the-loop-the-meta-hook-idea-3044</guid>
      <description>&lt;h2&gt;
  
  
  The night nine commits shipped without me
&lt;/h2&gt;

&lt;p&gt;One evening in May I leave a session open on a side project, the kind you keep for weekends. The instruction is simple, the agent chains &lt;code&gt;/goal&lt;/code&gt; after &lt;code&gt;/goal&lt;/code&gt;. I have dinner, I put the children to bed, I fall asleep. Next morning I find nine clean autonomous commits, each anchored on a materially validated artifact, exactly as R15 has been demanding since v0.6. No work lost. No stall. And, the detail that stops me in the doorway with my coffee, no session log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nine commits, zero log
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/close-session&lt;/code&gt; skill never fired because its natural trigger never arrived: a human deciding the session is over and typing the command. I had not decided, I was asleep. &lt;code&gt;/challenger&lt;/code&gt; was not invoked either, no bug having surfaced. Doctrinal discipline that night rested on nothing, since every invocation trigger assumed a hand on the keyboard, and there was none.&lt;/p&gt;

&lt;h2&gt;
  
  
  What R15 saved, what it did not see
&lt;/h2&gt;

&lt;p&gt;R15 did its job. &lt;em&gt;Commit each artifact as soon as it crosses its material oracle, do not batch.&lt;/em&gt; Nine artifacts, nine commits. If the session had stalled at the eighth, I would have found eight units of validated work, not one large patch lost in a silent crash.&lt;/p&gt;

&lt;p&gt;But I see, re-reading the log, two distinct phenomena that needed two distinct rules. Work persistence belongs to commit cadence, since an agent that dies takes nothing to the grave. Reasoning drift belongs to a different mechanism, the one that catches an agent chaining five plausible decisions without any external voice reminding it to doubt. R15 knows how to save. It does not know how to wake.&lt;/p&gt;

&lt;p&gt;The drift mechanism is not malicious. Reinforcement learning from human feedback does not train a model to spontaneously demand contradiction, it trains it to please the prompter. When the prompter sleeps, no one pushes for friction. Complacency does not appear in those conditions, it simply rises to the surface like a sheet of water that no dam holds back any longer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The meta-hook prototype
&lt;/h2&gt;

&lt;p&gt;The prototype is sober. A PostToolUse hook counts consecutive invocations of background agents, delegated sub-agents, or chained &lt;code&gt;/goal&lt;/code&gt; commands without a substantial human message in between. Past five, the hook triggers a system prompt that forces invocation of &lt;code&gt;falsify-before-fix&lt;/code&gt; or &lt;code&gt;close-session&lt;/code&gt; depending on context. The counter resets on any user message above twenty characters, excluding &lt;em&gt;OK&lt;/em&gt;, &lt;em&gt;yes&lt;/em&gt;, &lt;em&gt;proceed&lt;/em&gt;, which fix nothing and question nothing.&lt;/p&gt;

&lt;p&gt;Five is not measured, it is a doctrinal choice. Lower and the hook becomes a parasite firing on every normal session. Higher and the drift has already happened by the time it fires. Five covers, empirically, the window where a session goes from &lt;em&gt;productive&lt;/em&gt; to &lt;em&gt;autonomous beyond reason&lt;/em&gt;. I will revise if practice demands it. The hook lives on the laboratory project, not on the ERP, where it benefits from a lower urgency, an existing hook infrastructure, and a high tolerance for experiments that would make a production ERP grumpy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the night closed
&lt;/h2&gt;

&lt;p&gt;The pattern is dry: discipline holds when a human invokes the triggers, falls when autonomy takes over. It is an asymmetry, and one you can try to correct with a device. Not naming it would amount to coding by ear at the exact moment no human ear is listening.&lt;/p&gt;

&lt;p&gt;If another solo Claude Code dev has run a prolonged unattended session, or built a different device for the same angle, I am listening.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Counterpart Toolkit v0.7, amendment R15. Meta-hook &lt;code&gt;autonomy-detection.sh&lt;/code&gt; in prototype on the laboratory project. Source: &lt;a href="https://github.com/michelfaure/doctrine-counterpart/blob/main/CLAUDE.md" rel="noopener noreferrer"&gt;github.com/michelfaure/doctrine-counterpart/blob/main/CLAUDE.md&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Pourquoi votre sub-agent ne charge pas la même mémoire que vous (et comment il pousse sur main dans votre dos)</title>
      <dc:creator>Michel Faure </dc:creator>
      <pubDate>Sat, 06 Jun 2026 08:46:54 +0000</pubDate>
      <link>https://dev.to/michelfaure/pourquoi-votre-sub-agent-ne-charge-pas-la-meme-memoire-que-vous-et-comment-il-pousse-sur-main-dans-4p4b</link>
      <guid>https://dev.to/michelfaure/pourquoi-votre-sub-agent-ne-charge-pas-la-meme-memoire-que-vous-et-comment-il-pousse-sur-main-dans-4p4b</guid>
      <description>&lt;h2&gt;
  
  
  Le commit qu'aucun parent n'aurait passé
&lt;/h2&gt;

&lt;p&gt;18 mai, fin d'après-midi. Je délègue à un sub-agent un chantier d'autosend de premiers contacts, six fichiers à toucher. Le brief tient en quinze lignes, phase 0 nommée, commandes d'audit listées avant tout INSERT. Trois quarts d'heure plus tard, retour : &lt;em&gt;committed to main&lt;/em&gt;. Je relis deux fois. Je lance &lt;code&gt;git log --oneline -5&lt;/code&gt; et je trouve &lt;code&gt;3756e63&lt;/code&gt;, un commit feature posé sur la branche par défaut, sans branche, sans PR, sans tag &lt;code&gt;[workaround-assumed]&lt;/code&gt;. Trente minutes de cherry-pick, de reset et de PR rétroactive, &lt;em&gt;comme si rien n'était&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Ce qui cuit, c'est que cette classe d'incident, je l'avais eue dix jours plus tôt. Le 14 mai, un commit à moi était parti sur la mauvaise branche après qu'un &lt;code&gt;git checkout&lt;/code&gt; antérieur a été silencieusement annulé entre deux tours. J'avais écrit la règle le soir même, &lt;code&gt;feedback_git_branch_check_avant_commit.md&lt;/code&gt;, deux paragraphes : &lt;em&gt;"avant tout commit non-trivial, taper &lt;code&gt;git branch --show-current&lt;/code&gt;"&lt;/em&gt;. Je la consulte mécaniquement depuis. Le sub-agent qui a poussé &lt;code&gt;3756e63&lt;/code&gt; ne l'avait jamais lue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce que la mémoire d'un parent ne transmet pas
&lt;/h2&gt;

&lt;p&gt;Mon modèle mental était faux. Je m'imaginais une hiérarchie où la mémoire user-scope que je consulte — cent vingt feedbacks à l'heure où j'écris ces lignes — descendait &lt;em&gt;par héritage&lt;/em&gt; vers les agents délégués. Comme si appeler un sub-agent revenait à lui tendre une boîte d'outils déjà ouverte, mes règles dedans.&lt;/p&gt;

&lt;p&gt;La réalité est plus crue. Un sub-agent opère dans &lt;strong&gt;sa propre sandbox de contexte&lt;/strong&gt;. Il reçoit le brief que je lui écris, éventuellement un sous-ensemble de rules projet-scope rattachées au répertoire de travail, mais pas l'index user-scope du parent. Aucune transitivité. La règle que je traite comme &lt;em&gt;load-bearing&lt;/em&gt;, celle dont la violation produit l'incident, est opérationnellement absente pour le délégué si elle n'est pas inlinée dans le brief.&lt;/p&gt;

&lt;p&gt;L'asymétrie reste invisible tant que j'opère seul. Le parent charge sa mémoire, applique ses règles, le système tient. Elle devient un trou structurel dès que je délègue, et plus la délégation est répétée, plus la classe d'incident est probable. La mémoire d'un agent ne se transmet pas par héritage. Elle se transmet par briefing explicite, ou pas du tout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tous les feedbacks ne pèsent pas pareil
&lt;/h2&gt;

&lt;p&gt;Certes, on pourrait objecter qu'inliner &lt;em&gt;tous&lt;/em&gt; les feedbacks dans chaque brief reviendrait à reconstruire un index complet à chaque appel, et qu'aucun sub-agent ne lirait un brief de deux mille mots avec attention. L'objection est juste, et la règle ne demande pas cela.&lt;/p&gt;

&lt;p&gt;La plupart de mes feedbacks ne sont pas équivalents. Certains sont génériques — ma préférence pour le français, ma signature de commit, mes goûts typographiques. D'autres portent un &lt;strong&gt;invariant structurel&lt;/strong&gt; dont la violation produit un incident immédiat ou différé : &lt;em&gt;vérifier la branche avant chaque commit non-trivial&lt;/em&gt;, &lt;em&gt;jamais de bulk DELETE sans pré-flight count récent&lt;/em&gt;, &lt;em&gt;audit DB matériel avant tout test contrat&lt;/em&gt;. Ceux-là, je ne les saute pas. Le critère tient en une phrase : un feedback est &lt;em&gt;load-bearing&lt;/em&gt; pour une tâche si, pour le parent, sauter cette règle aurait produit l'incident qu'on cherche à éviter. Un coût de récupération qui vaut la peine pour le parent est un coût de briefing qui vaut la peine pour le délégué.&lt;/p&gt;

&lt;h2&gt;
  
  
  L'amendement R9, dans son texte
&lt;/h2&gt;

&lt;p&gt;J'ai posé l'amendement dans la version 0.7 du toolkit :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;R9 amendment — The brief must inline (or path-reference) the
user-scope feedbacks the parent treats as load-bearing for
this task. Sub-agents do not transitively inherit the parent's
memory index — what is not in the brief is operationally absent.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trois cas d'application reviennent. Agent qui touche au git : inliner le feedback branch-check. Agent qui touche aux opérations bulk DB : inliner le pre-flight count et la whitelist de sources safe. Agent qui touche à l'audit : inliner le feedback d'audit DB matériel. Deux ou trois lignes de brief, une classe d'incident entière évitée.&lt;/p&gt;

&lt;h2&gt;
  
  
  La discipline qui tient quand un humain est dans la boucle
&lt;/h2&gt;

&lt;p&gt;La doctrine tient quand un humain est dans la boucle, et tombe dès que l'autonomie prend la main. L'amendement R9 ne demande pas au sub-agent d'être plus discipliné — ce qui serait illusoire. Il demande au parent de &lt;strong&gt;matérialiser&lt;/strong&gt; sa propre discipline dans le brief, avant de cliquer &lt;em&gt;delegate&lt;/em&gt;. J'aurais épargné trente minutes de cherry-pick si j'avais consacré quinze secondes à inliner &lt;em&gt;check git branch&lt;/em&gt; dans le brief de cet après-midi-là. La même règle, deux fois : une fois pour moi, une fois pour le délégué.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Counterpart Toolkit v0.7, amendement R9. Toolkit public sous CC-BY-4.0. La règle vit dans &lt;a href="https://github.com/michelfaure/doctrine-counterpart" rel="noopener noreferrer"&gt;&lt;code&gt;doctrine-counterpart/CLAUDE.md&lt;/code&gt;&lt;/a&gt; ; l'audit matériel qui a justifié l'amendement vit dans &lt;code&gt;v0.7-candidates.md&lt;/code&gt; — N=1 structurel, promote sur arbitrage : le mode de défaillance silencieuse se mesure en gravité, pas en fréquence.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why your sub-agent doesn't load the same memory as you (and how it commits to main behind your back)</title>
      <dc:creator>Michel Faure </dc:creator>
      <pubDate>Sat, 06 Jun 2026 08:46:51 +0000</pubDate>
      <link>https://dev.to/michelfaure/why-your-sub-agent-doesnt-load-the-same-memory-as-you-and-how-it-commits-to-main-behind-your-back-cgi</link>
      <guid>https://dev.to/michelfaure/why-your-sub-agent-doesnt-load-the-same-memory-as-you-and-how-it-commits-to-main-behind-your-back-cgi</guid>
      <description>&lt;h2&gt;
  
  
  The commit no parent would have made
&lt;/h2&gt;

&lt;p&gt;18 May, late afternoon. I delegate a six-file autosend chantier to a sub-agent. Brief: fifteen lines, named phase 0, audit commands before any INSERT. Forty-five minutes later, the report lands: &lt;em&gt;committed to main&lt;/em&gt;. I run &lt;code&gt;git log --oneline -5&lt;/code&gt; and find &lt;code&gt;3756e63&lt;/code&gt;, a feature commit on the default branch. No branch, no PR. Thirty minutes of cherry-pick.&lt;/p&gt;

&lt;p&gt;I had the same class of incident ten days earlier. On 14 May, a commit of mine went to the wrong branch because an earlier &lt;code&gt;git checkout&lt;/code&gt; had been silently undone. I wrote the rule the same night, &lt;code&gt;feedback_git_branch_check_avant_commit.md&lt;/code&gt;: &lt;em&gt;"before any non-trivial commit, run &lt;code&gt;git branch --show-current&lt;/code&gt;"&lt;/em&gt;. I consult it mechanically. The sub-agent that pushed &lt;code&gt;3756e63&lt;/code&gt; had never read it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a parent's memory doesn't transmit
&lt;/h2&gt;

&lt;p&gt;My mental model was wrong. I pictured a hierarchy where the user-scope memory I consult — one hundred and twenty feedback files — descended &lt;em&gt;by inheritance&lt;/em&gt; to delegated agents.&lt;/p&gt;

&lt;p&gt;The reality is cruder. A sub-agent operates in &lt;strong&gt;its own context sandbox&lt;/strong&gt;. It gets the brief I write, perhaps a subset of project-scope rules tied to the working directory, but not the parent's user-scope index. No transitivity. The rule I treat as &lt;em&gt;load-bearing&lt;/em&gt; — the one whose violation produces the incident — is operationally absent for the delegate unless I inline it in the brief.&lt;/p&gt;

&lt;p&gt;The asymmetry is invisible alone. The parent loads its memory, the system holds. It becomes a structural hole the moment I delegate, and the more I delegate, the more probable the incident class. Agent memory doesn't pass by inheritance. It passes by explicit briefing, or not at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not all feedbacks carry the same weight
&lt;/h2&gt;

&lt;p&gt;One could object that inlining &lt;em&gt;every&lt;/em&gt; feedback into every brief rebuilds the index at each call. The objection is fair, and the rule does not ask for that.&lt;/p&gt;

&lt;p&gt;Most of my feedbacks are not equivalent. Some are generic — language preference, commit signature, typographic tastes. Others carry a &lt;strong&gt;structural invariant&lt;/strong&gt; whose violation produces an incident: &lt;em&gt;check the branch before each non-trivial commit&lt;/em&gt;, &lt;em&gt;no bulk DELETE without a fresh count&lt;/em&gt;, &lt;em&gt;materially audit the DB before any contract test&lt;/em&gt;. The criterion is one sentence. A feedback is &lt;em&gt;load-bearing&lt;/em&gt; if, for the parent, skipping it would have produced the incident we want to avoid. A retrieval cost worth paying for the parent is a briefing cost worth paying for the delegate.&lt;/p&gt;

&lt;h2&gt;
  
  
  R9 amendment
&lt;/h2&gt;

&lt;p&gt;I wrote the amendment into v0.7 of the toolkit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;R9 amendment — The brief must inline (or path-reference) the
user-scope feedbacks the parent treats as load-bearing for
this task. Sub-agents do not transitively inherit the parent's
memory index — what is not in the brief is operationally absent.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three cases recur. Git-touching agent: inline the branch-check feedback. Bulk DB agent: inline the pre-flight count and the safe-source whitelist. Audit-touching agent: inline the material DB audit feedback. Two or three lines of brief, an entire incident class avoided.&lt;/p&gt;

&lt;h2&gt;
  
  
  The discipline that holds when a human is in the loop
&lt;/h2&gt;

&lt;p&gt;The doctrine holds when a human is in the loop, and falls the moment autonomy takes over. R9 does not ask the sub-agent to be more disciplined. It asks the parent to &lt;strong&gt;materialise&lt;/strong&gt; discipline in the brief, before clicking &lt;em&gt;delegate&lt;/em&gt;. Fifteen seconds inlining &lt;em&gt;check git branch&lt;/em&gt; that afternoon would have spared the cherry-pick. The same rule, twice — once for me, once for the delegate.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Counterpart Toolkit v0.7, R9 amendment. Toolkit public under CC-BY-4.0. The rule lives in &lt;a href="https://github.com/michelfaure/doctrine-counterpart" rel="noopener noreferrer"&gt;&lt;code&gt;doctrine-counterpart/CLAUDE.md&lt;/code&gt;&lt;/a&gt;; the material audit that justified the amendment lives in &lt;code&gt;v0.7-candidates.md&lt;/code&gt; — N=1 structural, promoted on arbitration: the silent-failure mode is measured in severity, not in frequency.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Pourquoi ton DELETE bulk sur Supabase est faux avant même de tourner (drift en 30 min)</title>
      <dc:creator>Michel Faure </dc:creator>
      <pubDate>Thu, 04 Jun 2026 10:12:19 +0000</pubDate>
      <link>https://dev.to/michelfaure/pourquoi-ton-delete-bulk-sur-supabase-est-faux-avant-meme-de-tourner-drift-en-30-min-51k9</link>
      <guid>https://dev.to/michelfaure/pourquoi-ton-delete-bulk-sur-supabase-est-faux-avant-meme-de-tourner-drift-en-30-min-51k9</guid>
      <description>&lt;h2&gt;
  
  
  Le comptage qui ne survit pas à un café
&lt;/h2&gt;

&lt;p&gt;16 mai, console Supabase ouverte sur un lot de lignes orphelines. Catherine m'a remonté trois doublons d'émargement la veille avec sa formule habituelle, &lt;em&gt;« hum, ça bug, mais c'est vite corrigé »&lt;/em&gt;. Une probe SQL plus tard, ce ne sont plus trois cas, c'est la classe entière. Je lance &lt;code&gt;SELECT COUNT(*) FROM seances WHERE cours_id NOT IN (SELECT id FROM cours)&lt;/code&gt; à 10 h 47. La sortie brute dit 351. Je calibre le &lt;code&gt;DELETE&lt;/code&gt;, je relis la clause &lt;code&gt;WHERE&lt;/code&gt;, je vais chercher un café.&lt;/p&gt;

&lt;p&gt;Dix minutes plus tard, la main sur le clavier, un réflexe résiduel me fait relancer le &lt;code&gt;COUNT&lt;/code&gt;. Le chiffre est maintenant 381. Trente lignes apparues entre mes deux probes. Un cron a tourné une fois pendant que je commandais mon café et a ré-injecté ses lignes. Le &lt;code&gt;DELETE&lt;/code&gt; que j'allais lancer aurait emporté un périmètre qui n'était plus celui que j'avais calibré dix minutes plus tôt. Aucune alarme, aucune erreur. Juste un écart de trente lignes que rien ne m'aurait montré si l'habitude ne m'avait pas fait relancer la probe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deux probes, deux fonctions distinctes
&lt;/h2&gt;

&lt;p&gt;Le réflexe naturel est d'appeler &lt;em&gt;comptage&lt;/em&gt; le geste qui retourne un entier. Cette confusion lexicale produit le piège. La première probe scope le travail — &lt;em&gt;« voici l'ordre de grandeur, voici la classe d'incidents »&lt;/em&gt;. Elle informe une décision humaine de cadrage. Une demi-heure peut s'écouler entre cette probe et la décision finale, et c'est sain : un audit qui se précipite se trompe de classe.&lt;/p&gt;

&lt;p&gt;La seconde probe a une fonction structurellement différente. Elle autorise. Elle dit &lt;em&gt;« le périmètre que tu t'apprêtes à toucher est bien celui que tu as calibré »&lt;/em&gt;. Sa durée d'invalidation n'est pas comptée en heures de réflexion humaine mais en cycles de cron. Sur un Postgres en production traversé par des crons toutes les cinq minutes, des webhooks asynchrones, un sync nocturne, deux ou trois assistantes qui saisissent en parallèle, le périmètre a la durée de vie d'un intervalle entre deux exécutions de cron — minutes, pas heures.&lt;/p&gt;

&lt;p&gt;Tout dev le sait intellectuellement. Aucun ne l'opère. Une bonne pratique sans dispositif matériel s'évapore exactement au moment où elle serait utile — j'en ai fait l'expérience trois fois en dix jours, et je ne suis pas un débutant.&lt;/p&gt;

&lt;h2&gt;
  
  
  R7 amendée, et le hook qui ferme la porte
&lt;/h2&gt;

&lt;p&gt;Counterpart Toolkit v0.7, publié le 20 mai, ajoute un paragraphe à R7 :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Pour un bulk DELETE/UPDATE sur un système actif : relancer la requête de comptage immédiatement avant la mutation. Abort si delta &amp;gt; 5 % de la probe initiale. Un comptage de plus de 30 minutes est obsolète sur un système actif. La première probe scope le travail, la seconde est la porte.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Trente minutes est le plancher empirique en-dessous duquel une probe ne croise presque certainement aucun cycle de cron sur notre stack. Cinq pour cent est le seuil au-delà duquel la surface inattendue dépasse le coût d'un re-cadrage.&lt;/p&gt;

&lt;p&gt;R7 vit dans &lt;code&gt;CLAUDE.md&lt;/code&gt;. Lue en début de session, oubliée au moment utile. Le fix est un hook PreToolUse qui scanne tout payload &lt;code&gt;mcp__supabase__execute_sql&lt;/code&gt; pour les patterns &lt;code&gt;DELETE&lt;/code&gt; et &lt;code&gt;UPDATE&lt;/code&gt; non ciblés par clé unique. Pour passer, le SQL doit porter un marqueur explicite :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- count-fresh:20260516-1057&lt;/span&gt;
&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;seances&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;cours_id&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cours&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le hook parse l'horodatage et bloque s'il dépasse 30 minutes. Pas de marqueur, pas d'exécution. La bypass n'est pas une porte dérobée, c'est une déclaration nominative que l'opérateur a relancé la probe. La discipline n'est pas dans la mémoire — elle est dans la friction entre opérateur et mutation.&lt;/p&gt;

&lt;p&gt;Trois fois en dix jours, j'allais détruire un périmètre que je croyais connaître. La quatrième fois, le hook m'a refusé l'exécution — mon marqueur datait de 47 minutes. J'ai relancé la probe : douze lignes avaient bougé. Vingt secondes de coût, un re-do que je n'aurais pas vu venir avant le lendemain matin.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Counterpart Toolkit v0.7, R7 amendée sur N=2 incidents. Hook &lt;code&gt;~/.claude/hooks/pre-bulk-mutation-count-staleness.sh&lt;/code&gt;. CC-BY-4.0 : github.com/michelfaure/doctrine-counterpart&lt;/em&gt;&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>postgres</category>
      <category>claudecode</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why your bulk DELETE on Supabase is wrong before you run it (count drift in 30 min)</title>
      <dc:creator>Michel Faure </dc:creator>
      <pubDate>Thu, 04 Jun 2026 10:12:17 +0000</pubDate>
      <link>https://dev.to/michelfaure/why-your-bulk-delete-on-supabase-is-wrong-before-you-run-it-count-drift-in-30-min-33n8</link>
      <guid>https://dev.to/michelfaure/why-your-bulk-delete-on-supabase-is-wrong-before-you-run-it-count-drift-in-30-min-33n8</guid>
      <description>&lt;h2&gt;
  
  
  The count that doesn't survive a coffee
&lt;/h2&gt;

&lt;p&gt;May 16th, Supabase console open on a batch of orphan rows. Catherine flagged three duplicate attendance entries the day before with her usual line, &lt;em&gt;"hum, it bugs, but it's quickly fixed."&lt;/em&gt; A SQL probe later, it isn't three cases — it's the whole class. I run &lt;code&gt;SELECT COUNT(*) FROM seances WHERE cours_id NOT IN (SELECT id FROM cours)&lt;/code&gt; at 10:47. The raw output says 351. I draft the &lt;code&gt;DELETE&lt;/code&gt;, re-read the &lt;code&gt;WHERE&lt;/code&gt;, walk to the coffee machine.&lt;/p&gt;

&lt;p&gt;Ten minutes later, hand on the keyboard, a residual reflex makes me re-run the &lt;code&gt;COUNT&lt;/code&gt;. It now says 381. Thirty rows appeared between my two probes. A cron job ran once while I was queuing for coffee and re-injected its rows. The &lt;code&gt;DELETE&lt;/code&gt; I was about to launch would have wiped a perimeter that wasn't the one I had scoped ten minutes earlier. No alarm, no error. Just a thirty-row drift nothing would have shown me if habit hadn't made me re-probe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two probes, two distinct functions
&lt;/h2&gt;

&lt;p&gt;The natural reflex is to call &lt;em&gt;count&lt;/em&gt; the act of returning an integer. That lexical confusion is the trap. The first probe scopes the work — &lt;em&gt;"here's the order of magnitude, here's the incident class."&lt;/em&gt; It informs a human framing decision. Half an hour can pass between this probe and the final call, and that's healthy: a rushed audit gets the class wrong.&lt;/p&gt;

&lt;p&gt;The second probe has a structurally different function. It authorizes. It says &lt;em&gt;"the perimeter you're about to touch is the one you scoped."&lt;/em&gt; Its invalidation window isn't measured in human reasoning time but in cron cycles. On a production Postgres with crons running every five minutes, async webhooks, an overnight sync, two or three assistants writing in parallel, the perimeter has the lifespan of an interval between two cron runs — minutes, not hours.&lt;/p&gt;

&lt;p&gt;Every dev knows this intellectually. None of us operate it. A best practice without material enforcement evaporates exactly when it would be useful — I hit the same drift three times in ten days, and I'm not a beginner.&lt;/p&gt;

&lt;h2&gt;
  
  
  R7 amended, and the hook that closes the gap
&lt;/h2&gt;

&lt;p&gt;Counterpart Toolkit v0.7, published May 20th, adds one paragraph to R7:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;For bulk DELETE/UPDATE on a live system: re-run the count query immediately before the mutation. Abort if delta &amp;gt; 5% from the initial probe — counts older than ~30 minutes are stale in active systems. The first probe scopes the work; the second probe is the gate.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thirty minutes is the empirical floor below which a probe almost certainly crosses one cron cycle on our stack. Five percent is the threshold above which the unexpected surface dwarfs the cost of re-scoping.&lt;/p&gt;

&lt;p&gt;R7 lives in &lt;code&gt;CLAUDE.md&lt;/code&gt;. Read at session start, forgotten when it matters. The fix is a PreToolUse hook that scans every &lt;code&gt;mcp__supabase__execute_sql&lt;/code&gt; payload for unkeyed &lt;code&gt;DELETE&lt;/code&gt; and &lt;code&gt;UPDATE&lt;/code&gt; patterns. To pass, the SQL must carry an explicit marker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- count-fresh:20260516-1057&lt;/span&gt;
&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;seances&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;cours_id&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cours&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hook parses the timestamp and blocks if it exceeds 30 minutes. No marker, no execution. The bypass isn't a backdoor; it's a nominative declaration that the operator re-ran the probe. Discipline isn't in memory — it's in the friction between operator and mutation.&lt;/p&gt;

&lt;p&gt;Three times in ten days I almost destroyed a perimeter I thought I knew. The fourth time, the hook refused execution — my marker was 47 minutes old. I re-probed: twelve rows had moved. Twenty seconds of cost, a redo I wouldn't have seen coming until the next morning.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Counterpart Toolkit v0.7, R7 amended on N=2 incidents. Hook &lt;code&gt;~/.claude/hooks/pre-bulk-mutation-count-staleness.sh&lt;/code&gt;. CC-BY-4.0: github.com/michelfaure/doctrine-counterpart&lt;/em&gt;&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>postgres</category>
      <category>claudecode</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Une ligne dans CLAUDE.md qui casse le réflexe over-engineering de Claude</title>
      <dc:creator>Michel Faure </dc:creator>
      <pubDate>Tue, 02 Jun 2026 10:54:56 +0000</pubDate>
      <link>https://dev.to/michelfaure/une-ligne-dans-claudemd-qui-casse-le-reflexe-over-engineering-de-claude-3kp1</link>
      <guid>https://dev.to/michelfaure/une-ligne-dans-claudemd-qui-casse-le-reflexe-over-engineering-de-claude-3kp1</guid>
      <description>&lt;h2&gt;
  
  
  La quatrième fois, c'est trop tard
&lt;/h2&gt;

&lt;p&gt;Dans l'épisode précédent ([CANONICAL URL #52: à compléter après push de over-engineering-3-recadrages]), je raconte trois recadrages dans la même session — &lt;em&gt;plus simple, plus simple, plus simple&lt;/em&gt; — avant que Claude finisse par proposer la version d'une fonction qui tient en huit lignes plutôt que celle avec interface, registry et trois fichiers. Le lendemain matin je rouvre le projet, je demande un correctif mineur dans le même module, et la réponse arrive architecturée exactement comme la veille. Strategy pattern, registry, trois tests d'intégration. Le recadrage n'a pas survécu au &lt;code&gt;/clear&lt;/code&gt;. Il n'avait aucune raison de survivre. Aucun fichier ne le portait, aucun mécanisme ne le déclenchait quand le réflexe revenait.&lt;/p&gt;

&lt;p&gt;La quatrième fois, c'est trop tard. Il faut graver à la troisième.&lt;/p&gt;

&lt;h2&gt;
  
  
  La ligne
&lt;/h2&gt;

&lt;p&gt;J'ai ajouté ceci dans CLAUDE.md, section &lt;em&gt;Conventions&lt;/em&gt;, un bloc au-dessus de la règle Server Components :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Default to the smallest change that fits. Add abstractions only after the second occurrence, never the first.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Quatre choix dans deux phrases. Chacun a coûté plusieurs essais avant de tenir.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pourquoi ces quatre
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Default to&lt;/code&gt;, pas &lt;code&gt;avoid&lt;/code&gt;.&lt;/strong&gt; La première version que j'avais essayée disait &lt;em&gt;Avoid premature abstraction&lt;/em&gt; et n'a strictement rien changé en pratique. Le négatif active l'objet qu'il vise. L'agent lit &lt;em&gt;premature abstraction&lt;/em&gt; et active la catégorie &lt;em&gt;abstraction&lt;/em&gt;, contre laquelle il a un défaut entraîné qui le pousse à en produire. Le positif pose une direction sans nommer le réflexe inverse. C'est le détail le moins évident de l'exercice, et c'est celui qui a fait basculer le test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;That fits&lt;/code&gt;, pas seulement &lt;code&gt;smallest&lt;/code&gt;.&lt;/strong&gt; &lt;em&gt;The smallest change&lt;/em&gt; tout seul pousse au hack. &lt;em&gt;The simplest solution&lt;/em&gt; ouvre un débat sur la simplicité. &lt;em&gt;That fits&lt;/em&gt; précise la mesure : couvrir le besoin, pas l'élégance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deuxième occurrence, pas la troisième.&lt;/strong&gt; La règle des trois de Martin Fowler, dans &lt;em&gt;Refactoring&lt;/em&gt;, dit « the third time you do something similar, refactor ». Je l'ai abaissée à deux pour une raison qui ne vaut probablement que pour le dev solo Claude Code : pas de PR review, donc la deuxième occurrence est le dernier moment où je suis encore lucide sur l'abstraction que je m'apprête à acter. À la troisième, Claude propose spontanément et je signe sans regarder, parce que la pression session m'a déjà usé.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Never the first&lt;/code&gt;.&lt;/strong&gt; Le &lt;em&gt;never&lt;/em&gt; est volontaire et probablement excessif. Mais un défaut formulé avec une exception devient une exception cherchée. &lt;em&gt;Never&lt;/em&gt; coupe la négociation interne. Quand le cas vraiment exceptionnel arrive — et il arrive — je dois argumenter contre la règle explicitement, ce qui est exactement la friction que je veux.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce que j'ai vu jusqu'ici
&lt;/h2&gt;

&lt;p&gt;Trois sessions, trois sujets, trois modules différents. Claude a proposé une abstraction prématurée que j'ai relevée et qu'il a retirée en un échange, contre trois ou quatre fois par session la semaine d'avant. Pas une étude. Un observateur, biais de confirmation possible, échantillon ridicule. Mais la ligne se charge avec CLAUDE.md à chaque démarrage, et la pression baseline pointe maintenant contre le défaut entraîné. Un vrai A/B test sur la sortie Claude Code fera l'objet d'un article ultérieur de la série.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coda
&lt;/h2&gt;

&lt;p&gt;La doctrine en CLAUDE.md, ce n'est pas une liste de bonnes pratiques. C'est l'empilement matériel des leçons qu'on a refusé de répéter une quatrième fois. La règle des trois (Fowler, sur les abstractions) appliquée à la règle des trois (la mienne, sur les corrections orales en session). À la troisième fois où je me retrouve à dire la même chose à Claude, ça part en ligne dans CLAUDE.md. Sinon je la redirai dans la session suivante, et l'enseignement partira avec le contexte.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Suite de [CANONICAL URL #52: à compléter après push de over-engineering-3-recadrages]. A/B test de l'effet CLAUDE.md sur la sortie Claude Code : sujet d'un article ultérieur de la série.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>One line in CLAUDE.md that breaks Claude's over-engineering reflex</title>
      <dc:creator>Michel Faure </dc:creator>
      <pubDate>Tue, 02 Jun 2026 10:54:54 +0000</pubDate>
      <link>https://dev.to/michelfaure/one-line-in-claudemd-that-breaks-claudes-over-engineering-reflex-3fnk</link>
      <guid>https://dev.to/michelfaure/one-line-in-claudemd-that-breaks-claudes-over-engineering-reflex-3fnk</guid>
      <description>&lt;h2&gt;
  
  
  The fourth time is too late
&lt;/h2&gt;

&lt;p&gt;In the previous episode ([CANONICAL URL #52: to complete after push of over-engineering-3-recadrages]), I described three reframings in the same session — &lt;em&gt;no simpler, no simpler, no simpler&lt;/em&gt; — before Claude finally proposed the eight-line version of a function instead of the one with interface, registry and three files. The next morning I reopen the project, ask for a minor fix in the same module, and the answer arrives architected exactly like the day before. Strategy pattern, registry, three integration tests. The reframing did not survive &lt;code&gt;/clear&lt;/code&gt;. It had no reason to. No file carried it, no mechanism triggered when the reflex returned.&lt;/p&gt;

&lt;p&gt;The fourth time is too late. You write the line on the third.&lt;/p&gt;

&lt;h2&gt;
  
  
  The line
&lt;/h2&gt;

&lt;p&gt;I added this to CLAUDE.md, in the &lt;em&gt;Conventions&lt;/em&gt; section, one block above the Server Component rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Default to the smallest change that fits. Add abstractions only after the second occurrence, never the first.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Four choices in two sentences. Each cost several drafts before it held.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why these four
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Default to&lt;/code&gt;, not &lt;code&gt;avoid&lt;/code&gt;.&lt;/strong&gt; The first version I tried said &lt;em&gt;Avoid premature abstraction&lt;/em&gt; and changed nothing in practice. The negative activates the object it names. The agent reads &lt;em&gt;premature abstraction&lt;/em&gt; and surfaces the &lt;em&gt;abstraction&lt;/em&gt; category, against which it has a trained default that pushes it to produce one. The positive sets a direction without naming the reverse reflex. This is the least obvious detail of the exercise and the one that tipped the test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;That fits&lt;/code&gt;, not just &lt;code&gt;smallest&lt;/code&gt;.&lt;/strong&gt; &lt;em&gt;The smallest change&lt;/em&gt; alone pushes toward a hack. &lt;em&gt;The simplest solution&lt;/em&gt; opens a debate on what simple means. &lt;em&gt;That fits&lt;/em&gt; states the measure: covering the need, not elegance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second occurrence, not third.&lt;/strong&gt; Martin Fowler's rule of three from &lt;em&gt;Refactoring&lt;/em&gt; says "the third time you do something similar, refactor". I lowered it to two for a reason that probably only holds for solo Claude Code dev: there is no PR review, so the second occurrence is the last moment where I am still lucid about the abstraction I am about to commit to. By the third, Claude proposes the abstraction spontaneously and I sign without looking, because session pressure has worn me down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Never the first&lt;/code&gt;.&lt;/strong&gt; The &lt;em&gt;never&lt;/em&gt; is deliberate and probably excessive. But a default formulated with an exception becomes an exception searched for. &lt;em&gt;Never&lt;/em&gt; shuts down the internal negotiation. When the genuinely exceptional case arrives — and it does — I must argue against the rule explicitly, which is exactly the friction I want.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I have seen so far
&lt;/h2&gt;

&lt;p&gt;Three sessions, three subjects, three modules. Claude proposed one premature abstraction that I caught and that he retracted in one exchange, against three or four per session the week before. Not a study. One observer, confirmation bias, sample ridiculous. But the line loads with CLAUDE.md on every start, and the baseline pressure now points against the trained default. A proper A/B test is the subject of a later post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coda
&lt;/h2&gt;

&lt;p&gt;The doctrine in CLAUDE.md is not a list of best practices. It is the material accumulation of lessons I refused to repeat a fourth time. The rule of three (Fowler, abstractions) applied to the rule of three (mine, oral reframings in session). The third time I find myself saying the same thing to Claude, it goes into CLAUDE.md as a line. Otherwise I will say it again in the next session, and the lesson will leave with the context.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sequel to [CANONICAL URL #52: to complete after push of over-engineering-3-recadrages]. A/B test of the CLAUDE.md effect on Claude Code output: subject of a later post in this series.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Quick Win Card #02 — Ton compteur de lignes ment après 30 minutes (le hook qui refuse les DELETE bulk périmés)</title>
      <dc:creator>Michel Faure </dc:creator>
      <pubDate>Mon, 01 Jun 2026 12:09:57 +0000</pubDate>
      <link>https://dev.to/michelfaure/quick-win-card-02-ton-compteur-de-lignes-ment-apres-30-minutes-le-hook-qui-refuse-les-delete-440b</link>
      <guid>https://dev.to/michelfaure/quick-win-card-02-ton-compteur-de-lignes-ment-apres-30-minutes-le-hook-qui-refuse-les-delete-440b</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffoznbkz9gbe4y8dfg1uw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffoznbkz9gbe4y8dfg1uw.png" alt="Quick Win Card #02 strip — Michel probe COUNT en matin → 351 orphelines, café, retour, re-probe avant DELETE → 381, Niran « count drift », hook bash qui bloque la commande, bypass nommé count-fresh:20260516-1107." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  L'accroche
&lt;/h2&gt;

&lt;p&gt;Hier dans QW-01, un fichier d'état mentait. Aujourd'hui c'est un compteur. Même classe d'erreur, échelle différente. La première carte coûte du temps ; celle-ci peut coûter trente lignes que tu ne récupéreras pas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce qui a cassé
&lt;/h2&gt;

&lt;p&gt;16 mai matin. Je veux purger des séances orphelines remontées par une sonde quotidienne. Probe SQL standard : &lt;code&gt;SELECT COUNT(*) FROM seances WHERE cours_id NOT IN (SELECT id FROM cours)&lt;/code&gt; retourne &lt;code&gt;351&lt;/code&gt;. Je rédige le &lt;code&gt;DELETE&lt;/code&gt;, je le passe en revue, je vais chercher un café. Dix minutes plus tard, par réflexe, je relance la probe avant d'appuyer sur ENTER. Réponse : &lt;code&gt;381&lt;/code&gt;. Trente lignes apparues pendant la pause, parce qu'un cron de génération de séances a tourné une fois dans l'intervalle.&lt;/p&gt;

&lt;p&gt;Si j'avais lancé le &lt;code&gt;DELETE&lt;/code&gt; confiant sur les &lt;code&gt;351&lt;/code&gt; que j'avais en tête, il aurait emporté un périmètre déjà déphasé. Pas catastrophique sur un orphelines audit — mais sur un &lt;code&gt;DELETE FROM contacts WHERE statut='liste_rouge'&lt;/code&gt; calibré le matin, exécuté l'après-midi, c'est une catégorie entière de données qui disparaît au passage d'un import inattendu. Cette fois-là j'ai eu de la chance. La prochaine fois je n'en aurais pas.&lt;/p&gt;

&lt;p&gt;Le coût n'est pas dans les trente lignes erronées du jour. Il est dans la confiance qu'on accorde à un comptage qu'on a vu il y a vingt minutes, et qu'on traite comme valide alors que le système a continué de respirer sans nous. C'est exactement la même mécanique que QW-01 — l'agent-pilote qui croit son propre résumé. Sauf qu'ici, le résumé est un nombre, et le résumé périmé devient une commande.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le hook qui refuse de DELETE périmé
&lt;/h2&gt;

&lt;p&gt;À poser dans &lt;code&gt;~/.claude/hooks/pre-bulk-mutation-count-staleness.sh&lt;/code&gt;. Bloque toute mutation bulk qui ne porte pas un marqueur explicite &lt;em&gt;« j'ai recompté à l'instant »&lt;/em&gt;. Le bypass est nommé : pas de drift silencieux, l'intention est inscrite dans la commande elle-même.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# Block bulk DELETE/UPDATE without a fresh count marker in the SQL.&lt;/span&gt;
&lt;span class="c"&gt;# Bypass: append `-- count-fresh:YYYYMMDD-HHMM` to your statement.&lt;/span&gt;
&lt;span class="c"&gt;# The hook trusts your honesty on freshness — its job is to break&lt;/span&gt;
&lt;span class="c"&gt;# the autopilot, not to police you. The marker forces you to type&lt;/span&gt;
&lt;span class="c"&gt;# the act of recounting; the discipline of typing it truthfully is on you.&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLAUDE_TOOL_INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;DELETE&lt;span class="se"&gt;\ &lt;/span&gt;FROM&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;UPDATE&lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;SET&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLAUDE_TOOL_INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ count-fresh:&lt;span class="o"&gt;([&lt;/span&gt;0-9]&lt;span class="o"&gt;{&lt;/span&gt;8&lt;span class="o"&gt;}&lt;/span&gt;-[0-9]&lt;span class="o"&gt;{&lt;/span&gt;4&lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"BLOCKED: bulk mutation without count-fresh:YYYYMMDD-HHMM marker"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deux conditions, dix lignes, un message d'erreur qui te dit quoi écrire pour passer. Le hook ne devine pas ton intention — il exige que tu la formules. Il ne te flique pas non plus : si tu mets un timestamp d'hier, il te laisse passer. La doctrine ne remplace pas la discipline, elle la rend visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  ROI
&lt;/h2&gt;

&lt;p&gt;Le hook n'économise pas du temps, il ferme une classe d'incidents. Une mutation bulk qui se trompe sur son périmètre, c'est entre dix minutes (rollback propre depuis un snapshot) et une demi-journée (reconstruction manuelle ligne par ligne avec recoupement de logs) — et ça, c'est dans les cas où on s'en aperçoit. Dans les autres, la prod a discrètement perdu une catégorie entière de données et personne ne sait. Le vrai gain n'est pas chiffrable en minutes. Il est dans la disparition d'un type d'incident qui n'aurait pas dû exister.&lt;/p&gt;

&lt;h2&gt;
  
  
  À appliquer maintenant
&lt;/h2&gt;

&lt;p&gt;Pose le hook dans &lt;code&gt;~/.claude/hooks/&lt;/code&gt;, rends-le exécutable, ajoute-le à ton &lt;code&gt;settings.json&lt;/code&gt; sous &lt;code&gt;hooks.preToolUse&lt;/code&gt;. La prochaine fois que ton agent (ou toi-même) prépare un &lt;code&gt;DELETE&lt;/code&gt; sur un compteur qu'il a vu il y a vingt minutes, le hook répond &lt;em&gt;« BLOCKED »&lt;/em&gt; et te demande de re-probe avant de signer. Ajoute &lt;code&gt;-- count-fresh:20260516-1107&lt;/code&gt; à ton SQL une fois la probe fraîche, et la commande passe. Le contexte d'un agent est, par construction, périmé — ce hook ne te protège pas seulement toi, il protège ton agent contre son propre tampon mental.&lt;/p&gt;

&lt;p&gt;Ton quick win tient en cinq minutes — celles qu'il faut pour copier le hook, le tester sur un faux &lt;code&gt;DELETE&lt;/code&gt;, et le laisser garder ton lit cette nuit.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Quick Win Card series, épisode 02. Counterpart Toolkit v0.7, amendement R7 promu 20/05/2026. Repo doctrine : github.com/michelfaure/doctrine-counterpart. Suite directe de QW-01 — même classe d'erreur, échelle différente.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>supabase</category>
      <category>postgres</category>
      <category>devops</category>
    </item>
    <item>
      <title>Quick Win Card #02 — Your row count lies after 30 minutes (the hook that refuses stale bulk DELETE)</title>
      <dc:creator>Michel Faure </dc:creator>
      <pubDate>Mon, 01 Jun 2026 12:09:55 +0000</pubDate>
      <link>https://dev.to/michelfaure/quick-win-card-02-your-row-count-lies-after-30-minutes-the-hook-that-refuses-stale-bulk-delete-30</link>
      <guid>https://dev.to/michelfaure/quick-win-card-02-your-row-count-lies-after-30-minutes-the-hook-that-refuses-stale-bulk-delete-30</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffoznbkz9gbe4y8dfg1uw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffoznbkz9gbe4y8dfg1uw.png" alt="Quick Win Card #02 strip — Michel probes COUNT in the morning → 351 orphans, coffee, returns, re-probes before DELETE → 381, Niran " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hook
&lt;/h2&gt;

&lt;p&gt;Yesterday in QW-01, a status file lied. Today it's a counter. Same class of error, different scale. The first card costs you time; this one can cost you thirty rows you'll never get back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke
&lt;/h2&gt;

&lt;p&gt;May 16, morning. I want to purge orphan sessions surfaced by a daily probe. Standard SQL probe: &lt;code&gt;SELECT COUNT(*) FROM seances WHERE cours_id NOT IN (SELECT id FROM cours)&lt;/code&gt; returns &lt;code&gt;351&lt;/code&gt;. I draft the &lt;code&gt;DELETE&lt;/code&gt;, I review it, I go get coffee. Ten minutes later, out of habit, I rerun the probe before hitting ENTER. Answer: &lt;code&gt;381&lt;/code&gt;. Thirty rows appeared during the break, because a session-generation cron ran once in the meantime.&lt;/p&gt;

&lt;p&gt;Had I fired the &lt;code&gt;DELETE&lt;/code&gt; confident in the &lt;code&gt;351&lt;/code&gt; I had in my head, it would have swept a perimeter that was already out of phase. Not catastrophic on an orphan audit — but on a &lt;code&gt;DELETE FROM contacts WHERE statut='liste_rouge'&lt;/code&gt; calibrated in the morning and executed in the afternoon, an entire data category vanishes when an unexpected import slipped in. That day I got lucky. Next time I won't.&lt;/p&gt;

&lt;p&gt;The cost isn't in the thirty wrong rows of the day. It's in the trust you grant to a count you saw twenty minutes ago, treating it as valid while the system kept breathing without you. It's exactly the same mechanic as QW-01 — the agent-pilot believing their own summary. Except here the summary is a number, and the stale summary becomes a command.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hook that refuses stale DELETE
&lt;/h2&gt;

&lt;p&gt;Drop in &lt;code&gt;~/.claude/hooks/pre-bulk-mutation-count-staleness.sh&lt;/code&gt;. Blocks any bulk mutation that doesn't carry an explicit &lt;em&gt;"I just recounted"&lt;/em&gt; marker. The bypass is named: no silent drift, intent is inscribed in the command itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# Block bulk DELETE/UPDATE without a fresh count marker in the SQL.&lt;/span&gt;
&lt;span class="c"&gt;# Bypass: append `-- count-fresh:YYYYMMDD-HHMM` to your statement.&lt;/span&gt;
&lt;span class="c"&gt;# The hook trusts your honesty on freshness — its job is to break&lt;/span&gt;
&lt;span class="c"&gt;# the autopilot, not to police you. The marker forces you to type&lt;/span&gt;
&lt;span class="c"&gt;# the act of recounting; the discipline of typing it truthfully is on you.&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLAUDE_TOOL_INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;DELETE&lt;span class="se"&gt;\ &lt;/span&gt;FROM&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;UPDATE&lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;SET&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLAUDE_TOOL_INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ count-fresh:&lt;span class="o"&gt;([&lt;/span&gt;0-9]&lt;span class="o"&gt;{&lt;/span&gt;8&lt;span class="o"&gt;}&lt;/span&gt;-[0-9]&lt;span class="o"&gt;{&lt;/span&gt;4&lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"BLOCKED: bulk mutation without count-fresh:YYYYMMDD-HHMM marker"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two conditions, ten lines, an error message that tells you exactly what to write to pass. The hook doesn't guess your intent — it requires you to spell it out. It doesn't police you either: paste yesterday's timestamp and you go through. The doctrine doesn't replace the discipline, it makes it visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  ROI
&lt;/h2&gt;

&lt;p&gt;The hook doesn't save time, it closes an incident class. A bulk mutation that misjudges its perimeter costs anywhere from ten minutes (clean rollback from a snapshot) to half a day (manual line-by-line reconstruction with log cross-checking) — and that's the cases where you notice. In the others, production has quietly lost an entire data category and nobody knows. The real win isn't measurable in minutes. It's the disappearance of a kind of incident that should never have existed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply now
&lt;/h2&gt;

&lt;p&gt;Drop the hook in &lt;code&gt;~/.claude/hooks/&lt;/code&gt;, make it executable, register it in your &lt;code&gt;settings.json&lt;/code&gt; under &lt;code&gt;hooks.preToolUse&lt;/code&gt;. Next time your agent (or yourself) prepares a &lt;code&gt;DELETE&lt;/code&gt; against a count seen twenty minutes ago, the hook replies &lt;em&gt;"BLOCKED"&lt;/em&gt; and asks you to re-probe before signing. Append &lt;code&gt;-- count-fresh:20260516-1107&lt;/code&gt; to your SQL once the probe is fresh, and the command goes through. An agent's context is, by construction, stale — this hook doesn't only protect you, it protects your agent against its own mental cache.&lt;/p&gt;

&lt;p&gt;Your quick win takes five minutes — the time to copy the hook, test it against a fake &lt;code&gt;DELETE&lt;/code&gt;, and let it guard your sleep tonight.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Quick Win Card series, episode 02. Counterpart Toolkit v0.7, amendment R7 promoted 20/05/2026. Doctrine repo: github.com/michelfaure/doctrine-counterpart. Direct sequel to QW-01 — same class of error, different scale.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>supabase</category>
      <category>postgres</category>
      <category>devops</category>
    </item>
    <item>
      <title>Une journée avec `/challenger` : 4 bugs, 4 hypothèses réfutées avant le fix</title>
      <dc:creator>Michel Faure </dc:creator>
      <pubDate>Fri, 29 May 2026 01:16:02 +0000</pubDate>
      <link>https://dev.to/michelfaure/une-journee-avec-challenger-4-bugs-4-hypotheses-refutees-avant-le-fix-1ikh</link>
      <guid>https://dev.to/michelfaure/une-journee-avec-challenger-4-bugs-4-hypotheses-refutees-avant-le-fix-1ikh</guid>
      <description>&lt;h2&gt;
  
  
  Le matin où j'ai cru que c'était une seule journée
&lt;/h2&gt;

&lt;p&gt;Samedi 16 mai, huit heures dix. Café tiède dans la tasse à tête de Françoise, héritée d'un anniversaire de bureau. Le tableau Sentry de la nuit affiche un point rouge isolé sur un cron à trois heures, et un message vocal de Catherine arrivé à sept heures cinquante — &lt;em&gt;« Hum, ça bug. Mais c'est vite corrigé. »&lt;/em&gt; Sept mots dans son ton habituel, ni urgent ni inquiet, l'évidence d'une journée qui commence par un ticket à fermer avant les autres.&lt;/p&gt;

&lt;p&gt;Catherine n'a presque jamais tort sur la première moitié de sa phrase, et elle a parfois tort sur la seconde. Le bug du jour, ce n'est pas un bug, ce sont quatre, et entre celui de huit heures et celui de dix-huit heures se loge une journée entière qui ressemble à une seule chose vue de loin, et à quatre choses très différentes vue de près. Nous avons tous tendance à compter les incidents par tickets, par messages, par sessions, ce qui revient à compter par symptômes. Les compter par hypothèses falsifiables, c'est en trouver quatre là où nous croyions n'en avoir qu'un.&lt;/p&gt;

&lt;p&gt;L'autopsie qui suit n'est pas un cas d'école. C'est une journée ordinaire que j'aurais préféré finir à dix-neuf heures et que j'ai finie à vingt-trois, parce que sur le quatrième bug j'ai sauté le protocole que les trois premiers m'avaient pourtant réappris à respecter le matin même.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug numéro un, les signatures qui se dédoublent
&lt;/h2&gt;

&lt;p&gt;Catherine remonte qu'un cours de l'antenne affiche, dans le PDF d'émargement, chaque élève signé deux fois. Le réflexe productif arrive avant la réflexion : &lt;em&gt;« le formulaire double-poste sur double-clic, on ajoute une unique-constraint et on passe »&lt;/em&gt;. Quinze lignes de migration et c'est plié. Sauf que c'est faux.&lt;/p&gt;

&lt;p&gt;Le skill &lt;code&gt;/challenger&lt;/code&gt; impose un autre départ. Hypothèse en une phrase : &lt;em&gt;« la jointure &lt;code&gt;emargements ↔ seances&lt;/code&gt; ramène N×M lignes quand un cours a plusieurs séances générées pour la même date. »&lt;/em&gt; Une cause structurelle, pas un symptôme. Trois sondes pour réfuter, pas pour confirmer :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Sonde 1 — combien de séances pour ce cours, cette date ?&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;cours_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_seance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;n_seances&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;seances&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;date_seance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-16'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cours_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_seance&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- 4 cours touchés, 18 séances dédoublées (filtre année scolaire mal câblé)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La sortie réfute l'hypothèse initiale du double-clic et confirme un drift plus profond. Dix-huit séances apparaissent en double parce que le générateur de séances a filé deux passes avec un filtre &lt;code&gt;getAnneeScolaire(date)&lt;/code&gt; mal câblé entre 2025-2026 et 2026-2027 sur les cours pivotant à la rentrée. Le fix qu'on aurait posé sur le formulaire n'aurait pas effacé une seule séance fantôme, il aurait juste empêché les &lt;em&gt;futures&lt;/em&gt; doubles signatures, en laissant le PDF du jour gâté.&lt;/p&gt;

&lt;p&gt;La sonde a coûté quatre-vingt-dix secondes. Le rollback qu'elle a évité, je l'estime à une bonne demi-heure de cycle commit-deploy-Sentry-Catherine-rappelle. Premier ratio de la journée, six pour un.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug numéro deux, les notes du formateur qui s'évaporent
&lt;/h2&gt;

&lt;p&gt;Onze heures. Une formatrice signale, calmement, que les notes pédagogiques qu'elle saisit à la fin d'un émargement ne réapparaissent pas quand un autre formateur la remplace la séance suivante. Hypothèse de surface, séduisante : &lt;em&gt;« le formulaire ne précharge pas les notes du précédent émargement »&lt;/em&gt;. Une ligne de &lt;code&gt;useEffect&lt;/code&gt; aurait l'air d'un fix correct.&lt;/p&gt;

&lt;p&gt;Hypothèse &lt;code&gt;/challenger&lt;/code&gt; reformulée : &lt;em&gt;« le champ &lt;code&gt;notes_formateur&lt;/code&gt; est lié à l'identifiant utilisateur signataire, pas au cours ou à la séance, donc un remplacement par un autre signataire masque légitimement les notes saisies par le titulaire. »&lt;/em&gt; Trois sondes : (a) lecture du schema, (b) lecture du composant, (c) &lt;strong&gt;question à la formatrice elle-même&lt;/strong&gt;, parce qu'aucune sonde technique ne peut trancher si c'est un bug ou un choix métier.&lt;/p&gt;

&lt;p&gt;C'est ici que &lt;code&gt;/challenger&lt;/code&gt; cède la place à un autre skill, &lt;code&gt;ask-3-options-before-code&lt;/code&gt;, promu source dans la doctrine v0.7. Avant d'écrire la moindre ligne, formuler trois options à arbitrer hors technique, que les notes soient par séance et visibles à tous les signataires successifs, qu'elles soient privées au signataire et jamais partagées, ou qu'elles soient partagées au sein de l'équipe pédagogique d'un cours sans être exposées aux remplaçants ponctuels. La formatrice tranche pour la deuxième. Il n'y a pas de bug, il y a un choix de design qui mérite une décision écrite plutôt qu'un fix patché à l'oreille. ADR mini, zéro ligne de code, deux minutes de discussion.&lt;/p&gt;

&lt;p&gt;Certes, un dev solo pressé aurait fait l'option 1 par défaut et déployé. Mais ce qui paraissait gagné en vitesse aurait coûté trois retours utilisateur la semaine d'après, la formatrice ayant ses notes lues par tout l'atelier, ce qu'elle ne voulait pas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug numéro trois, le trait noir fantôme
&lt;/h2&gt;

&lt;p&gt;Quatorze heures. Le planning éditeur affiche un fin trait noir vertical sur une seule colonne, traversant la grille du haut en bas. CSS qui fuit, voilà ce que dit l'œil. Hypothèse &lt;code&gt;/challenger&lt;/code&gt; formulée : &lt;em&gt;« un événement orphelin remonte une ligne vide qui se rend en bordure épaisse, parce qu'une suppression de cours n'a pas cascade-supprimé ses séances. »&lt;/em&gt; Trois sondes, l'inspect DOM, une requête de cohérence sur &lt;code&gt;seances&lt;/code&gt; orphelines, et, la sonde qui réfute en dernier ressort par discipline acquise, &lt;code&gt;git blame&lt;/code&gt; sur le composant &lt;code&gt;&amp;lt;PlanningGrid&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;C'est la troisième sonde qui tranche. Un commit récent a ajouté un &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; conditionnel pour séparer visuellement les ateliers dans la grille, en branchant sur une variable &lt;code&gt;isLastOfAtelier&lt;/code&gt; mal calculée en bordure de tableau. Ce n'est pas un fantôme de la base de données, c'est une bordure d'interface. Une ligne TypeScript, pas de migration, deux minutes de fix. Aurais-je commencé par chercher en base, j'aurais perdu une heure à inspecter des cours et des séances qui n'avaient rien à se reprocher.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug numéro quatre, celui où j'ai sauté le protocole
&lt;/h2&gt;

&lt;p&gt;Dix-huit heures. La fatigue est là, et avec elle ce qu'il faut bien appeler une certitude fragile. Une feuille d'émargement papier de secours imprime &lt;em&gt;« jeudi 14h »&lt;/em&gt; alors que le cours est &lt;em&gt;« mercredi 14h »&lt;/em&gt;. &lt;em&gt;« Le PDF imprime le jour de la séance, pas le jour du cours »&lt;/em&gt;, je me dis, et je commit sans sonde.&lt;/p&gt;

&lt;p&gt;Vingt-cinq minutes plus tard, rollback. Désormais toutes les feuilles disent &lt;em&gt;« mercredi »&lt;/em&gt;, y compris les cours du jeudi. Le compteur Sentry remonte trois alertes en cinq minutes. Catherine, elle, n'a pas rappelé, elle a écrit &lt;em&gt;« hum, ça bug »&lt;/em&gt; et m'a laissé seul avec la fatigue.&lt;/p&gt;

&lt;p&gt;La cause réelle, qu'une sonde de quatre-vingt-dix secondes aurait remontée, c'est que &lt;code&gt;creneau_label&lt;/code&gt; est stocké en Cache LSC sans rafraîchisseur, divergent depuis trois semaines pour les cours dont le &lt;code&gt;jour_semaine&lt;/code&gt; a été corrigé sans propager le label vers la colonne dérivée. Pas un bug PDF, un bug de catégorisation Live/Snapshot/Cache déjà documenté dans la doctrine, que l'audit ADR-0024 avait flagué un mois plus tôt comme classe d'incident structurelle.&lt;/p&gt;

&lt;p&gt;Un agent qui ne nous contredit pas n'est pas un counterpart, c'est une dactylo plus rapide ; un skill qu'on saute le soir n'est plus un skill, c'est une rigueur abandonnée. Le coût n'est pas dans l'oubli ponctuel, il est dans ce que l'oubli révèle, à savoir que le protocole tient à la discipline humaine, pas à un mécanisme matériel encore assez bloquant pour intercepter la fatigue de fin de journée.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trois fix tenus, un rollback, le ratio qui fait tout
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.claude/skills/challenger/SKILL.md (extrait)&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;challenger&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Force la formulation d'une hypothèse en 1 phrase&lt;/span&gt;
  &lt;span class="s"&gt;puis l'exécution de 3 sondes matérielles destinées à RÉFUTER&lt;/span&gt;
  &lt;span class="s"&gt;cette hypothèse avant tout code de fix.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sur la journée, le compte tient en quatre lignes. Bug numéro un, sonde tenue, fix juste, demi-heure gagnée. Bug numéro deux, protocole détourné vers la question métier, deux minutes au lieu d'une régression utilisateur. Bug numéro trois, sonde tenue, fix d'une ligne, une heure gagnée. Bug numéro quatre, protocole sauté, vingt-cinq minutes de rollback, alerte Sentry, et la note dans le journal de bord que le quatrième bug d'une journée n'a pas le droit d'être traité comme un fix isolé. Trois fix tenus pour un rollback, le ratio est plus parlant que la moyenne. Le skill ne nous évite pas l'erreur, il nous évite la troisième heure de la même erreur.&lt;/p&gt;

&lt;p&gt;Une métaphore d'atelier ferait peut-être passer la chose : on ne cuit pas une pièce avant d'avoir vérifié la couche d'engobe. La cuisson, c'est le commit. L'engobe, c'est la sonde. Sauter l'engobe parce qu'on est pressé, c'est ce qui fait éclater la pièce dans le four.&lt;/p&gt;

&lt;p&gt;Cinq à dix minutes de sonde évitent vingt à trente minutes de cycle fix-puis-rollback. Le calcul est trivial sur une journée, il devient écrasant sur soixante. Si une seule des quatre sondes vous évite, demain, le rollback que j'ai eu hier soir, le protocole s'est déjà remboursé.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Skill &lt;code&gt;/challenger&lt;/code&gt;, Counterpart Toolkit v0.7 R4 Falsify before fix. Repo public github.com/michelfaure/doctrine-counterpart. Article #54 de la série My ERP with Claude Code, autopsie d'une journée de quatre incidents enchaînés.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>debugging</category>
    </item>
    <item>
      <title>A day in the life of `/challenger`: 4 bugs, 4 hypotheses falsified before the fix</title>
      <dc:creator>Michel Faure </dc:creator>
      <pubDate>Fri, 29 May 2026 01:15:24 +0000</pubDate>
      <link>https://dev.to/michelfaure/a-day-in-the-life-of-challenger-4-bugs-4-hypotheses-falsified-before-the-fix-2pp</link>
      <guid>https://dev.to/michelfaure/a-day-in-the-life-of-challenger-4-bugs-4-hypotheses-falsified-before-the-fix-2pp</guid>
      <description>&lt;h2&gt;
  
  
  The morning I thought it was one single day
&lt;/h2&gt;

&lt;p&gt;Saturday May 16, eight ten in the morning. Lukewarm coffee in Françoise's mug — inherited from an office birthday. The overnight Sentry board shows an isolated red dot on a three AM cron, and a voicemail from Catherine that arrived at seven fifty — &lt;em&gt;"Hum, it bugs. But it's quickly fixed."&lt;/em&gt; Seven words in her usual tone, neither urgent nor worried, the obviousness of a day that starts with a ticket to close before the others.&lt;/p&gt;

&lt;p&gt;Catherine is almost never wrong on the first half of her sentence, and she's sometimes wrong on the second. Today's bug isn't a bug, it's four, and between the eight-AM one and the six-PM one fits a whole day that looks like one thing from afar and four very different things up close. We all tend to count incidents by tickets, by messages, by sessions, which amounts to counting by symptoms. Counting them by falsifiable hypotheses means finding four where we thought we only had one.&lt;/p&gt;

&lt;p&gt;The autopsy that follows isn't a textbook case. It's an ordinary day I'd have preferred to end at seven PM and that I ended at eleven, because on the fourth bug I skipped the protocol that the first three had taught me to respect that very morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug number one, signatures that duplicate
&lt;/h2&gt;

&lt;p&gt;Catherine reports that one of the branch's courses shows, in the attendance PDF, each student signed twice. The productive reflex arrives before reflection: &lt;em&gt;"the form double-posts on double-click, we add a unique-constraint and move on"&lt;/em&gt;. Fifteen lines of migration and done. Except it's wrong.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/challenger&lt;/code&gt; skill imposes a different start. Hypothesis in one sentence: &lt;em&gt;"the &lt;code&gt;emargements ↔ seances&lt;/code&gt; join returns N×M rows when a course has multiple sessions generated for the same date."&lt;/em&gt; A structural cause, not a symptom. Three probes to refute, not to confirm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Probe 1 — how many sessions for this course, this date?&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;cours_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_seance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;n_seances&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;seances&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;date_seance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-05-16'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cours_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_seance&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- 4 courses affected, 18 doubled sessions (school year filter mis-wired)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output refutes the initial double-click hypothesis and confirms a deeper drift. Eighteen sessions appear in double because the session generator ran two passes with a &lt;code&gt;getAnneeScolaire(date)&lt;/code&gt; filter mis-wired between 2025-2026 and 2026-2027 on the courses pivoting at the start of the new school year. The fix we would have placed on the form would not have erased a single phantom session, it would have just prevented &lt;em&gt;future&lt;/em&gt; double signatures, leaving today's PDF spoiled.&lt;/p&gt;

&lt;p&gt;The probe cost ninety seconds. The rollback it avoided, I estimate at a good half hour of commit-deploy-Sentry-Catherine-calls-back cycle. First ratio of the day, six to one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug number two, the trainer's notes that vanish
&lt;/h2&gt;

&lt;p&gt;Eleven AM. A trainer reports, calmly, that the pedagogical notes she enters at the end of an attendance form don't reappear when another trainer replaces her the following session. Surface hypothesis, seductive: &lt;em&gt;"the form doesn't preload the previous attendance's notes"&lt;/em&gt;. A line of &lt;code&gt;useEffect&lt;/code&gt; would look like a correct fix.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/challenger&lt;/code&gt; hypothesis reformulated: &lt;em&gt;"the &lt;code&gt;notes_formateur&lt;/code&gt; field is bound to the signing user's identifier, not to the course or session, so a replacement by another signatory legitimately masks the notes entered by the regular trainer."&lt;/em&gt; Three probes: (a) read the schema, (b) read the component, (c) &lt;strong&gt;ask the trainer herself&lt;/strong&gt;, because no technical probe can decide whether it's a bug or a business choice.&lt;/p&gt;

&lt;p&gt;This is where &lt;code&gt;/challenger&lt;/code&gt; cedes to another skill, &lt;code&gt;ask-3-options-before-code&lt;/code&gt;, promoted to source in doctrine v0.7. Before writing a single line, formulate three options to arbitrate outside the technical realm: that notes are per-session and visible to all successive signatories, that they are private to the signatory and never shared, or that they are shared within a course's pedagogical team without being exposed to occasional replacements. The trainer chooses the second. There's no bug, there's a design choice that deserves a written decision rather than a fix patched by ear. Mini ADR, zero lines of code, two minutes of discussion.&lt;/p&gt;

&lt;p&gt;Granted, a rushed solo dev would have done option 1 by default and deployed. But what seemed gained in speed would have cost three user complaints the following week, with the trainer's notes read by the entire branch, which she didn't want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug number three, the phantom black line
&lt;/h2&gt;

&lt;p&gt;Two PM. The planning editor displays a thin vertical black line on a single column, crossing the grid from top to bottom. CSS leak, says the eye. &lt;code&gt;/challenger&lt;/code&gt; hypothesis formulated: &lt;em&gt;"an orphan event surfaces an empty row that renders as a thick border, because a course deletion didn't cascade-delete its sessions."&lt;/em&gt; Three probes: DOM inspect, a consistency query on orphan &lt;code&gt;seances&lt;/code&gt;, and — the probe that refutes by acquired discipline — &lt;code&gt;git blame&lt;/code&gt; on the &lt;code&gt;&amp;lt;PlanningGrid&amp;gt;&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;It's the third probe that decides. A recent commit added a conditional &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; to visually separate branches in the grid, branching on an &lt;code&gt;isLastOfAtelier&lt;/code&gt; variable miscalculated at the table edge. It's not a database ghost, it's an interface border. One TypeScript line, no migration, two minutes of fix. Had I started by searching the database, I'd have lost an hour inspecting courses and sessions that had nothing to reproach themselves with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug number four, the one where I skipped the protocol
&lt;/h2&gt;

&lt;p&gt;Six PM. Fatigue is here, and with it what must be called fragile certainty. An emergency paper attendance sheet prints &lt;em&gt;"Thursday 2 PM"&lt;/em&gt; whereas the course is &lt;em&gt;"Wednesday 2 PM"&lt;/em&gt;. &lt;em&gt;"The PDF prints the session date, not the course date"&lt;/em&gt;, I tell myself, and I commit without a probe.&lt;/p&gt;

&lt;p&gt;Twenty-five minutes later, rollback. Now all sheets say &lt;em&gt;"Wednesday"&lt;/em&gt;, including Thursday courses. The Sentry counter raises three alerts in five minutes. Catherine, on her end, didn't call back, she wrote &lt;em&gt;"hum, it bugs"&lt;/em&gt; and left me alone with the fatigue.&lt;/p&gt;

&lt;p&gt;The real cause, that a ninety-second probe would have surfaced, is that &lt;code&gt;creneau_label&lt;/code&gt; is stored as LSC Cache without a refresher, diverging for three weeks for courses whose &lt;code&gt;jour_semaine&lt;/code&gt; was corrected without propagating the label to the derived column. Not a PDF bug, a Live/Snapshot/Cache categorisation bug already documented in the doctrine, that the ADR-0024 audit had flagged a month earlier as a structural incident class.&lt;/p&gt;

&lt;p&gt;An agent that doesn't contradict us isn't a counterpart, it's a faster typist; a skill we skip in the evening is no longer a skill, it's an abandoned rigour. The cost isn't in the occasional skip, it's in what the skip reveals — that the protocol holds on human discipline, not on a material mechanism still blocking enough to intercept end-of-day fatigue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three fixes held, one rollback, the ratio that matters
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.claude/skills/challenger/SKILL.md (excerpt)&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;challenger&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Force the formulation of a hypothesis in 1 sentence&lt;/span&gt;
  &lt;span class="s"&gt;then the execution of 3 material probes designed to REFUTE&lt;/span&gt;
  &lt;span class="s"&gt;this hypothesis before any fix code.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Over the day, the count holds in four lines. Bug one, probe held, fix correct, half hour saved. Bug two, protocol diverted toward the business question, two minutes instead of a user regression. Bug three, probe held, one-line fix, an hour saved. Bug four, protocol skipped, twenty-five minutes of rollback, Sentry alert, and the note in the journal that the fourth bug of a day doesn't have the right to be treated as an isolated fix. Three fixes held for one rollback, the ratio speaks louder than the average. The skill doesn't save us from the error, it saves us from the third hour of the same error.&lt;/p&gt;

&lt;p&gt;A workshop metaphor might convey it: you don't fire a piece before checking the slip layer. The firing is the commit. The slip is the probe. Skipping the slip because you're in a hurry is what makes the piece crack in the kiln.&lt;/p&gt;

&lt;p&gt;Five to ten minutes of probe avoid twenty to thirty minutes of fix-then-rollback cycle. The math is trivial on a day, it becomes crushing over sixty. If a single one of the four probes saves you, tomorrow, the rollback I had yesterday evening, the protocol has already paid for itself.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Skill &lt;code&gt;/challenger&lt;/code&gt;, Counterpart Toolkit v0.7 R4 Falsify before fix. Public repo github.com/michelfaure/doctrine-counterpart. Article #54 of the series My ERP with Claude Code, autopsy of one day of four chained incidents.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
