Skip to content

fix(runners,#15105): intégrité + maintenance bornée du cache _work persistant - #15273

Merged
jsboige merged 1 commit into
mainfrom
fix/runner-cache-integrity-15105
Sep 9, 2026
Merged

jsboige merged 1 commit into
mainfrom
fix/runner-cache-integrity-15105

Conversation

@jsboige

@jsboige jsboige commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Grain: MED/tooling -- lane myia-po-2026:CoursIA -- prev: MED/docs #15174

Sujet

actions/checkout pose gc.auto = 0 dans 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

Mesures (WSL git 2.43 = version de l'image ubuntu:24.04 de la flotte)

Question Mesure
Accumulation reproduite : 1 pack par fetch (1 → 11 en 10 fetches)
repack -ad sur clone au filtre réellement honoré (blobs absents) 5 packs promisor → 1, réseau nul pendant le repack, .promisor préservé sur le pack consolidé, lazy-fetch + fetch incrémental OK après
Refs de zéro octet for-each-ref rc=0 + warning stderr ; réparation par périmètre refs/logs ; fichiers vides de objects/ intacts
Dubious ownership (dépôt root, sonde user) rc=128, 0 refs sans stderr ; -c safe.directory='*' lit les refs

Tests (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 partiel blob:none fidè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 -e de l'entrypoint).
  • test_supervise_guards.sh : 40 PASS / 0 FAIL en WSL ET Git Bash. Test 20 nouveau : écart work_cache_health.sh entre checkout et image → refus PERIMEE avec fichier fautif nommé. Stub docker dispatche sur le chemin sondé (2 probes). sleep du 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

  • Un slot au cache corrompu est nommé par un organe : l'entrypoint journalise work_cache: N ref(s) cassée(s)… réparation / IRRECUPERABLE… purge avant tout enregistrement.
  • Le compte de packs d'un slot actif est borné, avec la mesure avant/après citée : seuil 16 par défaut, journal repack N -> M packs (seuil 16), mesure locale citée ci-dessus.
  • Le détecteur porte ses contrôles positifs (safe.directory, stderr lu, .promisor préservé) — validé par ses faux négatifs (tests 1, 3 et la sonde ownership).

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

docker build -t coursia-linux-runner:2.337.0 scripts/ci/docker/linux-runner/

Le knob est armé par défaut (16) ; COURSIA_RUNNER_CACHE_PACK_THRESHOLD=0 le désactive par machine.

Closes #15105

…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 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.

[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.sh passée intégralement chez moi (git réel, fixtures --no-local + uploadpack.allowFilter pour un clone genuiment partiel — le mécanisme d'accumulation promisor de la flotte est réellement reproduit) : repack 4→1 packs mesuré, marqueur .promisor pré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_workdir rendant 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_git portant -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 de for-each-ref (rc=0 mesuré sur git 2.43 — seul le warning nomme la ref) ; (3) .promisor vide est légitime → la réparation touche .git/refs et .git/logs uniquement, jamais objects/ où un find -empty -delete naï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 -f plutôt que && (ne tue pas le conteneur sous set -e avant 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.sh source le script et appelle wch_check_workdir "$ACTIONS_RUNNER_INPUT_WORK" "${RUNNER_WORK_CACHE_PACK_THRESHOLD:-16}" avant cd /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)

  1. Le glob "$workdir"/*/*/.git ne 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.
  2. safe.directory='*' désactive la protection d'ownership pour tout dépôt touché par wch_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.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #15273 (fix(runners,#15105): intégrité + maintenance bornée du cache _work persistant) touche au moins un chemin de fichier aussi modifie par d'autres PRs ouvertes. Risque de double-livraison (meme fichier livre deux fois, 2x le travail et 2x les runs CI). Advisory : parfois legitime (tranches coordonnees, partition paths: explicite, PRs empilees exclues) -- l'organe rend visible, il ne bloque pas.

@github-actions

github-actions Bot commented Sep 9, 2026

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.

@jsboige
jsboige merged commit abc45ba into main Sep 9, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cache _work persistant : ni maintenance des packs, ni precondition d'integrite (264 packs, 1471 refs vides)

2 participants