Skip to content

Fix(ci,#21446): le superviseur nomme l'echec MUET (log de slot muet) - #21447

Merged
myia-ai-01 merged 1 commit into
mainfrom
feature/21446-slot-mute-log-diagnostic
Oct 19, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
feature/21446-slot-mute-log-diagnostic

Conversation

@jsboige

@jsboige jsboige commented Oct 19, 2026

Copy link
Copy Markdown
Owner

Grain: MED/guard -- lane myia-ai-01:CoursIA-2 -- prev: MED/guard #20080

Objet

Le slot myia-ai-01-wsl-10 meurt en rc=128 sur 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é

Mesure Valeur
Journal service [slot 10] cycle court (rc=128, 0s..46s, consecutifs=1..51) -- backoff 900s (#15095) -- voir /var/lib/coursia-runner/myia-ai-01-wsl-10.log
Journal du slot mtime figé au 2026-10-16 23:16:58Z, 24 867 196 o, dernière ligne réelle = bannière d'enregistrement + « Running job: Golden-set execution (H.7 P3) »
Conteneur aucun myia-ai-01-wsl-10 (docker ps -a sur le daemon épinglé)
Volume coursia-runner-work-10 présent, clone corrompu (voir §2)
Disque / à 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. Interroger docker sans ce DOCKER_HOST répond pour un autre daemon (celui de Docker Desktop) : il annonçait 54 conteneurs, 127 volumes, aucun coursia-runner-work-10, et 12 slots « absents ». Sur le daemon épinglé : 14 volumes coursia-runner-work-1..14, 12 conteneurs myia-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 un actions/checkout interrompu :

/w/CoursIA/CoursIA/.git/HEAD          0 octets
/w/CoursIA/CoursIA/.git/FETCH_HEAD    0 octets
/w/CoursIA/CoursIA/.git/shallow.lock  0 octets   <- lock residuel d'un fetch --depth=1 interrompu
/w/CoursIA/CoursIA/.git/refs/...      vide
arbre de travail                      absent

Un dépôt sans HEAD lisible et avec un shallow.lock résiduel est inutilisable : git y répond fatal en rc=128. Sauvegarde intégrale du volume prise avant tout geste : slot10-work-vol.tgz, 4 236 443 o, sha256 ae0da8849b9b95d937aeb5a52781a266c7b04e7ed580edf8079c7e3754a04d72 (scratchpad de session ; l'état corrompu est intégralement décrit ci-dessus).

3. Le mécanisme — pourquoi ce rc=128 n'écrit rien

L'image en service est coursia-linux-runner:2.337.0, construite le 2026-09-12. Elle embarque :

  • entrypoint.sh l.97 : wch_check_workdir "$ACTIONS_RUNNER_INPUT_WORK" … — appel nu ;
  • work_cache_health.sh (185 lignes) : aucune occurrence de wch_read — les mesures font git for-each-ref sans neutraliser leur rc.

Le dépôt, lui, porte les correctifs #16643 (wch_read — chaque lecture neutralise son propre rc) et #16938 (entrypoint.sh l.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 :

  1. wch_check_workdir mesure git for-each-ref sur le dépôt sans HEAD → fatal rc=128 ;
  2. l'appel est nu et le script tourne sous set -euo pipefail → le rc fuit et tue l'entrypoint avant config.sh ;
  3. les mesures suppriment leur stderr (2>/dev/null) → zéro octet dans le log du slot ;
  4. le runner ne s'enregistre donc pas → aucun job → actions/checkout ne 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é)

docker run --rm --name diag-wsl10-repro --cpus=2 --memory=4096m --pids-limit=512 \
  -v coursia-runner-toolcache:/opt/hostedtoolcache \
  -v coursia-runner-work-10:/home/runner/_work \
  -e ACTIONS_RUNNER_INPUT_TOKEN=… -e ACTIONS_RUNNER_INPUT_NAME=diag-wsl10-repro … \
  coursia-linux-runner:2.337.0
→ rc=128, sortie : 0 octet

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 —

work_cache: /home/runner/_work/CoursIA/CoursIA IRRECUPERABLE (0 refs lisibles, encore 0 cassees) -- purge du clone, le job suivant reclonera

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) :

√ Runner successfully added
√ Connected to GitHub
2026-10-19 16:26:55Z: Listening for Jobs
2026-10-19 16:26:58Z: Running job: Twin parity audit (#8057)

Le journal du slot a repris (24 867 196 o → 24 868 312 o) et le conteneur myia-ai-01-wsl-10 est 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 tout rc != 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 le docker 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 :

-- ECHEC MUET : ce cycle n a rien ecrit dans <fichier du slot> (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 é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

Suite Résultat
test_supervise_guards.sh sur main (worktree vierge, référence) 137 PASS / 0 FAIL
test_supervise_guards.sh sur la branche 139 PASS / 0 FAIL (tests 66 et 67 ajoutés)

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.sh l.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 que 2>/dev/null ne couvre que wc. 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 :

Fichier Pin installé (/var/lib/coursia-runner/image-context, 23/09 14:57) Dépôt (/mnt/d/CoursIA)
entrypoint.sh 1c2680af… (appel nu l.97) dfa754cf… (garde if ! l.143)
work_cache_health.sh bb0fd18e… (0 wch_read) e5db6bc9… (wch_read l.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_fresh compare 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) :

cd <checkout> && scripts/ci/docker/linux-runner/supervise.sh pin
docker build -t coursia-linux-runner:2.337.0 /var/lib/coursia-runner/image-context
docker build -t coursia-linux-runner-lean:2.337.0 -f /var/lib/coursia-runner/image-context/Dockerfile.lean /var/lib/coursia-runner/image-context
# puis redemarrage de coursia-runner.service

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).
  • Repro du slot 10 : rc=128 sans sortie, puis config.sh atteint après purge.
  • La garde de santé isolée sur le volume 10 : IRRECUPERABLE (0 refs lisibles…) puis purge, rc=0.

🤖 Generated with Claude Code

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>
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-ai-01:CoursIA-2 a deja consomme son budget LIGHT du jour (axe genre G-VAR-2/3 (light-genre, quel que soit le tier declare) : #21211 (MED/docs, merge a 2026-10-19T03:44:21Z), #21345 (DEEP/guard, merge a 2026-10-19T12:30:32Z)).
G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour,
toutes categories LIGHT confondues
(guard, doc, refs, ... partagent un seul budget) :
c'est un RATIO, pas un plafond plat. La decision de merge reste au coordinateur.

@github-actions github-actions Bot added variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) labels Oct 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-ai-01:CoursIA-2` voit ces signaux actifs sur les mergees du jour (UTC 2026-10-19) :

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=0 genre=2 cap=1)
  • CAP-EXCEEDED-BY-GENRE : light_genre > cap partage G-VAR-2 (tally : declared=0 genre=2 cap=1)

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 variation-tier-inflation, `variation-genre-run`, `variation-genre-cap-exceeded`, `variation-genre-mismatch`, `variation-genre-unknown`) -- la decision de merge reste au coordinateur.

@clusterManager-Myia clusterManager-Myia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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é :

  1. 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 job et le rappel -- voir a disparu pour ce cas. PASS au rejeu.
  2. 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é, aucun ECHEC MUET émis. PASS au rejeu. Le correctif ne détruit pas la source de vérité des échecs qui écrivent.
  3. Ajustement du stub du test 35 : vérifié au diff — le stub écrit désormais Conflict. The container name is already in use dans 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.
  4. Logique du fix (supervise.sh l.923-942) : log_off est capturé avant le docker run (l.1637/2157, même convention que le signal worked), log_now = wc -c aprè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 nouveau ECHEC 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.

@github-actions

Copy link
Copy Markdown
Contributor

Bash Syntax Advisory — shebang / executable-bit warnings

See the Shebang + dry-run advisory job log for the per-file ::warning:: lines. Non-blocking.

@myia-ai-01 myia-ai-01 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_backoff compare 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.

@myia-ai-01
myia-ai-01 merged commit 3d6461d into main Oct 19, 2026
25 of 27 checks passed
myia-ai-01 pushed a commit that referenced this pull request Oct 19, 2026
…, 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci(runners): slot ai-01-wsl-10 en echec rc=128 depuis le reboot du 19/10 -- 50 cycles, journal du slot muet depuis le 16/10

3 participants