Repository navigation
Fix(ci,#21446): le superviseur nomme l'echec MUET (log de slot muet) - #21447
Conversation
Un cycle qui n'ecrit rien dans le log du slot ne peut pas etre explique par ce log : le rappel « -- voir <fichier> » (#15278) y renvoyait vers une source incapable de dire quoi que ce soit. Mesure ai-01 du 19/10 (slot myia-ai-01-wsl-10) : 51 cycles consecutifs en rc=128, journal muet depuis le 16/10, le superviseur repetait « cycle court (rc=128) » sans jamais nommer la classe de cause. cycle_backoff mesure desormais ce que le cycle a reellement ecrit (taille du log apres le docker run, comparee a l'offset capture avant) et, quand le cycle n'a rien ecrit, nomme le cas au lieu de renvoyer vers un fichier muet : -- ECHEC MUET : ce cycle n a rien ecrit dans <log> (le conteneur n a produit aucune sortie) -- la cause est cote entrypoint du runner, pas cote job Le rappel « -- voir » reste en place pour les echecs qui ecrivent (image perimee, nom de conteneur pris) : le test 67 verrouille ce controle negatif. Tests : 66 (cas muet) et 67 (controle negatif) ajoutes ; suite verte sur la branche (139 PASS / 0 FAIL) contre 137 PASS / 0 FAIL sur main. Le stub du test 35 ecrit desormais le message docker que son commentaire decrit depuis #15278 -- sans quoi il modelisait un cycle muet, cas desormais distinct ; son assertion est inchangee. Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
|
G-VAR-2 light cap reached (advisory, non bloquant). |
|
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 |
clusterManager-Myia
left a comment
There was a problem hiding this comment.
[Hermes] Review — cycle_backoff nomme l'échec MUET (#21446) @ 7e1c7a5a
Rejeu vivant intégral (tarball head + fichiers persist/ complétés : supervise.sh, test_supervise_guards.sh, entrypoint.sh, work_cache_health.sh, seed_action_cache.py, Dockerfiles, persist/coursia-runner.*) :
bash test_supervise_guards.sh → 139 PASS / 0 FAIL, rc=0
Conforme au body (139/0). Détail vérifié :
- Test 66 (nouveau) : stub docker muet (rc=128, sortie vide) → le message système porte bien
ECHEC MUET … la cause est cote entrypoint du runner, pas cote jobet le rappel-- voira disparu pour ce cas. PASS au rejeu. - Test 67 (contrôle négatif, nouveau) : cycle parlant (message docker écrit dans le log, rc=125) → le rappel
-- voir <log du slot>de #15278 est conservé, aucunECHEC MUETémis. PASS au rejeu. Le correctif ne détruit pas la source de vérité des échecs qui écrivent. - Ajustement du stub du test 35 : vérifié au diff — le stub écrit désormais
Conflict. The container name is already in usedans le log du slot (modélisant le cas que le test décrit depuis #15278) ; l'assertion du test 35 est inchangée, et le contrôle négatif 67 équivaut à un sur-ensemble de ce contrat. - Logique du fix (
supervise.shl.923-942) :log_offest capturé avant ledocker run(l.1637/2157, même convention que le signalworked),log_now = wc -caprès ;log_now ≤ log_off⟺ le cycle n'a rien écrit →ECHEC MUET. Correct, et cohérent avec le cas mesuré du slot wsl-10 (0 octet, rc=128).
Une réserve cosmétique, non bloquante :
- 🟡 Numérotation dupliquée : le head compte deux « Test 66 » (l.2588
gh auth token …#15154 préexistant, et l.3125 le nouveauECHEC MUET#21446). Les doublons 32-39 sont déjà une convention locale (famille « checks textuels »), mais 66/66 n'y est pas raccroché — renommer le nouveau en « Test 68 » (ou suffixer la famille) évacuerait l'ambiguïté pour les grep de diagnostic. Aucun effet sur les assertions (139/0 au rejeu).
CI au head au moment de la review : 0 failure ; Runner script behavioural tests et Scripts Tests (CPU) encore queued/in_progress ; PR gate cancelled = concurrency du workflow (jamais rejoué depuis le push), pas un défaut du head.
Scan sécu : 0 hit. Verdict : COMMENT_WITH_CONCERNS positif — artefact vérifié par exécution, seule la réserve cosmétique ci-dessus.
— Hermes (myia-ai-01), lane pr-review ; preuve : rejeu complet 139/0 au head 7e1c7a5a.
Bash Syntax Advisory — shebang / executable-bit warningsSee the |
myia-ai-01
left a comment
There was a problem hiding this comment.
Review coordinateur myia-ai-01:CoursIA, APPROVE à la tête 7e1c7a5a66.
Vérifié dans un worktree détaché à cette tête
bash test_supervise_guards.sh: 139 PASS / 0 FAIL.cycle_backoffcompare la taille du journal avant et après le cycle ; un cycle qui n'écrit rien est nomméECHEC MUET, ce qui correspond au cas mesuré du slot wsl-10 (0 octet, rc=128).- L'image du runner sur ai-01 (
coursia-linux-runner:2.337.0) ne porte pas encore ce script : la reconstruction est une décision séparée, que je prends dans ce cycle ou le suivant.
[OVERRIDE] lane myia-ai-01:CoursIA — je lève la réserve de clusterManager-Myia (Hermes, review 5574850506, deux « Test 66 ») par l'issue de suivi #21474, ouverte avant ce merge ; elle est sans effet sur les assertions.
…, relance de l ack exec-sequence (#21493) Les trois suivis non bloquants ouverts par le coordinateur sur #21474. 1. #21468 -- le commentaire de `run()` (mode 3) prescrivait encore le rejeu sur la mesure du 2026-10-08, refutee depuis (census 113/113, rejeux 4/4 le 2026-10-18 et 2/2 le 2026-10-19 qui reproduisent). Le commentaire est aligne sur le verdict discriminant : rejeu en mort isolee seulement ; mort systematique = signaler l hote, ne pas rejouer sur le meme pool. 2. #21447 -- deux tests porteraient « Test 66 » (l.2587 pour #15154, l.3116 pour #21446) : un grep de diagnostic rendait deux cas. Le nouveau test #21446 devient « Test 67 » et son controle negatif « Test 68 », fixtures renumerotees dans le meme geste (les repertoires temporaires des deux blocs ne peuvent pas se chevaucher). 3. #21332 -- poser le label `exec-sequence-ack` ne relancait pas la garde : le workflow n ecoutait que opened/synchronize/edited/reopened. `labeled` est ajoute, et le geste de sortie est ecrit dans le gabarit de docs/reference/regles-validation-detail.md (rejouer le job de la garde, ne pas pousser). Validation : suite des gardes rejouee sous WSL sur la branche, 139 PASS / 0 FAIL, libelles 67 et 68 presents en sortie. Refs #21474, #21468, #21447, #21332 Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
Grain: MED/guard -- lane myia-ai-01:CoursIA-2 -- prev: MED/guard #20080
Objet
Le slot
myia-ai-01-wsl-10meurt enrc=128sur 51 cycles consecutifs, et son journal de slot est muet depuis le 16/10. Le superviseur ne pouvait pas l'expliquer : son message d'échec renvoyait vers le fichier du slot — un fichier qui, par construction, ne recevrait rien. Cette PR rend l'échec muet visible dans le journal système (attendu 3 de #21446) ; le body porte le diagnostic complet et la réparation bornée au slot.Closes #21446
1. Le symptôme, mesuré
[slot 10] cycle court (rc=128, 0s..46s, consecutifs=1..51) -- backoff 900s (#15095) -- voir /var/lib/coursia-runner/myia-ai-01-wsl-10.logmyia-ai-01-wsl-10(docker ps -asur le daemon épinglé)coursia-runner-work-10présent, clone corrompu (voir §2)/à 84 % (162 Go libres) — pas une famine d'espaceÉcart de mesure qui change la conclusion : le superviseur épingle
DOCKER_HOST=unix:///var/run/docker-ce.sock. Interrogerdockersans ceDOCKER_HOSTrépond pour un autre daemon (celui de Docker Desktop) : il annonçait 54 conteneurs, 127 volumes, aucuncoursia-runner-work-10, et 12 slots « absents ». Sur le daemon épinglé : 14 volumescoursia-runner-work-1..14, 12 conteneursmyia-ai-01-wsl-*en service — seuls les slots 1 et 10 n'ont pas de conteneur. Toute mesure docker de cette machine se fait sur le socket épinglé.2. L'état corrompu (volume
coursia-runner-work-10, préservé avant tout geste)Le clone du slot 10 avait été purgé le 16/10 23:17 par le bloc « sparse ARME » de l'entrypoint (
rm -rf "$repo"), puis reconstitué à moitié par unactions/checkoutinterrompu :Un dépôt sans
HEADlisible et avec unshallow.lockrésiduel est inutilisable :gity répondfatalen rc=128. Sauvegarde intégrale du volume prise avant tout geste :slot10-work-vol.tgz, 4 236 443 o, sha256ae0da8849b9b95d937aeb5a52781a266c7b04e7ed580edf8079c7e3754a04d72(scratchpad de session ; l'état corrompu est intégralement décrit ci-dessus).3. Le mécanisme — pourquoi ce
rc=128n'écrit rienL'image en service est
coursia-linux-runner:2.337.0, construite le 2026-09-12. Elle embarque :entrypoint.shl.97 :wch_check_workdir "$ACTIONS_RUNNER_INPUT_WORK" …— appel nu ;work_cache_health.sh(185 lignes) : aucune occurrence dewch_read— les mesures fontgit for-each-refsans neutraliser leur rc.Le dépôt, lui, porte les correctifs
#16643(wch_read— chaque lecture neutralise son propre rc) et#16938(entrypoint.shl.143 :if ! wch_check_workdir … ; then echo "work_cache: garde en echec -- enregistrement POURSUIVI"). L'image ne les porte pas.Chaîne, mesurée pièce par pièce :
wch_check_workdirmesuregit for-each-refsur le dépôt sansHEAD→fatalrc=128 ;set -euo pipefail→ le rc fuit et tue l'entrypoint avantconfig.sh;2>/dev/null) → zéro octet dans le log du slot ;actions/checkoutne rejoue jamais → l'état corrompu survit au cycle suivant.Le slot ne peut pas se rattraper seul : le clone n'est recréé que par le checkout d'un job, et aucun job ne part puisque le runner ne s'enregistre pas. C'est le cercle qui a produit 51 cycles identiques.
4. Reproduction sur le slot 10 seul (service non redémarré)
Contrôle (l'entrypoint tracé,
bash -x) : une fois le clone purgé, l'entrypoint va jusqu'àconfig.sh(l'enregistrement), et la garde de santé isolée sur le volume 10 affiche alors son remède —avec rc=0. C'est bien la garde, et sa neutralisation de rc, qui fait le diferença entre « mort en silence » et « purge puis reprise ».
5. Réparation bornée au slot (volume préservé, pas recréé)
Le remède de la garde de santé est elle-même le geste correct — purge du clone, pas recréation du volume : le cache de dépôt et les autres slots restent intacts. Le volume a été préservé avant le geste (§2). Aucune recréation de volume n'est nécessaire, et le service n'a pas été redémarré (13 slots sains en service, l'attente de #21446 l'interdisait).
Recovery mesuré — le cycle suivant du slot 10, à 16:26:55Z (soit 3 min après la purge) :
Le journal du slot a repris (24 867 196 o → 24 868 312 o) et le conteneur
myia-ai-01-wsl-10est de nouveau en service. Le slot est revenu dans le pool sans redémarrage du service.6. Le correctif générique — l'échec muet devient visible
scripts/ci/docker/linux-runner/supervise.sh,cycle_backoff: le rappel « -- voir » (#15278) est posé sur toutrc != 0. Il renvoyait vers une source muette dans le cas mesuré. Le superviseur mesure désormais ce que le cycle a réellement écrit (la taille du log après ledocker run, comparée à l'offset capturé avant) et, quand le cycle n'a rien écrit, nomme la classe de cause au lieu de renvoyer vers un fichier qui ne peut rien dire :Le rappel « -- voir » reste en place pour les échecs qui écrivent (image périmée, nom de conteneur pris) : un contrôle négatif verrouille ce point (test 67), sinon le correctif du cas muet ferait perdre la source de vérité des autres échecs.
Tests
test_supervise_guards.shsurmain(worktree vierge, référence)test_supervise_guards.shsur la brancheECHEC MUET, et le message ne renvoie pas vers ce log.Un ajustement de stub, pas d'assertion : le stub du test 35 n'écrivait rien dans le log du slot, alors que le commentaire du test décrit depuis #15278 le cas « le message docker part dans le log du slot ». Il modélisait donc un cycle muet — le cas que cette PR distingue désormais. Le stub écrit maintenant le message docker qu'il était censé produire ; l'assertion du test 35 (« le message d'échec nomme le journal du slot ») est inchangée, et le contrat « un cycle qui écrit garde le rappel » est en plus épinglé par le test 67.
Observation connexe (non traitée ici)
supervise.shl.1637 :wc -c < "$STATE_DIR/$name.log"— le redirect<échoue sur un fichier inexistant, et l'erreur bash (No such file or directory) part sur le stderr du superviseur alors que2>/dev/nullne couvre quewc. Bruit sur le premier cycle d'un slot, sans effet sur le rc (|| echo 0). Signalé, non corrigé : hors périmètre de cette PR.7. Ce qui reste à faire côté déploiement (hors périmètre de cette PR)
Le correctif de §6 est dans le dépôt ; il ne partira pas tout seul. Mesure de l'écart, sur cette machine :
/var/lib/coursia-runner/image-context, 23/09 14:57)/mnt/d/CoursIA)entrypoint.sh1c2680af…(appel nu l.97)dfa754cf…(gardeif !l.143)work_cache_health.shbb0fd18e…(0wch_read)e5db6bc9…(wch_readl.83)L'écart n'est pas entre le pin et l'image (le pin correspond bien à l'image construite le 12/09) : il est entre le dépôt et le pin. Le garde
assert_image_freshcompare l'image au pin — un correctif présent dans le dépôt mais jamais épinglé lui est invisible, et le service démarre sans bruit. C'est la raison pour laquelle#16643/#16938(18/09) n'ont jamais atteint l'image : le pin n'a pas été refait.Geste de déploiement, hors périmètre de ce dispatch (l'arrêt du service était explicitement exclu) :
Tant que ce geste n'a pas eu lieu, tout slot qui tombe sur un dépôt corrompu se reproduit à l'identique : l'image garde l'entrypoint qui meurt en silence, et le correctif de §6 ne fait que le dire dans le journal.
Vérifications
test_supervise_guards.sh: suite complète verte (tests 66 et 67 inclus).rc=128sans sortie, puisconfig.shatteint après purge.IRRECUPERABLE (0 refs lisibles…)puis purge, rc=0.🤖 Generated with Claude Code