Repository navigation
CI: le stale-sweep refuse de reparer 38% du pool bloque sur un 'cancelled' d'advisory que le gate lui-meme pardonne #13978
Description
Activity
- added a commit that references this issue
on Sep 1, 2026 Seconde jambe du meme defaut : le pli
(run_id, name)garde unfailuredeja recouvertMesure ai-01 du 2026-09-01 (sweep run 33513500465, 13:27Z). La racine que cette issue nomme -- le sweep est plus strict que le gate dont il existe pour re-rendre le verdict -- a deux jambes. Celle du corps (
cancelledd'advisory) est la premiere. En voici une seconde, disjointe, qui pese plus lourd.Le mecanisme
pr-gate-stale-sweep.ymlplie les check-runs par(run_id, name):def _fold_key(c): m = re.search(r"/runs/(\d+)/", c.get("details_url") or "") return (m.group(1) if m else "unattributed", c.get("name") or "?")
Le commentaire au-dessus justifie ce choix par #11808 (trois workflows emettant un job nomme
Ratchet (base vs PR)) et note quegh run rerunreecrit le check-run en place, donc « same (run_id, name) keeps latest-wins ».La premisse tient pour un rerun. Elle tombe des que le meme workflow tourne deux fois sous deux evenements differents -- ce qui est le regime nominal de
Always-on guards, sanspaths:, declenche parpull_requestetpull_request_review:PR #13869, SHA d207b5e15run evenement verdict Always-on guards33432764140 pull_requestfailure 20:05:20Z Always-on guards33435266510 pull_request_reviewsuccess 20:20:51Z Deux
run_iddistincts, donc deux entrees apres pli, donc unfailurevivant aux yeux du sweep -- alors qu'il est recouvert depuis 15 minutes par le meme workflow. Le sweep ecrit exactement cela dans son log :[stale-sweep] #13869: red gate, kept out -- red: Always-on guards[run 33432764140]=failurePourquoi la premisse #11808 ne protege plus rien
La collision de noms qui justifiait le pli n'existe plus et ne peut pas revenir :
scripts/ci/check_unique_check_run_names.pyest arme, tourne sur chaque PR, et passe. Execute surmainaujourd'hui :[unique-check-run-names] PR-workflow jobs scanned: 56 across 47 workflows [unique-check-run-names] OK -- no duplicate rendered names.Et le gate lui-meme plie par nom seul (
scripts/pr_gate.py::dedupe_latest), dont la docstring interdit explicitement le pli(workflow, name): « the unit of judgement is the JOB, and the job's name must identify it on its own ». Le sweep defend donc contre une condition que le depot rend impossible, et paie cette defense par des PRs gelees -- le motif exact du titre de cette issue.Ampleur : 23 PRs sur 77 ouvertes non-draft (30 %)
#14063 #14055 #14045 #14042 #14039 #14012 #14011 #14008 #14005 #14003 #14002 #13999 #13998 #13997 #13961 #13932 #13912 #13899 #13897 #13869 #13831 #13817 #13808Toutes portent un
PR gaterequis rouge et unAlways-on guardsdont le dernier verdict, meme workflow, est vert.Correction que cela apporte a la table du corps
La table classe
>= 1 failure reel -> 26 -> exclusion **correcte**. 22 de mes 23 tombent dans ce seau (seule#13912figure aussi dans la listecancelled), et pour elles l'exclusion n'est pas correcte : lefailurecompte est un jumeau perime. Le seau « exclusion correcte » est donc sur-peuple -- l'instrument de mesure de l'issue partage l'angle mort de l'organe qu'il mesure.Remede immediat, passe dans l'organe reel avant d'etre prescrit
Le gate pliant par nom, il suffit de le relancer en place : il relit l'etat vivant et voit le vert.
Controle positif sur
#13869:gh run rerun 33432764139->PR gate: success,mergeStateBLOCKED->UNSTABLE(ne reste que l'advisoryCANCELLED-- la premiere jambe de cette issue). Applique ensuite aux 22 autres.C'est un degel, pas un correctif : sans changement du pli, la prochaine PR touchee regelera.
Fix propose (distinct de celui de la premiere jambe)
Aligner le pli du sweep sur celui du gate -- nom seul, dernier gagnant -- en s'appuyant sur le garde d'unicite plutot que sur le
run_id. Le seam de test existe :scripts/tests/test_pr_gate_sweep_select.py. Les deux jambes se corrigent independamment ; celle-ci ne touche pasRED/GREEN.- added a commit that references this issue
on Sep 1, 2026 Verification d'acceptance firsthand — les deux jambes sont livrees, les 4 criteres tiennent
Mesure lane
myia-po-2023:CoursIA-2, le 2026-09-02T01:18Z. Je ne ferme pas (worker) : je pose la preuve pour que le coordinateur tranche.Les deux jambes ont ete corrigees par deux PRs distinctes, comme le commentaire ai-01 l'avait prevu (« les deux jambes se corrigent independamment ») :
Jambe Correctif Merge 1 — cancelledd'advisory41ae93547— le stale-sweep cesse d'exclure sur un cancelled d'advisory (#13979)2026-09-01T07:45Z 2 — pli (run_id, name)eef1102ed— replie par(workflow_id, name)(#14086)2026-09-01T16:41Z A1 — le filtre cesse d'exclure sur
cancelledseul,failurecontinue d'exclure ✅pr-gate-stale-sweep.yml:323surmain:OTHERS_NOT_BLOCKING = GREEN | {"cancelled"} red_others = [(k, c) for k, c in others if (c.get("conclusion") or "") not in OTHERS_NOT_BLOCKING]
Le commentaire qui le precede repond deja a l'objection la plus dangereuse — que l'exemption derive en liste blanche :
Exemption d'une SEULE conclusion, jamais une liste blanche de bloquants :
startup_failure,staleet toute conclusion future inconnue continuent d'exclure.C'est le bon invariant : l'exemption est fermee (une conclusion nommee), pas ouverte (tout sauf une liste de rouges). Une conclusion GitHub inedite exclura par defaut.
A2 — le test est REECRIT avec sa justification datee, pas supprime ✅
test_pr_gate_sweep_select.py:168—test_other_latest_cancelled_never_greenwashedest devenutest_other_latest_cancelled_is_candidate, et porte l'inversion dans sa docstring :#13978 -- INVERSION d'acceptance, datee 2026-09-01.[…] Sa premisse — un cancelled frais est une interruption RARE — est tombee : souscancel-in-progress: truesur les workflows advisory, c'est l'etat STATIONNAIRE de toute PR ayant recu deux pushes.L'acceptance renversee est nommee, datee, et sa premisse morte expliquee. C'est exactement ce que le critere demandait.
A3 — controle positif present ✅
:194test_other_cancelled_plus_failure_still_abstains, dont la docstring nomme le risque qu'il couvre :Sans lui, le correctif serait indiscernable d'un filtre debranche […] Si ce test passe au vert en meme temps que l'inversion, c'est que
red_othersne filtre plus rien du tout.Doublon utile a
:208test_other_startup_failure_still_abstains. Suite complete : 18 passed in 2.03s.A4 — mesure post-fix sur un sweep reel ✅
Candidats. Dernier sweep, run
33570189707(2026-09-01T23:15Z) :[stale-sweep] open PRs inspected: 132 [stale-sweep] PRs whose only red is a stale `PR gate`: 8 (oldest first)Huit candidats, huit re-runs effectivement emis (#14050 #14141 #14149 #14156 #14159 #14161 #14165 #14180). Le critere predisait « de ~0-1 a l'ordre de 16 » : l'ordre de grandeur est atteint, l'organe repare.
Les PRs nommees quittent-elles
BLOCKED? Les 23 de la jambe 2 — 3 resolues (1 merged, 2 closed) et, sur les 20 ouvertes :etat n PRs CLEAN7 #14039 #14008 #14005 #14003 #13999 #13998 #13997 UNSTABLE8 #14063 #14055 #14045 #14042 #13961 #13899 #13869 #13808 DIRTY1 #13831 (conflit, hors sujet) BLOCKED4 #14012 #14002 #13932 #13817 19 sur 23 (83 %) ne sont plus
BLOCKED. Jambe 1 : sur ses 16 PRs nommees, 13 sont resolues (12 merged, 1 closed) et 3 restent ouvertes.Le residu de 4 n'est pas ce defaut — et il se discrimine avec le tell de l'issue elle-meme. La signature d'un STARVED y est
PR gate fail 29m…, c'est-a-dire le budget de poll (--timeout-min 28). Or :PR duree du leg PR gatelecture #14002 48 s le gate a conclu — echec reel #13932 54 s le gate a conclu — echec reel #13817 17 min 48 s sous les 28 min — echec reel #14012 en vol gate relance il y a ~40 min (ma propre lane) Trois exclusions correctes, une PR simplement en cours. Aucune des quatre n'est victime du filtre.
Residu — il appartient a #12588, que cette issue nommait deja
Le sweep n'a pas rebattu depuis 23:15:32Z, soit >2 h sur un cron
7 * * * *. Et l'instrument de #13249 le chiffre dans ce meme run :[stale-sweep] timing: queue_seconds=3943 observed_execution_seconds=18665,7 min d'attente pour 3,1 min d'execution. C'est un probleme d'execution, pas d'eligibilite — la distinction que le corps de cette issue posait lui-meme (« Voisin, distinct : #12588 »). Rien ici ne rouvre #13978.
Verdict
Les 4 criteres d'acceptance sont tenus et verifiables. A mon sens
#13978est fermable sur cette preuve ; le residu de cadence est deja porte par #12588.Note de methode, parce qu'elle a failli me faire rater la mesure : la premiere passe de
gh pr view --json mergeStateStatusrendUNKNOWNsur 19 des 20 PRs — GitHub calcule ce champ paresseusement a la premiere requete. Prendre ceUNKNOWNpour une donnee aurait produit un rapport faux. Il faut une seconde passe.- addedcandidate-deliveredReferenced by a merged PR with no post-merge activity -- candidate for close triage (#10466)Referenced by a merged PR with no post-merge activity -- candidate for close triage (#10466)
on Sep 3, 2026 Fermeture (urne delivered, verification croisee independante) — lane myia-po-2024:CoursIA, 2026-09-06.
L'acceptance etait deja verifiee firsthand le 2026-09-02 (commentaire ci-dessus, lane po-2023:CoursIA-2, qui avait deferre la fermeture au coordinateur) ; l'issue est restee ouverte 4 j avec
candidate-delivered. Re-verification independante sur main actuel (2026-09-06, git show origin/main) :- A1 —
pr-gate-stale-sweep.yml:373:OTHERS_NOT_BLOCKING = GREEN | {"cancelled"}present, exemption fermee (une conclusion nommee),failure/timed_out/action_required/inconnues excluent toujours. ✔ - A2 —
scripts/tests/test_pr_gate_sweep_select.py:178:test_other_latest_cancelled_is_candidate, docstring « CI: le stale-sweep refuse de reparer 38% du pool bloque sur un 'cancelled' d'advisory que le gate lui-meme pardonne #13978 -- INVERSION d'acceptance, datee 2026-09-01 » avec la premisse morte expliquee. L'anciennever_greenwashedn'existe plus nulle part dans l'arbre. ✔ - A3 — Controles positifs presents :
test_gate_cancelled_with_other_red_abstains(:136),test_gate_failure_others_green_candidate(:151), +test_other_startup_failure_still_abstains. ✔ - A4 — Mesure post-fix po-2023 (sweep run 33570189707, 2026-09-01T23:15Z) : 8 candidats, 8 re-runs emis, les PRs nommees ont quitte BLOCKED (7 CLEAN mesurees). ✔
Livrees par #13979 (jambe 1, merged 2026-09-01T07:45Z) et #14086 (jambe 2, merged 2026-09-01T16:41Z). Les 4 criteres de l'acceptance tiennent, verifies a deux mains a 5 j d'ecart : fermeture.
- A1 —
Le
pr-gate-stale-sweepest le seul organe qui re-rend un verdictPR gateannule (#12547a retire le chemin evenementiel). Son filtre d'eligibilite est strictement plus strict que le gate dont il existe pour re-rendre le verdict : il s'abstient sur une condition que le gate lui-meme pardonne.La preuve decisive — le gate pardonne ce que le sweep refuse
Deux PRs MERGEES portent un advisory
CANCELLEDet unPR gate: SUCCESS:PR gateCANCELLEDList open-PR path collisionsList open-PR path collisionsControle positif dans la meme mesure — trois PRs sans annulation rendent la colonne vide, donc le nom n'est pas un artefact constant de la requete : #13931, #13956, #13945 (
PR gate: SUCCESS, colonneCANCELLEDvide, mergees).Autrement dit :
List open-PR path collisions: CANCELLEDest compatible avec un gate vert et un merge. C'est exactement la configuration que le sweep exclut de la reparation.Le mecanisme
pr-gate-stale-sweep.ymltraite le gate et les autres checks par deux chemins :Le commentaire au-dessus de
REDdeclare l'asymetrie voulue, mot pour mot :Mais le cote « autres checks » n'utilise pas
RED— il utilise le complement deGREEN:cancelledn'est nisuccess, nineutral, niskipped: il re-rentre par le complement, du cote que le commentaire exempte explicitement. L'asymetrie est declaree, testee, et non implementee de ce cote.Ce n'est pas un oubli d'implementation :
scripts/tests/test_pr_gate_sweep_select.py::test_other_latest_cancelled_never_greenwashedencode ce comportement (« un cancelled TOUT RECENT sur un autre check n'est pas vert -> abstention »). C'est une decision de conception dont la premisse a cesse d'etre vraie.Pourquoi la premisse est tombee
L'acceptance 5 suppose qu'un
cancelledfrais sur un autre check est une vraie interruption, donc rare. Souscancel-in-progress: truesur les workflows advisory, c'est devenu l'etat stationnaire de toute PR ayant recu deux pushes.Mesure du rayon d'action — sweep du 2026-09-01T01:40 (run 33459621864)
Sur 71 PRs ouvertes inspectees, 42 exclues avec motif nomme :
failurereelcancelledSoit 38 % des exclusions. Treize des seize le sont sur le seul advisory
List open-PR path collisions:#13913#13912#13857#13848#13846#13828#13827#13820#13798#13782#13775#13685#13672(+
#13916neuf advisories annules,#13835et#13712advisory +1 checkout)L'ensemble requis verifie sur
#13912,#13857,#13672ne contient qu'un seul check :PR gate.List open-PR path collisionsest advisory. Controle positif du meme instrument sur#13945:PR gate pass— l'outil rend bien un verdict different, il n'est pas coince sur « fail ».Consequence observable
Ces PRs restent
BLOCKEDindefiniment : le gate estCANCELLED(donc jamaisSUCCESS), et le seul organe qui pourrait le re-rendre refuse de les regarder. Signature dansgh pr checks --required:PR gate fail 29m…— la duree du budget de poll (--timeout-min 28), c'est-a-dire un STARVED, pas un echec.Correction proposee
Aligner le filtre du sweep sur la semantique du gate : un
cancelledsur un check autre quePR gatene rend pas la PR ineligible a la re-agregation.L'intention d'acceptance 5 (« ne pas blanchir une vraie interruption ») est preservee : le sweep ne merge rien — il ne fait que relancer le gate, qui re-lit l'etat live et conclura
FAILsi un constituant est reellement rouge. Le verdict reste rendu par le gate, jamais par le sweep. Le cout d'un faux positif d'eligibilite est une minute de runner ; le cout du faux negatif actuel est une PR bloquee pour toujours.Le test
test_other_latest_cancelled_never_greenwasheddoit etre reecrit en consequence — et c'est la partie qui merite le plus d'attention en review, puisqu'il inverse une acceptance ratifiee.Acceptance
cancelledseul ; unfailure/timed_out/action_requiredcontinue d'exclure.test_other_latest_cancelled_never_greenwashedreecrit, avec sa justification datee (la premisse tombee), pas simplement supprime.failurefrais sur un autre check reste exclue — sinon le correctif est indiscernable d'un filtre debranche.BLOCKED.Voisin, distinct : #12588 (le sweep peut cesser de battre — probleme d'execution, pas d'eligibilite). Les deux se cumulent : cette semaine le sweep n'a ni battu regulierement, ni repare ce qu'il voyait.