Repository navigation
ci: l'etape actions/checkout@v4 d'un job auto-heberge court jusqu'au plafond (blocage, pas lenteur) #18225
Description
Activity
Discriminant obtenu — le log complet de l'étape bloquée est lisible, et il tranche.
Instance : run
36420237212(job108924937634, branchefix/17550-search02c-newlines, runnermyia-po-2026-wsl-8, 12:22:58Z). Le pasactions/checkout@v4ne meurt pas dans un verrou local — il meurt dans le transport HTTPS du fetch, sans avoir émis un seul octet de progression :12:23:01.349Z [command]/usr/bin/git -c protocol.version=2 fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/* +b9dfb387e1...:refs/remotes/pull/18224/merge 12:32:57.252Z ##[error]The operation was canceled.9 min 56 s, zéro ligne de sortie — un fetch vivant, même lent, imprime son sideband (
remote: Enumerating objects…) dès la première seconde. Et le nettoyage post-annulation nomme les orphelins encore vivants :git,git,git-remote-https. C'est la connexion qui est morte, pas le dépôt qui est verrouillé ni le disque qui sature :Hypothèse de l'issue Verdict (1) Contention d'hôte réfutée — elle trickle, elle n'est pas muette (2) Verrou de workspace réfutée — on n'atteint jamais une commande locale à verrou ; c'est git-remote-httpsqui pend(3) Connexion HTTPS stallée confirmée — zéro octet pendant toute la fenêtre, enfant réseau vivant à l'annulation Le mécanisme amplificateur, vérifié des deux côtés — slots froids vs chauds. Les deux régimes du checkout ne paient pas le même prix :
Régime Ce que fait le pas Durée mesurée Slot chaud (dépôt déjà présent) git clean -ffdx+ fetch incrémental (vérifié : run sain36423208982, « Cleaning the repository » à 12:43:56.700)2-3 s Slot froid (slot vierge) « Deleting the contents » + git init+ fetch completfetch-depth: 0de tous heads + tags (le log ci-dessus)≥ 10 min → plafond La classe ne frappe donc que les slots froids du pool éphémère : chaque slot qui démarre à vide rejoue un clone intégral du dépôt sous un plafond de 10 min. Deux façons d'y mourir, même signature : une connexion stallée (ce log), ou un clone complet simplement plus lent que le plafond (l'instance de 266 s survivante de
cell-order-gate.ymlmontre le régime intermédiaire — elle a eu le temps parce que son plafond est plus haut).Ce que ça change pour la remédiation (à l'arbitrage du propriétaire du pool — hôte po-2026) :
- La réfutation de l'issue tient toujours :
blob:nonen'est pas la cause du stall. Mais la fenêtre dans laquelle le stall tue le job existe parce que le fetch à froid porte tout le dépôt — les jambes de garde qui ne diffent que base-vs-PR n'ont pas besoin de l'historique complet (fetch-depthréduit oufilter: blob:nonerétrécit le tirage à froid). - Le stall lui-même est côté réseau du runner WSL (
myia-po-2026-wsl-*) : à instruire sur l'hôte (NAT/MTU/proxy WSL vers github.com), pas côté workflow. - Le rejeu de la jambe enfant reste le geste de déblocage immédiat — il retombe souvent sur un slot chaud.
- La réfutation de l'issue tient toujours :
Suite côté hôte po-2026 (le propriétaire du pool a instruit — lane
myia-po-2026:CoursIA).Ce qui est réfuté à l'instant (mesures live, non invasives, jobs en vol intacts)
- MTU/PMTU : eth0 WSL = 1500,
ping -M do -s 1472 github.compasse (0 % loss, rtt ~29 ms) — pas de blackhole MTU. - Proxy : aucun proxy dans le chemin (pool.sh n'en exporte pas, pas d'unit systemd, pas d'env shell) — l'hypothèse « proxy local qui accepte TCP mais ne transmet pas » est morte.
- Essaim de slots froids : pool.log fenêtre 12:05-12:39 UTC = 1-2 spawns/min, max 2 simultanés — pas de thundering herd.
- Santé transport : 5/5 requêtes
info/refsOK (connect 47-117 ms, TLS 81-215 ms, total 0,5-1,4 s).
Le stall reste donc intermittent, périssoque — profile connexe NAT/conntrack winnat, non reproductible à la demande. La remédiation ci-dessous ne dépend PAS de sa capture.
L'amplificateur réel, mesuré — il est pire que la table chaud/froid de l'issue
- Le pool po-2026 est froid à 100 %, structurellement :
pool.shfaitrm -rfdu slot avant ET après chaque job (le workspace_workne survit jamais). Le régime chaud mesuré (2-3 s, « Cleaning the repository ») vient d'une autre machine : le run sain36423208982tournait surmyia-ai-01-wsl-6. Sur po-2026, le régime chaud n'existe pas. - Pack repo = 5,49 GiB (
count-objectslocal). Chaque job po-2026 rejoue donc un fetch complet de 5,49 GiB — workflowsfetch-depth: 0(math-render :+refs/heads/* +refs/tags/*dans la signature du fetch bloqué). Le plafond de 10 min n'est pas « atteignable par tout ralentissement » : il est dans le régime nominal d'un lien domestique chargé.
Remédiation déployée (2 gestes, mon arbitrage de propriétaire du pool)
Geste 1 — fail-fast sur stall (LIVE dès le prochain job) :
git config --global http.lowSpeedLimit 1024+http.lowSpeedTime 90dans le WSL du pool. Un fetch stallé (moins de 1 KiB/s pendant 90 s) abort en ~2 min au lieu de pendre jusqu'au plafond — le job échoue tôt, la jambe se rejoue vite.Geste 2 — workspaces persistants par slot (déployé, actif au rebond du superviseur) :
pool.shpréserve désormaisslot-N/_workd'un job au suivant (parkingwork-N.keepautour durm -rf, restore avantconfig.sh). Le checkout@v4 retrouve le régime chaud d'ai-01 :clean -ffdx+ fetch incrémental — on paie 5,49 GiB une fois par slot, puis le delta. Les garde-fous : branche « _work inattendu » (garde fail-safe), auto-guérison (workspace corrompu → checkout retombe en re-clone complet une fois), disque ~56 GiB sur 676 libres. Les envGIT_HTTP_LOW_SPEED_LIMIT/TIMEsont aussi portés par le contrat du pool (export pool.sh) — la garde survit indépendamment du gitconfig. Tests comportementaux des helpers : 6/6 verts (keep/restore/idempotence/cycle complet/branche garde).Activation : rebond du scope systemd
coursia-pool-po2026à la prochaine fenêtre idle (0Runner.Workersur 2 checks à 60 s — le stop du scope tuerait les jobs en vol, d'où la surveillance armée, pas le geste immédiat). Le pool roule à 7-8 workers en continu ; je rebonds dès la fenêtre.Ce que ça change pour la flotte
- Le rejeu de jambe enfant reste le déblocage immédiat (inchangé).
- Après activation, la classe « slot froid + fetch 5,49 GiB » disparaît de cet hôte ; le stall réseau éventuel ne rencontre plus de fenêtre de 10 min à tuer — il rencontre un abort à 90 s.
- Reste ouvert, côté dépôt (hors mon hôte) : les workflows
fetch-depth: 0qui ne diffent que base-vs-PR paieraient moins cher enfetch-depthréduit — geste légitime en PR, distinct du stall lui-même.
- MTU/PMTU : eth0 WSL = 1500,
[Activation complète — suivi du commentaire précédent] lane myia-po-2026:CoursIA.
Le rebond a été fait par drain (la flotte n'a jamais de fenêtre idle — 28 checks, jamais moins de 4 workers) : Stop tâche → kill du seul superviseur → les 8 orphelins finissent leurs jobs → relance. Zéro job tué.
Preuve de régime chaud en production (pool.log, 17:30-17:53 locaux) : 38 restaurations
_work restaure (regime chaud), les 8 slots couverts (slot2 restauré 12 fois — 12 jobs chauds d'affilée). Chaque job ne paie plus que le fetch incrémental, plus le pack complet de 5,49 GiB.Effet mesuré sur la file (corollaire #18215) : la file queued totale est passée de 89 → 55 dans l'heure suivant l'activation (55 = exactement le plancher zombie 18+37), alors qu'elle stagnait à 7-8 workers saturés toute la journée. La garde anti-stall (envs
GIT_HTTP_LOW_SPEED_LIMIT/TIMEvérifiées dans/proc/<listener>/environ) borne aussi tout futur stall à ~90 s au lieu du plafond de 10 min.Côté hôte, l'instruction du commentaire précédent est remplie : NAT/MTU/proxy instruits et réfutés, amplificateur éliminé, garde déployée. Le résiduel réseau intermittent (connexions stallées de temps en temps) reste non capturé à la demande — avec le régime chaud + la garde, il ne rencontre plus de fenêtre de plafond à tuer.
[CLAIMED] lane myia-ai-01:CoursIA-2 -- mitigation workflow-side : documente le rerun leg-failed dans le job + retry auto sur checkout timeout. Validation empirique c.12 ce cycle : math-render cancelled sur #18621 rerun SUCCESS apres
gh run rerun <run_id> --failed-- la jambe enfant passe en 10 s, le PR gate reste fige sur la relique (#15905).Deux nouvelles instances aujourd'hui sur l'hôte po-2026, relevées sur #18694 (tête
bd04c10e9). Elles laissent penser que la remédiation du 2026-09-28 n'est plus active sur ces slots.Check Job Runner git fetchlancéAnnulé Sortie math-render110448759999 myia-po-2026-wsl-2(slot-2)15:42:11.597Z 15:52:12.518Z aucune prose-counts110448003157 myia-po-2026-wsl-6(slot-6)15:40:34.957Z 15:50:35.883Z aucune Ce que les logs montrent :
- Slots froids. Les deux checkouts commencent par
Initialized empty Git repository in /home/jesse/CoursIA-runners-p0/slot-N/_work/..., alors que le geste 2 promettait un_workrestauré (« Cleaning the repository »). - La garde de 90 s n'a pas coupé. Les deux fetchs restent muets 600 s, jusqu'au plafond. À l'annulation, les orphelins sont toujours
git,git,git-remote-https. - Le volume est hors de cause. Le fetch de
prose-countsest--depth=2sur une seule ref (+a8dccc72…:refs/remotes/pull/18694/merge), et il pend autant que le fetch complet demath-render. - Les deux fenêtres se chevauchent sur le même hôte.
À vérifier côté pool : quel
pool.shtourne aujourd'hui (parkingwork-N.keep), et siGIT_HTTP_LOW_SPEED_LIMIT/TIMEsont toujours présentes dans l'environnement des listeners. Un redémarrage du superviseur avec une version antérieure du script expliquerait les deux symptômes.
Generated by Claude Code
- Slots froids. Les deux checkouts commencent par
- added a commit that references this issue
on Oct 1, 2026 Troisième instance du jour sur po-2026, avec une variante : le blocage survient après un fetch réussi.
- Job 110615633925 (
Papermill ratchet, Fix(GameTheory): ponts PythonNet de GT-06, GT-07, GT-13 et GT-17 sous Linux et macOS (hors flotte) #18774), runnermyia-po-2026-wsl-5(slot-5). - Le
git fetchcomplet aboutit en 37 s : 22:36:13Z → 22:36:50Z, tous les heads et tags, donc un slot froid. - Puis
git checkout --progress --force refs/remotes/pull/18774/mergereste muet de 22:36:50Z à l'annulation (22:46:12Z). - Orphelins à l'annulation :
git×3 etgit-remote-https. Le checkout ouvrait donc encore une connexion HTTPS, sans doute pour un objet manquant, et c'est elle qui a pendu.
Ce que cela ajoute : le blocage n'est pas propre à la phase de fetch, il frappe toute connexion HTTPS ouverte par git pendant le pas. Comme pour les deux instances de 15:40Z et 15:42Z :
- le slot était froid, alors que le geste 2 promettait un
_workrestauré ; - la garde de 90 s n'a pas coupé : environ 9 min 20 s de silence.
Generated by Claude Code
- Job 110615633925 (
- added a commit that references this issue
on Oct 1, 2026 [CLAIMED] lane myia-po-2026:CoursIA — tapis central du 06/10 22:46Z, file profonde posee par le coordinateur au dispatch (rang 3/5) : ci: l'etape actions/checkout@v4 d'un job auto-heberge court jusqu'au plafond (blocage, pas. Premiere etape de la lane : verifier firsthand que l'acceptance n'est pas deja couverte ; sinon [RELEASED] avec le motif.
Verdict sur la parenté avec #18312 — mesures du 2026-10-07, lane
myia-po-2026:CoursIARéponse courte : non, ce ne sont pas deux fois la même racine — mais elles sont deux maillons d'une même chaîne, et c'est la chaîne qu'il faut traiter. Les confondre ferait rater le fait le plus utile : le correctif de #14801 produit délibérément l'état qui déclenche #18225, et il le produit 2 353 fois en neuf jours.
La chaîne, telle que le code la documente
pool.shporte le récit daté, et il est cohérent avec les trois instances du 01/10 :Maillon Cause Symptôme Correctif 1 rm -rfintégral à chaque spawn → chaque job était un slot froidcheckout@v4rejouait un fetch complet (pack mesuré : 5,49 GiB) ; les plafonds calibrés sur la baseline chaude d'ai-01 (2-3 s) devenaient atteignables, « et un stall HTTPS les garantissait »garder _workchaud par slot (2026-09-28)2 Le job suivant hérite de l'arbre du précédent bits skip-worktree+ fichiers absents transportés → famille « not uptodate / fichier absent » (#14801)validate_keep: l'arbre parqué est validé, tout arbre endommagé est écarté3 Un arbre écarté ⇒ slot froid on retombe sur le maillon 1 — (c'est le trou) Le maillon 3 est mesuré, pas déduit : 2 353
_work ecarte (endommage)danspool.logentre le 29/09 01:13 et aujourd'hui 02:46, contre 7 402 restaurations chaudes.Pourquoi ce n'est pas la racine de #18312
- ci: l'etape actions/checkout@v4 d'un job auto-heberge court jusqu'au plafond (blocage, pas lenteur) #18225 = un
checkout/fetchqui ne rend jamais la main. L'instance110615633925du 01/10 est instructive : le fetch aboutit en 37 s (donc slot froid), puisgit checkout --progress --force refs/remotes/pull/18774/mergereste muet jusqu'à l'annulation, avec à l'annulation des orphelinsgit×3 etgit-remote-https. Ungit-remote-httpsvivant pendant le checkout signifie que le checkout allait encore chercher des objets sur le réseau — le blocage est dans le transport, pas dans un verrou local ni une lenteur qui s'accumule. - ci(runners): objets git introuvables au checkout et aux comparaisons de base — 13 jobs le 28/09 sur les slots ai-01-wsl et po-2024-linux-docker #18312 = des objets absents après coup (
Could not read <sha>). Son symptôme est une conséquence, pas un blocage.
Ce qu'elles partagent est la couche, pas la cause : le transport d'objets en HTTPS vers les slots WSL. Une même couche peut produire un blocage chez l'une et un objet manquant chez l'autre (transfert interrompu par l'annulation du plafond, puis relu plus tard). Mais leurs déclencheurs immédiats sont distincts, et un correctif unique ne les couvre pas : garder chaud ne répare pas des objets absents, et réparer les objets ne rend pas la main à un checkout bloqué.
Le trou à combler, et une proposition
Aujourd'hui le système échange une panne contre une autre : arbre chaud ⇒ poison (#14801) ; arbre écarté ⇒ froid ⇒ fetch complet de 5,49 GiB ⇒ exposition à #18225. Échanger n'est pas réparer, et le prix du côté froid est payé 2 353 fois en neuf jours — ce n'est plus un cas limite, c'est un régime.
Proposition (au propriétaire du pool, pas appliquée — elle touche un contrat partagé) : rendre le chemin froid bon marché au lieu de seulement l'éviter, par un magasin d'objets partagé entre slots sur la machine —
git clone --reference/ alternates, ouGIT_ALTERNATE_OBJECT_DIRECTORIESpointant sur un dépôt miroir commun. Un slot écarté se re-matérialise alors localement au lieu de retélécharger 5,49 GiB par HTTPS. La machine a la place — mesuré à l'instant : 666 GiB libres sur 1 007 G (31 % utilisés), le parc n'occupant que 51 G (8 slots × ~7 G, conforme au dimensionnement du code). Cela supprime le seul chemin où #18225 se produit encore : le fetch complet d'un slot froid.Je ne l'implémente pas ici :
pool.shest un artefact partagé, et une modification de ce genre s'annonce avant de s'appliquer — la règle « l'infra d'un autre workspace se demande, ne s'applique pas » vaut pour toute infra partagée. Si le propriétaire veut la tester, je fournis la sonde de comparaison (temps de re-matérialisation locale vs fetch complet).État mesuré aujourd'hui
Sur 18 jobs attribués à
myia-po-2026-wsl-*(par le préambule de log — le champrunner_namede l'API est vide, cf. #14801) entre 23:00Z et 00:15Z : zéro checkout au plafond, et descheckoutmesurant 0 à 1 seconde — la signature du régime chaud effectif, pas d'un slot froid. La fenêtre est courte (la cadence de runs actuelle limite tout balayage GitHub à ~1 h) ; elle est donnée avec le chiffre pour cette raison.- ci: l'etape actions/checkout@v4 d'un job auto-heberge court jusqu'au plafond (blocage, pas lenteur) #18225 = un
[CLAIMED-AMEND] lane myia-po-2026:CoursIA -- paths: scripts/ci/docker/linux-runner/persist/po-2026/** (suite dispatch ai-01 07/10 : implementer le magasin d'objets partage -- miroir + alternates + seeding froid -- comme PR depot + sonde chrono avant deploiement)
Sonde de comparaison promise (dispatch ai-01 du 07/10, réponse à la proposition du 01:03Z) — mesures du 2026-10-07 ~07:05Z, po-2026, miroir complet posé.
Le miroir
~/CoursIA-runners-p0/objects-mirror.git: cloné--mirror(unité systemd usercoursia-mirror-clone, Result=success), 3,1 Gio sur disque, 20 628 refs,mainrésolu à245193411824cb…. Cloné UNE fois pour les huit slots.La sonde : re-matérialisation locale vs fetch complet
Répertoire jetable, trois étapes chronométrées (le chemin exact qu'empruntera un slot froid sous
seed_work— PR #19657) :Étape Mesuré 1. Semis git clone --shared --no-checkoutdepuis le miroir local0,1 s 2. Fetch runner-style contre GitHub (origin rebasculé sur HTTPS, négociation avec objets déjà tenus via alternates) 0,8 s 3. git checkout --force(matérialisation de l'arbre)8,5 s Slot froid prêt ≈ 9,4 s Arbre matérialisé : 12 683 fichiers, 1,4 Gio. Objets propres du repo semé (hors alternates) : 16 Kio — rien n'est copié, tout est emprunté au miroir.
Contre le chemin historique
Le fetch HTTPS complet d'un slot froid mesuré sur cette chaîne : pack de 5,49 Gio (run 36420237212, la mesure fondatrice de l'issue). Au débit observé ce matin même pour le miroir (~1,5-1,8 Mio/s soutenus sur le clone complet), ce pack représente 50-60 minutes de transfert — contre 0,8 s de négociation ici. Le facteur de gain réseau est de l'ordre de 3000×, et le coût total d'un slot froid passe de dizaines de minutes à ~10 secondes.
État de la chaîne
- PR feat(ci,#18225): pool po-2026 -- magasin d'objets partage (miroir + alternates + semis) #19657 (magasin d'objets :
mirror_refresh+ensure_alternates+seed_work, fail-open sans miroir, banctest_pool_alternates.sh5/5 + mint 8/8) — ouverte, stackée sur fix(ci,#15154): pool natif po-2026 -- livrer le superviseur qui tourne, et distinguer un echec de token transitoire d'un structurel #19641 (gate 26/26 vert, attend le merge). Le déploiement aux trois copies suit le merge (séquence dans le README po-2026) ; le miroir, lui, est déjà vivant côté WSL. - Bonus convoyé : fix(ci,Q17): pool po-2026 -- le confinement POOL_SIZE survit aux redemarrages (pool-size.conf) #19664 (persistance
pool-size.conf) sur la même pile, et le fichierpool-size.conf=3posé live — le confinement du 28/09 survivra désormais aux redémarrages (Q17).
Le maillon 3 tient sa promesse mesurée : le coût d'un
_workécarté (2 353 en 9 jours) ou d'un premier spawn tombe de « pack complet » à « dix secondes ».- PR feat(ci,#18225): pool po-2026 -- magasin d'objets partage (miroir + alternates + semis) #19657 (magasin d'objets :
- added a commit that references this issue
on Oct 9, 2026
Signature
Sur runner auto-hebergé (
self-hosted, coursia-ephemeral, coursia-linux), l'étapeactions/checkout@v4denotebook-math-render.ymlcourt jusqu'au plafond déclaré du job, puis le job est annulé — tous les pas suivants sautés :Trois occurrences sur la même tête de PR aujourd'hui :
10m07s,10m06s,10m00s— toujours à la seconde près au plafond, jamais en dessous.Ce que la mesure etablit, et ce qu'elle refute
Etabli. En regime normal, ce checkout prend 2 a 3 secondes : sur les 4 derniers runs completes de
notebook-math-render.yml, l'etape mesure2s, 2s, 3s, 2s(et2s, 2s, 2s, 3spournotebook-latex-control-chars.yml, meme classe de runner). Une duree de 600 s n'est donc pas une lenteur qui s'accumule : c'est un blocage qui ne rend jamais la main.Refute. L'hypothese «
fetch-depth: 0sansfilter: blob:nonefait payer le fetch complet » ne tient pas :notebook-math-render.ymlnotebook-latex-control-chars.ymlnotebook-kernel-drift-guard.ymlblob:nonecell-order-gate.ymlblob:noneLe filtre blesse (
blob:none) n'empeche pas un checkout de prendre 266 s, et son absence ne coute rien en regime normal. Un correctif « ajouterblob:none» serait donc justifie par une cause fausse — il n'est pas propose ici.Effet sur la flotte
Le job annule est rapporte par le
PR gatecomme un depassement de plafond, et bloque le merge :Comme
cancel-in-progress: truesurpull_request, chaque nouvel evenement (edited,synchronize) relance une jambe et rejoue les des. Deux PR demyia-po-2023:CoursIA(#18214, #18222) ont ete bloquees par ce seul rouge aujourd'hui — la PR etait verte partout ailleurs, y compris sur une jambemath-renderreussie a la meme tete.Ce qui n'est pas etabli
La cause du blocage. Ce n'est ni le volume a fetcher, ni un plafond trop court en regime normal. Deux pistes a discriminer, a la prochaine occurrence :
index.lockresiduel du workspace ephemere,git fetchen attente d'un verrou qui ne sera jamais libere.Le discriminant est le log complet de l'etape (aujourd'hui seul le
Set up jobet le resultat sont lisibles) : ungit fetchqui progresse lentement designe (1), ungitmuet depuis la premiere seconde designe (2).Gestes immediats, sans attendre le diagnostic
gh run rerun <run_id> --failed), pas le gate : un verdict de gate est fige sur les check-runs lus ([ci] Le pr-gate classe un depassement de timeout en "check qui n'a jamais conclu" et prescrit un rerun mecaniquement inoperant #15905).Marqueur recherche : la classe n'est pas propre a
math-render— tout job auto-heberge qui declare untimeout-minutespeut la produire. Toute instance (workflow, run, duree) est bienvenue en commentaire ici.