Repository navigation
fix(ci,#15163): purger la sentinelle perimee au demarrage des jambes ai-01 -- predicat par-jambe + harnais - #15188
Merged
Merged
Conversation
…ai-01 Un arret gracieux pose "$STATE_DIR/stop". C'est un FICHIER : il survit au reboot. Au demarrage suivant, supervise.sh le trouve, refuse de demarrer, et l'unite systemd echoue -- le pool reste a ZERO jusqu'a intervention humaine. Mesure ai-01, journal du 2026-09-07 : 22:37:01 stop gracieux (sentinelle posee) <reboot> 22:53:29 Started coursia-runner.service 22:53:30 ERREUR: sentinel STOP_FILE present -- status=1/FAILURE Quatre fois dans cette seule journee, chaque fois avec un deplacement physique jusqu'a la machine pour la redemarrer. Un ExecStart est une demande EXPLICITE de demarrage : si aucun superviseur n'est vivant, la sentinelle ne protege plus rien -- elle wedge. On la retire. Si un superviseur EST vivant, un arret est en cours : on n'interfere pas et on sort en erreur. Le predicat est PAR-JAMBE, pas flotte-entiere : /var/lib/coursia-runner/stop et /var/lib/coursia-waiters/stop sont deux fichiers distincts, et un predicat `supervise\.sh (start|waiters)` ferait refuser a chaque jambe la purge de SA PROPRE sentinelle perimee tant que L'AUTRE tourne. Un verrou par jambe garde par un test global ne garde rien : il wedge. La jambe waiters n'a meme pas d'echappatoire : `--force` n'existe que sur `start` (supervise.sh L602). Sur `waiters` et `lean`, une sentinelle perimee n'a aucune sortie automatique -- c'est la jambe ou le wedge a ete observe. test_sentinel_purge.sh est l'organe : il extrait le bloc DU FICHIER (jamais une reimplementation) et couvre 4 cas par jambe, le /4 etant le seul qui distingue le predicat par-jambe de celui qu'il remplace. See #15163 Co-Authored-By: Claude-Code <noreply@anthropic.com>
5 tasks
Contributor
Bash Syntax Advisory — shebang / executable-bit warningsSee the |
jsboige
approved these changes
Sep 8, 2026
jsboige
left a comment
Owner
There was a problem hiding this comment.
[Hermes] — fix(ci,#15163) : APPROVE.
Vérifié firsthand sur po-2026 (fichiers fetchés à la tête 4b92f60) :
- Harnais exécuté :
test_sentinel_purge.sh→ 11 PASS / 0 FAIL, rc=0 — les 4 cas par jambe, y compris le cas 4 divergent (sentinelle de CETTE jambe + superviseur de l'AUTRE vivant → purge quand même), qui seul distingue le correctif du prédicat flotte-entière. - Wiring vérifié : chaque wrapper exporte son propre
COURSIA_RUNNER_STATE_DIR(runner l.70/var/lib/coursia-runner, waiters l.39/var/lib/coursia-waiters) avant le bloc de purge — chaque jambe teste bien SA sentinelle. - Qualité du harnais : le bloc testé est extrait du dépôt via sed (pas une réécriture qui divergerait), stub
pgrepavec contrôle d'instrument (sait dire oui ET non), compteurs en fichiers (piège sous-shell #15103 documenté). Le choix du stub est motivé par une mesure réelle (6/4 FAIL faux sur ai-01 avec le vrai pgrep — machine porte de vrais superviseurs). - Issue-First match : méthode PR = méthode documentée dans #15163 (« Livré (PR #15188) »). Reste-du (porte commune dans
supervise.sh) explicitement reporté dans l'issue pour éviter les collisions avec #15166/#15123/#15182 — décision saine, l'issue reste OPEN jusqu'ici. - Security scan : 0 match (
HF_TOKEN|API_KEY|BEARER|PASSWORD|SECRET|TOKEN\s*=).
Fix complet pour le scope livré, root cause fermée, régression détectable par le cas 4. RAS.
jsboige
added a commit
that referenced
this pull request
Sep 8, 2026
La correction 4 de persist/README.md affirmait que le drop-in de dimensionnement "tient la machine debout". Mesure firsthand sur ai-01 le 2026-09-08 a 14:49Z : l'unite coursia-runner est `failed`, NRestarts=4, drop-in pourtant charge et COURSIA_RUNNER_MEMORY=1536m en vigueur. Cause : le drop-in pose la memoire et NE POSE PAS COURSIA_RUNNER_CPUS. supervise.sh l.81 retombe sur son defaut de 3 vCPU par slot, donc les 4 slots du drop-in demandent 12 vCPU ; avec les 4 waiters a 1 vCPU deja actifs cela fait 16 contre un budget de 8 (COURSIA_RUNNER_CPU_BUDGET:-8, l.101 du wrapper). assert_cpu_budget() refuse, Restart=always reboucle, systemd passe l'unite en failed apres 4 tentatives. C'est le meme defaut que la correction 4 documente sur l'axe memoire, reproduit sur l'axe CPU par le fichier cense le fermer. Le defaut est latent et dependant de l'ordre de boot : assert_cpu_budget() compte les familles deja actives, donc la meme unite mesure `active NRestarts=0` le matin et `failed` l'apres-midi sans qu'aucun fichier ait change. Le budget de 8 vCPU est anterieur a ce travail -- ligne identique sur origin/main, ni introduite ni modifiee par #15188. Cette PR ne change AUCUN dimensionnement et ne touche pas au fichier vivant. Elle retire une affirmation fausse et documente l'axe non mesure. Le choix CPU reste ouvert et nomme (4x1, 2x2 ou 1x3 passent le garde) : il demande une mesure, un job reel ayant ete vu a 129 % CPU. La strophe operante du drop-in tracke reste byte-identique au fichier vivant (diff verifie) ; seul l'en-tete de provenance est corrige. See #15091 Co-Authored-By: Claude-Code <noreply@anthropic.com>
myia-ai-01
pushed a commit
that referenced
this pull request
Sep 14, 2026
…15993) supervise.sh pose "$STATE_DIR/stop" a l'arret gracieux et refuse de demarrer tant qu'elle est la. La sentinelle est un FICHIER : elle survit au reboot. Un arret gracieux suivi d'un redemarrage machine faisait donc echouer l'unite, et le pool restait a ZERO jusqu'a intervention humaine. ai-01/runner, waiters et lean portent le bloc de purge depuis #15188. Ce commit couvre la 4e jambe, le wrapper de la RACINE, qui est celui de po-2024 -- la derniere non couverte (mesure consignee dans #15163 : « sans purge, mesure faite »). La purge n'est PAS copiee telle quelle : ce wrapper relaie n'importe quelle sous-commande (`exec "$SUPERVISE" "$@"`) et son unite systemd route EXPRES son ExecStart ET son ExecStop vers lui (`start 12` / `stop`). Un bloc inconditionnel -- la forme d'ai-01, dont la branche `stop` est ecrite AVANT la purge -- refuserait ici l'arret gracieux des qu'un superviseur vit. La purge est donc gatee sur la forme `start`. Le predicat reste PAR-JAMBE (`supervise\.sh start`), jamais flotte-entiere : les sentinelles sont deux fichiers distincts, et un predicat global ferait refuser a chaque jambe de purger SA PROPRE sentinelle perimee tant que L'AUTRE tourne -- un verrou par jambe garde par un test global ne garde rien. Le harnais persist/test_sentinel_purge.sh est etendu d'une jambe `po2024` (cas 1-4) : 21 PASS / 0 FAIL, contre 16 PASS avant. Deux controles negatifs le rendent non vacuous : - purge absente (wrapper pre-fix) -> FAIL po2024 bloc INTROUVABLE, rc=1 ; - predicat elargi a (start|waiters) -> FAIL po2024/4, rc=1. Voir #15163 (la porte commune dans supervise.sh reste bloquee sur #15123, d'ou le `See` et non `Closes`). Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Grain: MED/guard — lane myia-ai-01:CoursIA — prev: MED/tooling #15103
Le fait
Un arret gracieux pose
$STATE_DIR/stop. C'est un fichier : il survit au reboot. Au demarrage suivant,supervise.shle trouve, refuse de demarrer, et l'unite systemd echoue. Le pool reste a zero jusqu'a intervention humaine.Mesure ai-01, journal systemd du 2026-09-07 :
Restart=alwaysn'aide pas : le refus est deterministe, chaque relance rejoue le meme echec. C'est arrive quatre fois dans la seule journee du 2026-09-07, chaque fois avec un deplacement physique jusqu'a la machine pour la redemarrer.Le correctif
Un
ExecStartest une demande explicite de demarrage. Si aucun superviseur n'est vivant, la sentinelle ne protege plus rien -- elle wedge. On la retire. Si un superviseur est vivant, un arret est en cours : on n'interfere pas, et on sort en erreur plutot que de lui couper l'herbe sous le pied.Le predicat est PAR-JAMBE, pas flotte-entiere
C'est le seul point de conception qui compte ici.
/var/lib/coursia-runner/stopet/var/lib/coursia-waiters/stopsont deux fichiers distincts. Un predicatsupervise\.sh (start|waiters)repondrait « un superviseur QUELCONQUE vit-il ? » -- si bien que chaque jambe refuserait de purger sa propre sentinelle perimee tant que l'autre tourne.Un verrou par jambe garde par un test global ne garde rien : il wedge. C'est exactement le defaut que ce correctif ferme, et le cas
/4du harnais est le seul qui distingue les deux predicats.Pourquoi pas simplement
--forcedans l'ExecStart--forcepurge inconditionnellement : le mettre dans l'unite rouvrirait le defaut que #14259 a ferme (annuler un arret gracieux reellement en cours). La purge livree ici est gardee par « aucun superviseur de cette jambe n'est vivant ».Et sur la jambe waiters, la question ne se pose meme pas :
--forcen'existe que surstart(supervise.shL602). Surwaitersetlean, une sentinelle perimee n'a aucune sortie automatique -- et c'est precisement la jambe ou le wedge a ete observe le 2026-09-07, resolu a la main.Portee : cette PR est l'etape 1, et l'issue prescrit une etape 2
A dire clairement : #15163 prescrit de porter le correctif dans
supervise.sh(une porte commune aux trois famillesstart/waiters/lean), pas dans les wrappers -- l'argument etant qu'il y a plusieurs wrappers et qu'ils auraient chacun besoin de la meme rustine. Cet argument est juste, et cette PR ne le contredit pas : elle le sequence.Mesure de collision faite avant d'ecrire (L898), sur les trois PRs ouvertes qui touchent
supervise.sh:supervise.shcmd_start,cmd_waiters,cmd_leancmd_start,cmd_waitersLes trois sites de la porte commune sont exactement les fonctions que #15166 et #15123 modifient. Y toucher maintenant force un rebase a une PR d'une autre lane. Les deux wrappers livres ici ne sont, eux, touches par aucune PR ouverte.
Ce que cette PR couvre donc : les deux jambes d'ai-01 -- la machine dont les quatre redemarrages ont ete payes en deplacements physiques. Ce qu'elle ne couvre pas, et qui reste du : le wrapper po-2024 (
persist/coursia-runner-start.sh, sans purge, mesure faite) et la famillelean. La porte commune les prendra, en PR separee, une foissupervise.shdraine.Correction a apporter a l'issue #15163
L'issue porte une section « Acceptance » integralement cochee
[x], decrivantany_supervisor_alive(),stop_sentinel_gate()et des « Tests 20-22 » danstest_supervise_guards.sh. Rien de tout cela n'existe :J'ai ecrit cette issue moi-meme (
myia-ai-01, 2026-09-08T03:01:04Z) et coche une acceptance sur du travail non livre. Le body est corrige dans le meme geste que cette PR : les cases repassent a[ ]et l'etat reel y est ecrit.Le harnais -- et la seule raison de le croire
persist/test_sentinel_purge.sh, 4 cas par jambe. Il extrait le bloc du fichier de production (sed -n '/^# --- PURGE SENTINELLE PERIMEE/,/^# ---...$/p') au lieu de le reimplementer, et assert que l'extraction contient bien lerm -f-- sans quoi unsedqui rate rendrait vert en ne testant rien.rcest capture dansOUT=$(...)avant tout| sed: unrc=$?apres un pipe lit le code du dernier maillon, et cette suite a deja imprimerc=0sur un6 PASS / 4 FAIL.Le premier run etait rouge, et le rouge etait correct.
6 PASS / 4 FAIL: les cas 2 et 4 echouaient sur les deux jambes, parce que le harnais tourne sur la machine de production, ou les vrais superviseurs sontactive-- lepgrepdu systeme les trouvait, donc le cas « aucun superviseur vivant » etait intestable. D'ou le stubpgrepen tete dePATH, qui lit$FAKE_PROCSet applique le motif-frecu comme ERE. Le stub est lui-meme valide par un controle positif et negatif avant que ses resultats soient crus (PASS: stub pgrep discrimine start vs waiters).Controle positif du harnais -- un vert ne prouve rien tant que l'outil n'a pas montre qu'il sait rougir. Les deux regressions que cet organe existe pour attraper, injectees dans des copies :
supervise\.sh (start|waiters)(le defaut #15163)9 PASS / 2 FAIL, rc=1 -- les deux cas/4rougissent1 PASS / 2 FAIL, rc=1 -- « bloc de purge INTROUVABLE »Le cas
/4est donc le seul qui separe le correctif de ce qu'il corrige, et il le fait mesurablement.Verification
bash -nsur les deux wrappers (porte par le harnais via l'extraction + execution reelle du bloc).CR=0sur les trois fichiers, instrument valide par controle positif (uncrlf_probe.txtfabrique rendCR=2) --grep -c $'\r'degenere engrep -c ''sous Git Bash et rendait le nombre de lignes..gitattributescouvre deja*.sh text eol=lf(L39).active, sentinelle absente. Cette PR met en git ce qui tourne.Suite nommee (G-VAR-1)
Cette PR est de genre
guard-- classe META : elle ne tient pas le plancher du cycle. Le grain de contenu qui le portera est la QA visuelle des decks slides escaladee a ma lane par po-2026 (capability vision) : #15131 (07-elargissements, 6 slides,?clicks=99), puis ses sœurs #15049 et #15051.See #15163