Skip to content

pr_gate: un check-run fige a status=in_progress avec conclusion=success fait poller l'agregateur jusqu'a STARVED (mesure #14967, 90 min) #14976

Description

@myia-ai-01

Le fait

Un check-run peut porter conclusion: success et status: in_progress en meme temps, indefiniment. pr_gate.py classe alors ce check en pending et poll jusqu'a l'expiration de son budget (45 min), puis rend STARVED.

Mesure du 2026-09-06 sur #14967 (tete b30eb6ef) :

valeur
check-run Detect notebook changes, id 101568709885
run parent Notebook Execution Required 34063718835 — status: completed, conclusion: success, updated_at 22:22:31Z
job started_at 22:20:17Z -> completed_at 22:20:27Z (10 s), conclusion: success
status du job in_progress — encore a 23:50:39Z, soit 90 min apres son propre completed_at

L'anomalie est confirmee sur deux endpoints independants (repos/.../commits/<sha>/check-runs et repos/.../actions/jobs/101568709885) : ce n'est pas une latence de coherence, c'est un enregistrement fige.

Consequence sur la PR : le PR gate 34063718782 demarre a 23:19:53Z avec --timeout-min 45 (deadline 00:04:53Z) et poll un job vert depuis 1 h. 64 des 65 autres check-runs etaient completed. Sans intervention, verdict STARVED sur une PR dont rien n'etait rouge.

La ligne

scripts/pr_gate.py:483

if status in STATUS_PENDING or not conclusion:
    pending.append(name)

Le or court-circuite sur son premier membre : un conclusion terminal ne peut pas racheter un status fige. Or c'est conclusion qui est autoritatif — l'UI GitHub elle-meme affiche ce job en vert. La forme correcte est conclusion-first : une conclusion terminale clot le check, quel que soit status ; status ne decide que lorsque conclusion est nul.

Le piege de reparation (la partie non evidente)

Relancer l'agregateur ne repare rien : le PR gate re-lirait le meme enregistrement fige et re-poll 45 min. C'est le reflexe naturel — et il coute un second budget entier.

Ce qui repare, c'est de relancer le workflow ENFANT qui porte le check-run fige, pour qu'un enregistrement frais le supplante (dedupe_latest retient le dernier par nom) :

gh run rerun 34063718835 --repo jsboige/CoursIA   # le run ENFANT, pas le PR gate

Mesure de la reparation sur #14967 : re-run lance 23:50Z -> nouveau check-run completed/success a 23:51:07Z -> PR gate settled success a 23:53:2xZ -> merge 23:53:43Z. Le budget de 45 min n'a pas ete re-paye.

Frequence — mesuree, pas supposee

Balayage des 30 PRs ouvertes a 23:55Z, comptant les check-runs status != completed AND conclusion != null sur chaque tete : 0. C'est donc un alea plateforme rare, pas un defaut recurrent. Ce ticket ne vaut pas par sa frequence mais par son cout unitaire (45 min de budget + un STARVED trompeur sur une PR verte) et par le fait que le geste de reparation est contre-intuitif.

# reconductible
for N in $(gh pr list --repo jsboige/CoursIA --state open --limit 30 --json number --jq '.[].number'); do
  S=$(gh pr view $N --repo jsboige/CoursIA --json headRefOid --jq .headRefOid)
  W=$(gh api "repos/jsboige/CoursIA/commits/$S/check-runs?per_page=100" \
      --jq '[.check_runs[]|select(.status!="completed" and .conclusion!=null)]|length')
  [ "$W" != "0" ] && echo "PR #$N wedged=$W"
done

Acceptance

  1. classify traite une conclusion terminale comme terminale : un check {status: "in_progress", conclusion: "success"} tombe dans ok, pas dans pending ; {status:"in_progress", conclusion:"failure"} tombe dans bad. status ne decide que si conclusion est nul.
  2. Controle positif en test — le cas fige doit ECHOUER avant le correctif et passer apres : construire le jeu de check-runs contenant {"name":"X","status":"in_progress","conclusion":"success"} et asserter X in ok. Sans cette assertion rouge-avant/vert-apres, le correctif n'est pas demontre (le test passerait deja sur un jeu ou tous les status sont coherents).
  3. Le message de STARVED nomme, pour chaque constituant en attente, le couple status/conclusion observe — un STARVED -- timed out waiting for: Detect notebook changes ou le job est vert depuis une heure est indiscernable d'un vrai enlisement, et c'est ce qui a coute le diagnostic ici.
  4. Le geste de reparation (relancer le run ENFANT, jamais l'agregateur) est consigne la ou un operateur le cherchera.

Ce que ce ticket ne demande pas

Aucun changement de budget (--timeout-min), de cadence de poll, ni de settle-polls : le budget n'est pas en cause — il a ete consomme a attendre un evenement qui n'arriverait jamais.

Activity

  1. jsboige commented on Sep 7, 2026

    @jsboige
    Owner

    [CLAIMED] lane myia-po-2026:CoursIA -- 2026-09-07T11:2xZ -- scripts/pr_gate.py classify conclusion-first + test controle positif fige + STARVED nomme status/conclusion + geste de reparation (rerun enfant) documente

  2. added a commit that references this issue on Sep 7, 2026
  3. added a commit that references this issue on Sep 22, 2026
  4. added a commit that references this issue on Sep 23, 2026
  5. added a commit that references this issue on Sep 23, 2026
  6. added a commit that references this issue on Oct 9, 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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions