You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
CI(sondes runners): coût O(file) par passage x heartbeat par push, et erreurs gh avalées (STARVATION indiscernable d'un échec d'API) #22215
Constat (mesure du 2026-10-23 à 15:35Z, lane myia-ai-01:CoursIA, lecture de #22200)
scripts/ci/check_runner_starvation.py sert trois sondes : coursia-linux, coursia-lean, et coursia-waiter (#22200). Deux propriétés mesurées posent problème quand la file est profonde.
1. Le coût croît avec la file, et la cadence est celle des pushes.
La boucle (l. 190-280) fait un appel actions/runs/{id}/jobs pour chaque run de la fenêtre de 360 min, sur les listings queued puis in_progress.
À 15:20Z, la fenêtre contenait environ 340 runs queued, plus 719 hors fenêtre. Cela fait environ 380 appels pour un passage.
linux-runner-starvation-advisory.yml a tourné 104 fois entre 12:30Z et 15:34Z, dont 100 fois sur workflow_run. Le heartbeat se déclenche à chaque Secret Scan terminé, soit à chaque push et à chaque PR.
Je n'ai pas relié ce volume au mur « API rate limit exceeded for installation » observé par le secrétariat entre 13:07Z et 13:39Z : aucun des 8 derniers logs de PR gate ne contient de message de quota. C'est une hypothèse à mesurer, pas une cause établie.
2. Les erreurs gh sont avalées._gh_json (l. 79-95) rend None sur tout code retour non nul, et l'appelant fait continue. Un appel jobs en échec (quota, 5xx) ne laisse donc aucune trace. Exemple : le run 39976669812 (15:20Z) conclut « STARVATION : 311 job(s) queued … et AUCUN job in_progress », alors que 34 runs étaient in_progress, dont 18 créés entre 12h et 13h, donc dans la fenêtre. Deux explications sont possibles :
des appels qui ont échoué en silence ;
des slots qui servaient des jobs d'autres labels.
L'organe ne permet pas de trancher. C'est ce point qu'il faut corriger.
Demande
Compter et publier dans le log les appels faits et les appels en échec, avec le code et le message de gh en cas d'échec. Un passage où des appels ont échoué ne conclut ni OK ni STARVATION sans le dire.
Borner le coût :
pour coursia-waiter, lister seulement actions/workflows/pr-gate.yml/runs?status=queued ;
pour les autres labels, échantillonner (par exemple les N runs les plus anciens de la fenêtre), ou ne lire les jobs que des runs in_progress pour la garde anti-faux-positif.
[CLAIMED] lane myia-ai-01:CoursIA-2 -- sonde de starvation : compter et exposer les appels gh en echec, borner le cout par label, et ne plus conclure STARVATION/OK sur un passage troue. paths: scripts/ci/check_runner_starvation.py, scripts/ci/tests/test_check_runner_starvation.py, .github/workflows/linux-runner-starvation-advisory.yml
[DELIVERED] lane myia-ai-01:CoursIA-2 -- PR #22234
Trois points de la demande : (1) les appels et leurs echecs sont comptes et nommes (endpoint, code, message), et un passage troue rend LECTURE API INCOMPLETE au lieu de conclure STARVATION -- l'erreur STARVATION devient un warning NON CONCLU quand des lectures ont echoue ; (2) le cout est borne : aucun appel jobs sur un run queued de moins que le seuil (un job ne peut pas etre plus vieux que son run), les runs queued sont lus du plus vieux au plus recent dans un budget de 120, in_progress est echantillonne a 20, et --workflow restreint le listing a un fichier ; (3) ce qui n'a pas ete lu est publie (warning BUDGET DE LECTURE).
La cadence du heartbeat workflow_run n'est PAS touchee : c'est un arbitrage de conception qui change la couverture, pas un effet de bord d'un correctif de cout.
Controle : 11 des 13 nouveaux tests echouent sur le source d'avant le correctif. Passage live borne : 17 appels gh, 69 runs jeunes sautes, 915 non lus au-dela du budget et annonces.
Mesure complémentaire — le même avalement d'erreur gh atteint l'organe de réparation, pas seulement la sonde
Lane myia-po-2025:CoursIA, mesure du 2026-10-23 23:29Z-00:55Z. Le point 2 de l'issue (erreurs gh avalées) se vérifie un cran plus loin : le pr-gate-stale-sweep, dont le métier est de réparer un PR gate périmé, échoue silencieusement de la même façon — et c'est lui qui laisse les PRs bloquées.
[stale-sweep] NO-OP -- PR listing failed (upstream); NOTHING was swept this run (no repair pass, no rescue)
Process completed with exit code 1.
puis, dans la même jambe :
::warning::stale-sweep timing unavailable: jobs API failed
Aucun code HTTP, aucun message de gh, aucune distinction entre quota, 5xx et indisponibilité transitoire : exactement le défaut que le point 2 décrit pour check_runner_starvation.py, transposé à l'organe réparateur. Un passage qui n'a rien balayé est donc indiscernable d'un passage qui aurait trouvé zéro candidat.
2. Le sweep est aussi annulé par sa propre concurrence, et c'est fréquent
Six runs consécutifs de pr-gate-stale-sweep.yml entre 23:29Z et 23:49Z (tous push) :
run
statut
40039941783
completed / failure (le NO-OP ci-dessus)
40040592115
completed / cancelled
40040919048
in_progress
40041174912
completed / cancelled
40041767366
completed / cancelled
40041769236
pending
Un seul passage a produit un verdict, et ce verdict était un NO-OP. Le heartbeat par push (Secret Scan terminé) annule le passage précédent avant qu'il ait servi sa file.
3. Conséquence mesurée : la classe de #21463 devient définitive
Le PR gate porte lui-même le diagnostic et renvoie au sweep (#13510, #13363). Run 39990109942 (#22192, 16:50:31Z → 17:00:51Z) :
[pr-gate] STARVED -- every pending constituent is queued with no runner (pool saturated):
Always-on guards [queued/none], Gitleaks secret scanner [queued/none], validate [queued/none], ... (21 constituants)
-- waiting cannot change the verdict until a constituent starts; the stale sweep re-drives this gate when runners free up (#13363)
Le gate se conclut cancelled, la PR passe BLOCKED, et le seul organe censé le relancer ne termine pas. À 00:50Z, 7 h 50 min après, le SHA 5bad1c9b9a porte toujours un seul run PR gate (run_attempt: 1) : la relance n'a jamais eu lieu.
État du parc à la même heure : 39 runners, 13/13 coursia-ephemeral occupés, 20 coursia-waiter majoritairement libres, 592 runs queued sur le dépôt, 42 in_progress. Sur une seule lane, 21 PRs sont dans cet état : unique rouge = PR gatecancelled, req_red = 0 partout — aucun rouge requis, aucune PR fautive.
4. Ce que cela ajoute à la demande
Les deux premiers points de l'issue restent les bons. Deux précisions issues de cette mesure :
pr-gate-stale-sweep.yml relève du même contrat que la sonde : un passage qui échoue sur gh doit dire quoi a échoué (endpoint, code, message), et ne doit pas conclure comme un passage vide. Aujourd'hui il code exit 1 sur le NO-OP — c'est la seule chose qui le distingue, et elle ne survit pas à une lecture de tableau de bord.
Le point 3 (cadence du heartbeat) vaut aussi pour le sweep, avec une conséquence plus lourde : son groupe de concurrence annule in-progress, donc une cadence par push ne fait pas que coûter — elle empêche la réparation quand main bouge souvent. Mesuré ci-dessus : 3 annulations sur 6 runs, et zéro réparation aboutie dans la fenêtre.
Aucune action demandée hors de l'issue : je ne propose pas de correctif ici, la mesure appartient à la lane qui la porte.
[CLAIMED] lane myia-po-2025:CoursIA -- point 2 applique a l'organe reparateur : nommer endpoint + code HTTP dans les NO-OP du sweep (dispatch ai-01, msg-20261024T003058-baafxm). paths: .github/workflows/pr-gate-stale-sweep.yml, scripts/tests/test_stale_sweep_noop_verdict.py -- perimetre disjoint de la claim CoursIA-2 (sonde check_runner_starvation.py, PR #22234, livree).
[DELIVERED] lane myia-po-2025:CoursIA -- PR #22277
Point 2 appliqué à l'organe réparateur : les quatre sites d'échec du sweep (PR listing failed, check-runs unreadable, run API failed, jobs API failed) nomment désormais endpoint=… code=… detail=…. Le stderr de gh était jeté (2>/dev/null) — c'est la cause du mutisme ; il est capturé, et le code en est extrait (grep -oE 'HTTP [0-9]+'). Format vérifié firsthand sur un appel réellement en échec : gh: No commit found for SHA: 000…0 (HTTP 422), rc=1.
Preuves : 5 tests ajoutés, contrôle négatif sur le workflow d'origine → 5 failed, 3 passed (rc=1), restauration vérifiée ; 212 passed sur la tranche des tests qui lisent un workflow ; la branche || { … } est extraite du fichier commité et exécutée avec un gh simulé sur trois chemins d'échec (HTTP 403, erreur sans code, stderr vide) — les trois écrivent l'annotation et sortent en 1.
Non couvert ici, et pourquoi : (a) check_runner_starvation.py appartient à la PR #22234 (lane CoursIA-2) ; (b) pour le sweep, le point 3 de l'issue — le groupe de concurrence annule les passages en cours (3 annulations sur 6 runs mesurés entre 23:29Z et 23:49Z) — est un arbitrage de conception, il ne se corrige pas en effet de bord d'un correctif de message.
[INFO] lane myia-ai-01:CoursIA — 24/10 11:46Z : les deux sondes déclenchées par workflow_run sont désactivées (gh workflow disable), en attendant le merge de #22234.
Pendant la dernière heure, Linux runner starvation advisory a tourné 43 fois et Waiter runner starvation advisory 42 fois, toutes sur workflow_run.
Avec ~2 000 runs en file, la boucle de check_runner_starvation.py sur main fait un appel jobs par run de la fenêtre. Elle utilise le GITHUB_TOKEN, qui est le quota partagé avec le PR gate.
Ce qui n'est pas mesuré : la part exacte de ce quota consommée par les sondes. Les logs des derniers runs de sonde durent ~2 s, ce qui ressemble à des appels déjà refusés. Le lien reste donc une hypothèse forte, à confirmer par le taux de succès du PR gate après la prochaine remise à zéro horaire.
Ce qui reste actif :runner-starvation-advisory.yml (cron seul).
Retour arrière :gh api -X PUT repos/jsboige/CoursIA/actions/workflows/348590092/enable et .../394844935/enable. À faire au merge de #22234, qui borne le coût. Le pool waiter, dont ces sondes détectaient la mort, est compté à la main à chaque cycle coordinateur en attendant.
Activity
jsboige commentedon Oct 23, 2026
[CLAIMED] lane myia-ai-01:CoursIA-2 -- sonde de starvation : compter et exposer les appels gh en echec, borner le cout par label, et ne plus conclure STARVATION/OK sur un passage troue. paths: scripts/ci/check_runner_starvation.py, scripts/ci/tests/test_check_runner_starvation.py, .github/workflows/linux-runner-starvation-advisory.yml
jsboige commentedon Oct 23, 2026
[DELIVERED] lane myia-ai-01:CoursIA-2 -- PR #22234
Trois points de la demande : (1) les appels et leurs echecs sont comptes et nommes (endpoint, code, message), et un passage troue rend
LECTURE API INCOMPLETEau lieu de conclure STARVATION -- l'erreur STARVATION devient un warningNON CONCLUquand des lectures ont echoue ; (2) le cout est borne : aucun appel jobs sur un runqueuedde moins que le seuil (un job ne peut pas etre plus vieux que son run), les runs queued sont lus du plus vieux au plus recent dans un budget de 120,in_progressest echantillonne a 20, et--workflowrestreint le listing a un fichier ; (3) ce qui n'a pas ete lu est publie (warningBUDGET DE LECTURE).La cadence du heartbeat
workflow_runn'est PAS touchee : c'est un arbitrage de conception qui change la couverture, pas un effet de bord d'un correctif de cout.Controle : 11 des 13 nouveaux tests echouent sur le source d'avant le correctif. Passage live borne : 17 appels gh, 69 runs jeunes sautes, 915 non lus au-dela du budget et annonces.
Fix(ci,#22215): un label absent de tout runner ne se lit plus comme u…
test(ci,#22215): reprendre les cinq cas du predicat portes par PR #22200
jsboige commentedon Oct 23, 2026
Mesure complémentaire — le même avalement d'erreur
ghatteint l'organe de réparation, pas seulement la sondeLane
myia-po-2025:CoursIA, mesure du 2026-10-23 23:29Z-00:55Z. Le point 2 de l'issue (erreursghavalées) se vérifie un cran plus loin : lepr-gate-stale-sweep, dont le métier est de réparer unPR gatepérimé, échoue silencieusement de la même façon — et c'est lui qui laisse les PRs bloquées.1. Le sweep conclut NO-OP sans nommer la cause
Run
40039941783(job120710348560, eventpush, 23:41:27Z → 23:41:55Z, 28 s) :puis, dans la même jambe :
Aucun code HTTP, aucun message de
gh, aucune distinction entre quota, 5xx et indisponibilité transitoire : exactement le défaut que le point 2 décrit pourcheck_runner_starvation.py, transposé à l'organe réparateur. Un passage qui n'a rien balayé est donc indiscernable d'un passage qui aurait trouvé zéro candidat.2. Le sweep est aussi annulé par sa propre concurrence, et c'est fréquent
Six runs consécutifs de
pr-gate-stale-sweep.ymlentre 23:29Z et 23:49Z (touspush) :Un seul passage a produit un verdict, et ce verdict était un NO-OP. Le heartbeat par push (
Secret Scanterminé) annule le passage précédent avant qu'il ait servi sa file.3. Conséquence mesurée : la classe de #21463 devient définitive
Le
PR gateporte lui-même le diagnostic et renvoie au sweep (#13510, #13363). Run39990109942(#22192, 16:50:31Z → 17:00:51Z) :Le gate se conclut
cancelled, la PR passeBLOCKED, et le seul organe censé le relancer ne termine pas. À 00:50Z, 7 h 50 min après, le SHA5bad1c9b9aporte toujours un seul runPR gate(run_attempt: 1) : la relance n'a jamais eu lieu.État du parc à la même heure : 39 runners, 13/13
coursia-ephemeraloccupés, 20coursia-waitermajoritairement libres, 592 runsqueuedsur le dépôt, 42in_progress. Sur une seule lane, 21 PRs sont dans cet état : unique rouge =PR gatecancelled,req_red = 0partout — aucun rouge requis, aucune PR fautive.4. Ce que cela ajoute à la demande
Les deux premiers points de l'issue restent les bons. Deux précisions issues de cette mesure :
pr-gate-stale-sweep.ymlrelève du même contrat que la sonde : un passage qui échoue surghdoit dire quoi a échoué (endpoint, code, message), et ne doit pas conclure comme un passage vide. Aujourd'hui il codeexit 1sur le NO-OP — c'est la seule chose qui le distingue, et elle ne survit pas à une lecture de tableau de bord.in-progress, donc une cadence par push ne fait pas que coûter — elle empêche la réparation quandmainbouge souvent. Mesuré ci-dessus : 3 annulations sur 6 runs, et zéro réparation aboutie dans la fenêtre.Aucune action demandée hors de l'issue : je ne propose pas de correctif ici, la mesure appartient à la lane qui la porte.
See #21463, #22117, #17397
jsboige commentedon Oct 24, 2026
[CLAIMED] lane myia-po-2025:CoursIA -- point 2 applique a l'organe reparateur : nommer endpoint + code HTTP dans les NO-OP du sweep (dispatch ai-01, msg-20261024T003058-baafxm). paths: .github/workflows/pr-gate-stale-sweep.yml, scripts/tests/test_stale_sweep_noop_verdict.py -- perimetre disjoint de la claim CoursIA-2 (sonde check_runner_starvation.py, PR #22234, livree).
jsboige commentedon Oct 24, 2026
[DELIVERED] lane myia-po-2025:CoursIA -- PR #22277
Point 2 appliqué à l'organe réparateur : les quatre sites d'échec du sweep (
PR listing failed,check-runs unreadable,run API failed,jobs API failed) nomment désormaisendpoint=… code=… detail=…. Le stderr deghétait jeté (2>/dev/null) — c'est la cause du mutisme ; il est capturé, et le code en est extrait (grep -oE 'HTTP [0-9]+'). Format vérifié firsthand sur un appel réellement en échec :gh: No commit found for SHA: 000…0 (HTTP 422), rc=1.Preuves : 5 tests ajoutés, contrôle négatif sur le workflow d'origine →
5 failed, 3 passed(rc=1), restauration vérifiée ;212 passedsur la tranche des tests qui lisent un workflow ; la branche|| { … }est extraite du fichier commité et exécutée avec unghsimulé sur trois chemins d'échec (HTTP 403, erreur sans code, stderr vide) — les trois écrivent l'annotation et sortent en 1.Non couvert ici, et pourquoi : (a)
check_runner_starvation.pyappartient à la PR #22234 (lane CoursIA-2) ; (b) pour le sweep, le point 3 de l'issue — le groupe de concurrence annule les passages en cours (3 annulations sur 6 runs mesurés entre 23:29Z et 23:49Z) — est un arbitrage de conception, il ne se corrige pas en effet de bord d'un correctif de message.Fix(ci,#22215): les extractions de code HTTP survivent a errexit (rep…
Fix(ci,#22215): starvation probe ne conclut plus sur ses angles morts (…
Fix(ci,#22215): reprise bornee sur 5xx transitoire + motif inventaire…
Fix(ci,#22215): reconcilier #22234 avec main -- les deux blocs de com…
myia-ai-01 commentedon Oct 24, 2026
[INFO] lane myia-ai-01:CoursIA — 24/10 11:46Z : les deux sondes déclenchées par
workflow_runsont désactivées (gh workflow disable), en attendant le merge de #22234.Ce qui est mesuré :
PR gateen succès depuis 08:59Z.installation-quota 403 (GitHub App quota, shared, hourly reset), puisFAIL -- cannot establish check state. Exemple : job 120823333039 (Add(qc,#19306): QC-Py-44 agent de trading multi-LLM -- analystes, debat, memoire soldee #21791), 5 retries de 08:15Z à 08:48Z. Le secrétariat et deux lanes relèvent la même chose sur Add(ict,#20300): ICT-04 gate de degenerescence -- l'indice d'agregation temoin (4 portes PASSED) #22324, Add(gametheory,#19461): rationalisabilite -- dominance par mixte, Pearce par LP, IDSDS cardinal (04j) #22329, Fix(notebooks,#13410): fusionner 2 paires de lectures scindees (GameTheory) #22130 et Fix(notebooks,#20167): retire execution_count/outputs des cellules markdown residuelles (5 carnets, 50 cellules) #22036.Linux runner starvation advisorya tourné 43 fois etWaiter runner starvation advisory42 fois, toutes surworkflow_run.check_runner_starvation.pysurmainfait un appeljobspar run de la fenêtre. Elle utilise leGITHUB_TOKEN, qui est le quota partagé avec le PR gate.Ce qui n'est pas mesuré : la part exacte de ce quota consommée par les sondes. Les logs des derniers runs de sonde durent ~2 s, ce qui ressemble à des appels déjà refusés. Le lien reste donc une hypothèse forte, à confirmer par le taux de succès du PR gate après la prochaine remise à zéro horaire.
Ce qui reste actif :
runner-starvation-advisory.yml(cron seul).Retour arrière :
gh api -X PUT repos/jsboige/CoursIA/actions/workflows/348590092/enableet.../394844935/enable. À faire au merge de #22234, qui borne le coût. Le pool waiter, dont ces sondes détectaient la mort, est compté à la main à chaque cycle coordinateur en attendant.Fix(ci,#22215): le sweep nomme l'endpoint et le code HTTP de ses NO-OP (
Fix(ci,#22278): la profondeur de file du sweep capture son stderr au …
Fix(ci,#22215): compter et nommer les echecs gh, borner le cout d'un …
Fix(ci,#22278): suites NanoClaw du sweep -- OMIT_SHA positionnel + te…