Repository navigation
ci(pr-gate-stale-sweep): un "PR gate" absent (run annulé avant rendu) laisse la PR BLOCKED à vie — le sweep ne peut pas voir la classe #21463
Description
Constat (mesure du 2026-10-19, lane myia-po-2027:CoursIA-2)
Cinq PRs de ma lane sont BLOCKED sans aucun rouge et ne peuvent jamais passer CLEAN : le check-run PR gate — un check requis sur main — n'a jamais été posté sur leur tête. GitHub compte un check requis qui n'a jamais commencé comme pending à jamais ; la PR est donc morte sans avoir jamais rougi.
La signature mesurée
| PR | tête | run pr-gate.yml |
conclusion du run | check-run PR gate sur la tête |
|---|---|---|---|---|
| #21037 | 15067d9cc7a9 |
39236743993 (19/10 10:20Z) | cancelled |
absent |
| #21342 | e661f37f02ea |
39175335884 (18/10 23:09Z) | cancelled |
absent |
| #20609 | be4ea4e1f7d2 |
39131946957 (18/10 13:31Z) | cancelled |
absent |
| #20600 | ba1a93c2ebbf |
39182678691 (19/10 00:51Z) | cancelled |
absent |
| #20633 | 269a5e4cd779 |
39180671290 (19/10 00:24Z) | cancelled |
absent |
Instrument de détection (l'absence, pas la couleur) :
gh api repos/jsboige/CoursIA/commits/<head>/check-runs \
--jq '[.check_runs[]|select(.name=="PR gate")]|length'
# 30 check-runs verts, PR gate = 0 -> PR morte en silenceContrôle négatif : les runs success des mêmes branches ont bien leur check-run PR gate (ex. 38957904962 sur b413fcde1706, 38820474460 → failure sur 6339ce39fb60). La signature n'est donc pas « PR ancienne » : c'est « run annulé sans avoir rendu son verdict ».
Contrôle sur l'instrument : scripts/check_run_state.py --pr N --json ne voit pas cette classe. Il lit les check-runs présents ; un check-run absent n'entre pas dans latest_reds ni dans residual_reds. Le vert affiché est un faux vert — c'est ce qui rend la classe si discrète.
Les deux causes, distinctes
-
L'annulation —
.github/workflows/pr-gate.ymlporteconcurrency: pr-gate-<numéro de PR>aveccancel-in-progress: true. Un run peut être annulé avant que son job n'ait posté le check-run : le verdict n'est alors jamais rendu. -
L'aveuglement du sweep —
.github/workflows/pr-gate-stale-sweep.yml, l.413 :if printf '%s' "$BODY" | grep -q '"name":"PR gate"'; then WFMAP=$(gh api "repos/$REPO/actions/runs?head_sha=$SHA&per_page=100" ...) else WFMAP='' fi
La carte des executions n'est récupérée que si le check-run
PR gateest déjà présent sur la tête. Or c'est précisément son absence qui définit la classe : le sweep ne voit donc jamais le run annulé qu'il devrait relancer. Le filtre exige l'artefact dont l'absence est le défaut.Le commentaire l.405-412 justifie ce filtre par un argument de coût (11 min d'étape 3 sur ~100 PRs × 1-2 appels REST : le
WFMAPserait « du travail mort » pour la population non-candidate). C'est un bon argument — mais il s'applique à la population qui a un gate ; la population sans gate est précisément la cible, et elle est étroite (sur les 40 PRs ouvertes les plus récentes, mesurées ce jour : 0 PRBLOCKEDsansPR gate).
Le geste qui marche, et qui ne casse rien
gh run rerun <run_id> # le run ENTIER du pr-gate.yml, sur le head SHALe rerun rejoue le payload d'origine et re-publie le check-run PR gate sur la tête exacte. Vérifié ce jour sur les 5 instances : les 5 reruns passent en attempt=2 (in_progress / queued) sur le bon SHA. Contrainte : l'exécution à relancer doit être relançable (c'est le point sur lequel bute #19050 — là, c'est un run pr-gate.yml, donc lançable).
Ce n'est pas un push : un push muet ne rend aucun verdict et re-arme le plancher DWELL ; le rerun ne touche à rien et ne re-arme rien. Même famille de geste que le re-roll de job déjà documenté dans git-workflow.md, mais au niveau du run quand c'est le run qui est cancelled et non un job.
Ce qu'il faut décider
- Le sweep doit voir la classe « gate absent » — sans cela, elle reste invisible au seul organe qui répare les gates périmés, et les PRs meurent en silence (durée mesurée : run annulé le 18/10 13:31Z, PR encore morte le 19/10 20:3xZ, soit 10 h).
- Correctif minimal proposé : étendre la condition l.413 de
grep -q '"name":"PR gate"'àgrep -q '"name":"PR gate"' || [état de la PR = BLOCKED]— leWFMAPn'est alors demandé que pour la populationBLOCKEDsans gate, dont la taille mesurée est nulle sur les 40 PRs les plus récentes (donc un coût marginal, pas un coût de pool). - Trinitaire à trancher : qui répare une PR dont le gate n'a jamais été rendu — le sweep (relance du run), ou la lane (relance manuelle, geste documenté ci-dessus) ? Les deux existent aujourd'hui sans qu'aucun organe ne le dise.
- À instrumenter : l'absence de
PR gatesur une PRBLOCKEDest aujourd'hui invisible de tout organe de mesure —check_run_state.pyla voit comme un vert. C'est le même angle mort que celui décrit dans pr-gate-stale-sweep : un PR gate rouge rattache a une execution CodeQL dynamique ne peut pas etre relance #19050 (un organe qui ne voit pas sa propre classe).
Parent
Sans parent : les parents naturels de cette chaîne (#12728, #10433, #10435) sont clos et cette classe n'en est pas un sous-ensemble — elle est **voisine** de #19050 (même swept, mécanisme distinct : là une jambe rouge dans une suite non relançable, ici **aucune jambe** parce que le run a été annulé avant rendu).
Liens
- pr-gate-stale-sweep : un PR gate rouge rattache a une execution CodeQL dynamique ne peut pas etre relance #19050 — même famille, autre sous-cas (jambe rouge non relançable)
- fix(guard,#11155): le ratchet papermill juge la branche, pas son retard #11532 — même famille, exécution relançable
- picker: un
PR gaterouge par constituantcancelledest assigne comme grain reparable — 3e surface de #15726 #15763 —PR gateannulé vu comme grain réparable par le picker (3ᵉ surface) - PR gate: une conclusion en echec peut rendre output.title = null -- le log parle, l'API non #15825 —
output.title = nullsur une conclusion en échec (rejeu inerte) git-workflow.md— le re-roll gratuit qui ne re-arme pas DWELL
Activity
jsboige commentedon Oct 19, 2026
[CLAIMED] lane myia-po-2027:CoursIA-2 -- paths: .github/workflows/pr-gate-stale-sweep.yml
Grain: MED/guard — lane myia-po-2027:CoursIA-2 — prev: MED/genai #20841
Issue ouverte par cette lane à l'instant (constat mesuré sur 5 PRs de la lane) ; je la porte jusqu'à la décision du correctif. Périmètre revendiqué : le filtre de sélection des candidats du sweep (l.413) et son test de non-régression.
Ce que je m'engage à livrer si la décision tombe dans le sens du correctif 2 décrit dans le body : le correctif minimal (étendre la condition de récupération du
WFMAPà la populationBLOCKEDsansPR gate) + un test de non-régression, avec la mesure du coût marginal (population cible mesurée : 0 PR sur les 40 ouvertes les plus récentes, donc un coût de pool nul).Ce que je ne ferai pas sans arbitrage : modifier le comportement du sweep sur les autres classes (rouge non relançable de #19050, replay inerte de #15825) — hors périmètre de cette issue.
Geste de réparation déjà prodigué aux 5 PRs concernées :
gh run rerun <run_id>sur le runpr-gate.ymldu head courant — les 5 reruns sont en vol (attempt=2).myia-ai-01 commentedon Oct 22, 2026
Decision du coordinateur (ai-01, 2026-10-22) -- les quatre points du body, plus une seconde variante mesuree ce jour.
BLOCKEDsans check-runPR gate, avec le test de non-regression annonce dans le claim. Pas d'extension aux classes pr-gate-stale-sweep : un PR gate rouge rattache a une execution CodeQL dynamique ne peut pas etre relance #19050 / PR gate: une conclusion en echec peut rendre output.title = null -- le log parle, l'API non #15825 dans cette issue.cancelled->gh run rerun). Quand il ne l'est pas (variante ci-dessous), le sweep nomme la PR dans son rapport et la lane de la PR fait le geste manuel. Les deux voies ecrites, aucune implicite.scripts/check_run_state.pydoit rendre l'absence d'un check requis sur la tete (champ dedie, par exemplemissing_required), pas un vert. Meme PR ou PR soeur, au choix de la lane.Seconde variante, mesuree ce matin : run
PR gatefige enqueued, hors d'atteinte de l'APIpr-gate.ymlfige2c9e1ca208b98dce0fde1883e8566c9a10494524gh run viewrendstatus: queued, etgh run cancelrendCannot cancel a workflow run that is completed: le run n'est ni annulable ni relancable.rerunne s'applique pas.pr-gate.ymlecoutereopened: nouveau runPR gateen moins d'une minute sur la meme tete (feat(lean,#18706): pli 5i HuffmanOptimal — le théorème d'optimalité complet #20687 : run 39744572765 cree 2026-10-22T08:00:41Z), sans nouveau commit, donc sans re-armer DWELL ni perimer une approbation a tete exacte. Applique ce jour a feat(lean,#18706): pli 5i HuffmanOptimal — le théorème d'optimalité complet #20687, chore(symbolicai): bump du gitlink Argumentum vers la tete de master (38cde3ec) #21935, fix(picker,#19643): borne timeout= sur les 2 derniers subprocess.run du tapis #21326.GITHUB_TOKENne declenche aucun workflow) : pour cette variante, il detecte et nomme, c'est tout. Detecteur : runPR gatede la tete enqueueddepuis plus de 2 h.check_run_state.pyvoyait ces quatre PRs comme vertes (20 a 39 jambes, aucun[RED]) : c'est le point 3 ci-dessus.myia-ai-01 commentedon Oct 22, 2026
[CLAIMED] lane myia-po-2027:CoursIA-2 -- dispatch c2212 : pr-gate-stale-sweep aveugle au PR gate absent
(check_lane_claim #9774 -- server-stamped UTC; body timestamps are NOT authoritative. Release with
[RELEASED]when your PR lands.)jsboige commentedon Oct 22, 2026
[DELIVERED] lane myia-po-2027:CoursIA-2 -- PR #22069
Sweep voit la classe gate-absent (PR
BLOCKEDsans check-runPR gate) : garde WFMAP étendue à la populationBLOCKED, le sélectionneur émet ces PRs comme candidates re-run (l'action existante retrouve le runPR gatede la tête et le relance), un runqueuedfigé est nommé (structurel) ;check_run_state.pyrendmissing_requiredet passe le verdict non-vert. 69 tests verts (6 nouveaux), pins structurels existants intacts.Fix(ci,#21463): garder l'entete du branchement completed dans la fene…
Fix(ci,#21463): pr-gate-stale-sweep aveugle au PR gate absent — le re…