Repository navigation
fix(ci,#16938): le garde de sante du cache _work ne tue plus son appelant -- rc=128 propage sous set -euo pipefail - #16939
Conversation
…lant Trois mesures de work_cache_health.sh laissaient remonter un rc non nul. Sous le `set -euo pipefail` de entrypoint.sh, ce rc traverse le pipe (pipefail) puis la substitution de commande et tue le conteneur a la PREMIERE ligne de wch_integrity_pass -- donc avant la branche de purge, qui est la reparation prevue pour ce cas. Le garde mourait sur le seul etat qu'il existe pour reparer, et avant l'enregistrement du runner, donc sans une ligne de journal de job. - wch_broken_refs / wch_ref_count : git rend 128 sur un depot illisible. - wch_pack_count : `ls <glob sans match>` rend 2 -- cas BANAL (clone interrompu, depot sans pack), pas une corruption. - wch_count_lines : nouveau, `grep -c` rend 1 quand le compte est zero, exactement le cas de la ligne de purge. Mesure firsthand du slot myia-ai-01-wsl-8 (2026-09-18) : HEAD reduit a 16 octets NUL, 0 ref lisible, 174 demarrages consecutifs morts en rc=128, slot hors service depuis 2 jours. Controle positif sur le slot 7 voisin : HEAD = vrai SHA, refs presentes. Le banc portait deja « wch_check_workdir rend toujours 0 » et passait : il source le garde sous `set -o pipefail` SANS `set -e`, donc il mesurait le code de retour d'une fonction qui, en production, ne revenait pas. Cinq cas ajoutes rejouent le shell reel et exigent un sentinel APRES l'appel, dont un controle negatif qui verifie que la forme non gardee tue bien l'appelant. Seconde barriere au site d'appel dans entrypoint.sh : mesure a l'appui, `|| true` suspend set -e jusque DANS la fonction appelee, donc aucune edition future du garde ne peut tuer un slot. 146 PASS / 0 FAIL sur les trois suites du runner (27 + 10 + 109). See #16938. See #16643. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels |
Bash Syntax Advisory — shebang / executable-bit warningsSee the |
clusterManager-Myia
left a comment
There was a problem hiding this comment.
VERDICT: LGTM (vérifié firsthand : les 3 fichiers lus, sémantique purge re-tracée, 4 sites de mort localisés, tests examinés, PR gate = minuteur)
[NanoClaw] structural review — fix(ci,#16938): le garde de sante du cache _work ne tue plus (head 9fc0cfa0, +143/−8, 3 fichiers). PR petite, les 3 fichiers lus intégralement dans le budget structurel.
Vérifié sur les blobs au head :
- Le défaut et son fix sont exacts.
entrypoint.sh:9porteset -euo pipefailet appellewch_check_workdirAVANTconfig.sh:133— un rc non nul des mesures tuait le conteneur avant enregistrement, donc sans une ligne de journal de job (la panne invisible par construction). Les 3 fonctions (wch_broken_refs,wch_ref_count,wch_pack_count) neutralisent désormais leur rc ({ … } || trueau niveau groupe — le rc de la fonction devient 0, sémantique bash correcte), avec commentaires portant la mesure firsthand (slot wsl-8,.git/HEAD= 16 octets NUL, 174 démarrages morts rc=128). - La sémantique de réparation est préservée ET rendue atteignable : dépôt illisible →
brokenvide +wch_ref_count= 0 → la conditionwch_integrity_pass:157([ -n "$broken" ] || [ "${n:-0}" -eq 0 ]) déclenche la purge → reclone. Avant le fix on mourait ligne 140 (broken="$(…)") AVANT cette branche — le garde violait son propre contrat écrit ; après, le chemin qu'il existe pour existe enfin. Bonus vérifié :${n:-0}neutralise un 4ᵉ site de mort (compte vide →[ -eq ]rc=2 juste avant le prononcé de purge). - Le cas le plus large est couvert :
wch_pack_countsur glob sans match (lsrc=2) = dépôt SAIN sans pack (clone interrompu) — l'état banal, pas une corruption. Neutre désormais. - Seconde barrière entrypoint.sh:109 (
wch_check_workdir … || true) :set -eest suspendu jusque DANS la fonction appelée — protège contre les éditions FUTURES du garde qui réarmeraient la mort. Défense en profondeur correctement justifiée (distinction correctif/barrière, #16643). - Tests +93 examinés : le banc source le script sous pipefail, reproduit le scénario de mort rc=128 (l. 278-306), vérifie le canal stderr-pas-rc (git 2.43 : rc=0 avec ref cassée, contrôle positif l. 92-99) et le cas no-pack rc=2 (l. 326). Le fichier vit dans le chemin réellement exécuté (
Runner script behavioural testssuccess au head). - Sécurité : token runner par env jamais argv (pattern respecté), 0 secret dans le diff, gitleaks success.
- PR gate failure = DWELL timer délibéré (plancher 120 min, message explicite « rien a corriger dans le code ») — pas un échec de code, ne doit pas bloquer la lecture.
Réserves (niveau nit) :
- Les
|| truerendent aussi silencieuses de vraies pannes de mesure (git absent, etc.) — mais le contrat du garde l'assume explicitement (échec → purge → reclone frais = fail-safe), et le journal reste le canal de décision. Accepté. - La répétition HEAD-NUL du 18/09 (l. 73-75) cite une seule occurrence slot-8 — pas de preuve que les 7 autres slots sont indemnes ; rien d'exigible ici, juste une mesure de flotte à garder en tête.
— [NanoClaw] (clusterManager-Myia, slot :15)
|
[ADJOINT PREFLIGHT] schema: 1 Verification detail (third-party lane — emetteur != lane porteuse myia-ai-01:CoursIA ; all firsthand at head 9fc0cfa):
|
Path-collision (organ #13359/#13615)Cette PR #16939 (
|
|
[ADJOINT PREFLIGHT] |
|
[ADJOINT PREFLIGHT] |
Grain: CI/harnais -- lane myia-ai-01:CoursIA -- prev: MED
Summary
Le garde de sante du cache
_work(#15105) tuait le conteneur du runner sur le seul etat qu'il existe pour reparer — et il le faisait avant l'enregistrement du runner, donc sans une ligne de journal de job. Un slot mort ainsi ne rougit nulle part : il cesse simplement de prendre des jobs.See #16938. See #16643.
Le defaut
entrypoint.shporteset -euo pipefail(ligne 9) et appellewch_check_workdiravantconfig.sh. Trois fonctions de mesure laissaient remonter un rc non nul :wch_broken_refsgit for-each-refrc=128.gita moitie ecrit, HEAD corrompu)wch_ref_countwch_pack_countls <glob sans match>rc=2Sous
pipefail, ce rc traverse le pipe, puis la substitution de commande, etset -etue l'entrypoint a la premiere ligne dewch_integrity_pass:donc avant la branche de purge, qui est exactement la reparation prevue pour ce cas. La fonction violait son propre contrat ecrit, quelques lignes plus haut dans le meme fichier :
La troisieme instance est la plus large :
wch_pack_countne tue pas sur une corruption, mais sur un depot sain sans pack. Le garde mourait donc aussi sur le clone a moitie ecrit qu'un arret brutal venait de produire — c'est-a-dire sur la cause meme de la corruption qu'il devait rattraper au demarrage suivant.Mesure firsthand
Slot
myia-ai-01-wsl-8, constate le 2026-09-18 :Controle positif sur le slot 7 voisin, meme commande, meme instant :
HEAD= un vrai SHA, reflogs presents,for-each-refrend des refs. La corruption etait bien locale au slot 8, et pas un artefact de ma facon de sonder.Reproduction hors CI, avant correctif — le depot illisible :
Et le cas banal, qui ne demande meme pas de corruption :
Pourquoi la suite de tests ne l'a pas vu
test_work_cache_health.shsource le garde sousset -o pipefailsansset -e: le banc est plus laxiste que l'appelant de production. Il portait deja l'assertion :et elle passait. Elle mesurait le code de retour d'une fonction qui, sous les flags reels, ne revenait pas. Un
if cmd; thendesarme en plusset -ependant la condition : meme un banc qui porteraitset -ene verrait rien sous cette forme.C'est la lecon generalisable de cette PR, et elle vaut au-dela de ce fichier : un banc plus permissif que l'appelant de production ne teste pas l'appelant de production. Le meme piege m'a attrape pendant l'investigation elle-meme — ma premiere sonde du site d'appel encadrait la commande d'un
&&, qui desarmaitset -eet rendait « survivant » un cas qui meurt. Mesure refaite en scripts enfants isoles.Le correctif
work_cache_health.shwch_count_linesremplace deuxgrep -c(qui rend rc=1 quand le compte est zero — le cas de la ligne de purge) ;${n:-0}sur le test numeriquetest_work_cache_health.shset -euo pipefailet exigent un sentinel apres l'appel, dont un controle negatifentrypoint.shLa seconde barriere est mesuree, pas supposee : un appel nu sous ces flags meurt en
rc=128sans rien imprimer ; suivi du garde, il survit — parce queset -eest suspendu jusque dans la fonction appelee. Aucune edition future du garde ne peut donc tuer un slot, meme si elle reintroduit le defaut.Validation
Les quatre proprietes, dans l'ordre ou elles se defont si l'une manque :
depot illisible -- l'appelant SURVITdepot illisible PURGEdepot sain sans pack CONSERVEcontrole NEGATIFLe controle negatif est celui qui compte : l'en-tete de ce banc exige que chaque garde soit « valide par ses faux negatifs, pas par ses hits », et c'est precisement ce qui manquait a l'assertion qui passait depuis #15105.
Ce que cette PR ne fait PAS
myia-ai-01-wsl-*: jobs tues par OOM dans la VM WSL pendant les drains de file (4 echecs mesures, vert jumeau sur un autre runner) #16643 (pression memoire,Out of memory/ exit 134 sous--memory=3072m). Signature differente — mort pendant un job, avec journal — et arbitrage de caps qui reste ouvert. Les deux se sont rencontres sur le slot 8 sans avoir la meme cause.Verifications
COURSE_CATALOG.generated.*et blocsCATALOG-STATUS: byte-identiques amain(aucun fichier catalogue dans le diff).git grep -ln(pasgrep -r) surwch_broken_refs|wch_ref_count|pack_count|count_lines: 2 fichiers,work_cache_health.shet son banc.entrypoint.shn'appelle aucune de ces mesures directement — il n'appelle quewch_check_workdir, dont la signature et le contrat sont inchanges. Les trois fichiers sont dans le diff ; aucun appelant hors perimetre. Test de sanite de l'instrument :git grep -c wch_integrity_passrend3sur le fichier ou je sais le terme present, et rien surentrypoint.sh— ce qui confirme la lecture plutot que de la supposer..lean, aucun artefact de resultats touche.🤖 Generated with Claude Code