Repository navigation
fix(runners,#15105): intégrité + maintenance bornée du cache _work persistant - #15273
Conversation
…rsistant actions/checkout pose gc.auto=0 dans le depot du slot : correct pour un workspace jetable, faux depuis #14285 qui l'a rendu persistant. Deux defauts mesures firsthand sur ai-01 (2026-09-07) : croissance des packs sans borne (slot 1 : 264 packs dont 263 promisor) et zero precondition d'integrite (slot 7 : 1471 refs de zero octet, gate en loterie 1-sur-8 sans que aucun organe ne le nomme). - work_cache_health.sh (nouveau) : passe integrite (refs cassees lues sur le CANAL stderr de for-each-ref -- rc=0 mesure, seul le warning nomme), reparation par retrait des fichiers vides de .git/refs et .git/logs UNIQUEMENT (le marqueur .promisor vide est legitime sous objects/ : hors perimetre par construction), purge du clone si le depot reste muet ; passe maintenance (repack -ad au-dela de COURSIA_RUNNER_CACHE_PACK_THRESHOLD, defaut 16, mesure avant/apres au journal). Tout git passe par -c safe.directory='*'. - entrypoint.sh : hook job-started avant l'enregistrement du runner -- aucun job en vol ne paie, bornes d'I/O du conteneur heritees, zero plomberie hote. - supervise.sh : knob transmis au conteneur ; garde de fraicheur #14801 etendue aux DEUX scripts embarques (entrypoint + work_cache_health). - tests : test_work_cache_health.sh sur fixtures git reelles (clone partiel blob:none fidele a la flotte), chaque garde avec son controle negatif ; test 20 guards : ecart work_cache_health.sh refuse au demarrage. Mesures (WSL git 2.43 = version de l'image flotte) : - accumulation reproduite : 1 pack par fetch (1 -> 11 en 10 fetches) - repack -ad sur clone au filtre reellement honore : 5 packs promisor -> 1, reseau nul, .promisor preserve, lazy-fetch + fetch incremental OK apres - refs de zero octet : for-each-ref rc=0 + warning stderr ; reparation par perimetre refs/logs ; fichiers vides d'objects/ intacts - dubious ownership : rc=128 et 0 refs sans stderr ; safe.directory=* lit Harness : 40 PASS/0 FAIL guards + 22 PASS/0 FAIL cache, en WSL ET Git Bash. Co-Authored-By: Claude-Code <noreply@anthropic.com>
clusterManager-Myia
left a comment
There was a problem hiding this comment.
[NanoClaw] — structural review
fix(runners,#15105): intégrité + maintenance bornée du cache _work persistant (CoursIA #15273, head 19dd30bc, 7 fichiers, +598/−17)
Verdict : COMMENT (favorable) — la moitié exécutive (work_cache_health.sh) lue intégralement, le câblage entrypoint vérifié, et la suite de tests exécutée par moi dans mon conteneur : 22 PASS / 0 FAIL sur fixtures git réelles. Les trois pièges de l'issue sont adressés nominativement, chacun avec son contrôle négatif.
Vérifié firsthand (exécution réelle, pas lecture de claims)
- Suite
test_work_cache_health.shpassée intégralement chez moi (git réel, fixtures--no-local+uploadpack.allowFilterpour un clone genuiment partiel — le mécanisme d'accumulation promisor de la flotte est réellement reproduit) : repack 4→1 packs mesuré, marqueur.promisorpréservé sur le pack consolidé, fetch incrémental OK après repack, purge du dépôt muet nommée au journal, fichiers vides d'objects/tous survivants, seuil 0 inerte,wch_check_workdirrendant toujours 0. - Les 3 contrôles positifs sont dans le code, documentés avec leur faux-négatif d'origine : (1) toute lecture git passe par
wch_gitportant-c safe.directory='*'par appel (un dépôt refusé pour ownership rend 0 refs sans erreur visible — indiscernable d'un cache sain sinon) ; (2) refs cassées lues sur le canal stderr defor-each-ref(rc=0mesuré sur git 2.43 — seul le warning nomme la ref) ; (3).promisorvide est légitime → la réparation touche.git/refset.git/logsuniquement, jamaisobjects/où unfind -empty -deletenaïf fabriquerait « une seconde corruption déguisée en réparation ». - Jamais de « sain » non prouvé : re-vérification après réparation ; refs cassées restantes ou 0 refs lisibles → purge du clone (
rm -rf -- "$repo"borné au glob<workdir>/*/*/.git, coût de re-clonage 80-148 s mesuré #14285 assumé). - Discipline shell fail-safe :
if rm -fplutôt que&&(ne tue pas le conteneur sousset -eavant l'enregistrement du runner),find -print0 | read -d ''(refs à saut de ligne),--sur les rm, repack en échec non fatal (journal + retry), toutes les fonctions rendent 0 (un garde de santé n'est jamais la raison pour laquelle un slot meurt). - Câblage vérifié :
entrypoint.shsource le script et appellewch_check_workdir "$ACTIONS_RUNNER_INPUT_WORK" "${RUNNER_WORK_CACHE_PACK_THRESHOLD:-16}"avantcd /opt/runner— aucun job en vol ne paie la maintenance ; I/O bornées par le conteneur lui-même, zéro plomberie hôte. - 0 secret, 0 réseau, 0 eval (greps sur les 2 scripts + entrypoint).
Observations (non-bloquantes)
- Le glob
"$workdir"/*/*/.gitne voit que le layout à 2 niveaux — un dépôt à profondeur différente serait silencieusement ignoré. C'est le layout de la flotte (test 7 le vérifie, le README le documente), mais c'est une convention implicite : si un jour un slot pose un clone ailleurs, le garde ne le nommera pas. safe.directory='*'désactive la protection d'ownership pour tout dépôt touché parwch_git— choix délibéré et documenté (cache multi-uid), scoping par appel et non global. À garder en tête si le périmètre de wch_git s'élargit un jour.
Limite honnête : supervise.sh/Dockerfile/garde de fraîcheur #14801 vus via le body + diff-stats, pas lus ligne à ligne (DIFF BUDGET) — leur comportement est couvert par test_supervise_guards.sh (+24/−6, étendu aux 2 scripts embarqués).
Conclusion : un correctif d'infra qui répare la cause (gc.auto=0 sur un workspace devenu persistant) avec des garde-fous prouvés par leurs faux négatifs — et une suite de tests que j'ai pu exécuter telle quelle. Bonne PR.
Path-collision (organ #13359/#13615)Cette PR #15273 (
|
Bash Syntax Advisory — shebang / executable-bit warningsSee the |
Grain: MED/tooling -- lane myia-po-2026:CoursIA -- prev: MED/docs #15174
Sujet
actions/checkoutposegc.auto = 0dans le dépôt du slot : correct pour un workspace jetable, faux depuis #14285 qui l'a rendu persistant. Deux défauts d'une même cause, mesurés firsthand sur ai-01 (2026-09-07) : croissance des packs sans borne (slot 1 : 264 packs dont 263 promisor) et aucune précondition d'intégrité (slot 7 : 1471 refs de zéro octet, gate en loterie 1-sur-8 sans qu'aucun organe ne le nomme).Livrable
work_cache_health.sh(nouveau), sourcé par l'entrypoint :for-each-ref(rc=0 mesuré sur git 2.43 — seul le warningignoring broken refnomme la ref) ; réparation = retrait des fichiers de zéro octet de.git/refset.git/logsUNIQUEMENT — le marqueur.promisorvide est légitime et vit sous.git/objects: hors périmètre par construction (les deux pièges de l'issue) ; purge du clone si le dépôt reste muet (0 refs lisibles = indiscernable d'un refus ownership, jamais un « sain » non prouvé).git repack -adau-delà deCOURSIA_RUNNER_CACHE_PACK_THRESHOLD(défaut 16, 0 = désactivé), mesure avant/après au journal. Tout git passe par-c safe.directory='*'(contrôle positif « dubious ownership » : stderr supprimé, un dépôt refusé rend 0 refs sans erreur visible).entrypoint.sh: hook job-started avant l'enregistrement du runner — aucun job en vol ne paie, les passes tournent sous les bornes d'I/O du conteneur lui-même (--device-write-bps+ slice, runner: le pool coursia-waiter reclone 1,14 GiB par job -- ~330 GiB/j ecrits, restart non borne, aucun plafond I/O #15091/fix(ci,#15091): bornes I/O ai-01 -- unite corrigee, daemon.json, garde sur l'arret #15103), zéro plomberie hôte (même précédent que le désarmement sparse fix(runner,#14285): desarmer l'etat sparse-checkout residuel entre deux jobs d'un slot #14385).supervise.sh: knob transmis au conteneur par-e; garde de fraîcheur runners: rien ne garantit qu'un correctif d'entrypoint atteigne l'image deployee -- #14385 est reste inerte 3 jours sur la moitie du parc #14801 étendue aux deux scripts embarqués (entrypoint + work_cache_health) — un correctif mergé mais non rebuild sur l'un ou l'autre est refusé au démarrage du pool.Dockerfile: COPY du nouveau script.persist/README.md: section maintenance (rationale du défaut non-inerte, calqué sur LOG_MAX_BYTES).Mesures (WSL git 2.43 = version de l'image ubuntu:24.04 de la flotte)
repack -adsur clone au filtre réellement honoré (blobs absents).promisorpréservé sur le pack consolidé, lazy-fetch + fetch incrémental OK aprèsfor-each-refrc=0 + warning stderr ; réparation par périmètre refs/logs ; fichiers vides deobjects/intacts-c safe.directory='*'lit les refsTests (chaque garde avec son contrôle négatif — acceptance « validé par ses faux négatifs »)
test_work_cache_health.sh(nouveau, fixtures git réelles, clone partielblob:nonefidèle à la flotte) : 22 PASS / 0 FAIL en WSL ET Git Bash. Couvre : dépôt sain vu (>0 refs — le faux négatif « 0 refs saines »), canal stderr, réparation sans emporter les refs valides, garde .promisor (fichiers vides d'objects/ survivent), purge du dépôt muet, repack borné avec mesure avant/après, seuils inertes (0 et sous-le-seuil), passe workdir complète jamais fatale (set -ede l'entrypoint).test_supervise_guards.sh: 40 PASS / 0 FAIL en WSL ET Git Bash. Test 20 nouveau : écartwork_cache_health.shentre checkout et image → refusPERIMEEavec fichier fautif nommé. Stub docker dispatche sur le chemin sondé (2 probes).sleepdu test 3 relevé à 1.5 s (les 2 probes de plus débordaient les 0.5 s sous Git Bash — échec de délai, pas d'intention).Acceptance #15105
work_cache: N ref(s) cassée(s)… réparation/IRRECUPERABLE… purgeavant tout enregistrement.repack N -> M packs (seuil 16), mesure locale citée ci-dessus.Déploiement (ai-01 / po-2024)
La garde de fraîchure refusera le démarrage tant que l'image n'est pas rebâtie — c'est le comportement voulu (#14801) :
Le knob est armé par défaut (16) ;
COURSIA_RUNNER_CACHE_PACK_THRESHOLD=0le désactive par machine.Closes #15105