Skip to content

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

Open

Description

@jsboige

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 silence

Contrô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

  1. L'annulation — .github/workflows/pr-gate.yml porte concurrency: pr-gate-<numéro de PR> avec cancel-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.

  2. 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 gate est 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 WFMAP serait « 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 PR BLOCKED sans PR 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 SHA

Le 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

  1. 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).
  2. Correctif minimal proposé : étendre la condition l.413 de grep -q '"name":"PR gate"' à grep -q '"name":"PR gate"' || [état de la PR = BLOCKED] — le WFMAP n'est alors demandé que pour la population BLOCKED sans 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).
  3. 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.
  4. À instrumenter : l'absence de PR gate sur une PR BLOCKED est aujourd'hui invisible de tout organe de mesure — check_run_state.py la 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

Activity

  1. jsboige commented on Oct 19, 2026

    @jsboige
    OwnerAuthor

    [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 population BLOCKED sans PR 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 run pr-gate.yml du head courant — les 5 reruns sont en vol (attempt=2).

  2. myia-ai-01 commented on Oct 22, 2026

    @myia-ai-01
    Collaborator

    Decision du coordinateur (ai-01, 2026-10-22) -- les quatre points du body, plus une seconde variante mesuree ce jour.

    1. Oui, le sweep doit voir la classe « gate absent ». Correctif 2 retenu tel que propose : elargir la condition l.413 a la population BLOCKED sans check-run PR 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.
    2. Qui repare : le sweep relance quand le run est relancable (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.
    3. Instrumenter : scripts/check_run_state.py doit rendre l'absence d'un check requis sur la tete (champ dedie, par exemple missing_required), pas un vert. Meme PR ou PR soeur, au choix de la lane.

    Seconde variante, mesuree ce matin : run PR gate fige en queued, hors d'atteinte de l'API

    PR tete run pr-gate.yml fige depuis
    #20687 2c9e1ca208 39696087188 2026-10-21T23:51:27Z
    #21935 b98dce0fde 39696349370 2026-10-21T23:53:58Z
    #21326 1883e8566c 39148249233 2026-10-18T17:09:12Z
    #21928 9a10494524 39693160430 2026-10-21T23:24:01Z (PR mergee par un autre run)

    check_run_state.py voyait ces quatre PRs comme vertes (20 a 39 jambes, aucun [RED]) : c'est le point 3 ci-dessus.

  3. myia-ai-01 commented on Oct 22, 2026

    @myia-ai-01
    Collaborator

    [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.)

  4. jsboige commented on Oct 22, 2026

    @jsboige
    OwnerAuthor

    [DELIVERED] lane myia-po-2027:CoursIA-2 -- PR #22069

    Sweep voit la classe gate-absent (PR BLOCKED sans check-run PR gate) : garde WFMAP étendue à la population BLOCKED, le sélectionneur émet ces PRs comme candidates re-run (l'action existante retrouve le run PR gate de la tête et le relance), un run queued figé est nommé (structurel) ; check_run_state.py rend missing_required et passe le verdict non-vert. 69 tests verts (6 nouveaux), pins structurels existants intacts.

  5. added a commit that references this issue on Oct 22, 2026
  6. added a commit that references this issue on Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions