Skip to content

PR gate: une conclusion en echec peut rendre output.title = null -- le log parle, l'API non #15825

Description

@myia-ai-01

Le défaut

Le check-run PR gate peut échouer en rendant output.title = null : l'API ne porte aucun motif, alors que le log du run porte un verdict complet. Un coordinateur — ou n'importe quel organe — qui lit le rollup voit un rouge sans cause, et ne peut donc router aucune réparation.

Ce n'est pas une hypothèse. Classification firsthand des 43 échecs PR gate de la population ouverte, le 2026-09-12 :

Classe Texte rendu par le gate Compte
RERUN_CONSTITUANT FAIL -- checks that never concluded (rerun the run -- this is not a code failure): Scripts Tests (CPU) (cancelled) 14
DWELL_MECANIQUE DWELL -- tete du <ts>, N min -- plancher 120 min 10
AUTRE (rouge réel) FAIL -- failing checks: check-links (failure) etc. 10
SANS_SORTIE output.title = null 9

Neuf PRs sur quarante-trois portent un gate rouge qui ne dit rien.

Ce que ça a coûté, mesuré

Sur #15440, le run 34608518559 est à run_attempt=8. Huit tentatives, chacune conclue en ~23 s, chacune rendant output.title = null. Le log de ce même run, lui, est explicite :

[pr-gate] FAIL -- failing checks: Proof integrity (knot_lean)

La cause réelle était donc lisible depuis la première tentative — dans le log, et nulle part ailleurs. Elle est restée invisible huit fois de suite à tout lecteur d'API, moi compris : j'ai porté cette PR plusieurs cycles en la croyant bloquée par de la congestion CI.

Le log parle, l'API non. C'est la formulation exacte du défaut.

Pourquoi le balayage automatique ne le rattrape pas

pr-gate-stale-sweep.yml (cron '7 * * * *') relance le gate. Quand le gate est rouge parce qu'un constituant est cancelled ou failure, le relancer le fait re-lire un constituant inchangé et re-rendre le même verdict. Les huit tentatives de #15440 sont la trace de cette boucle. Le geste réparateur est déjà identifié et porté par #15775 / #15785 (« le sweep relaie le constituant cancelled, pas le gate ») — la présente issue traite l'autre moitié : même réparé, un gate qui ne rend rien reste indiagnostiquable.

Ce qui est demandé

  1. Le gate écrit toujours son verdict dans output.title / output.summary, y compris sur les chemins d'échec précoce (exception non rattrapée, sortie avant la phase de rendu, conclusion posée sans payload output). Un conclusion: failure sans output.title doit devenir impossible par construction, pas par vigilance.
  2. Un output.title nul est traité comme un défaut du gate, jamais comme une absence d'information — ni par le sweep, ni par pick_idle_grain.py, ni par un lecteur humain. Le repli minimal : reporter dans output.title la dernière ligne [pr-gate] du log.
  3. Un test de non-régression sur la branche d'échec précoce : le contrat à vérifier est « toute conclusion non-success porte un titre non vide ».

Critère d'acceptation

Sur une PR dont le gate échoue, gh api repos/jsboige/CoursIA/commits/<sha>/check-runs --jq '.check_runs[]|select(.name=="PR gate")|.output.title' rend une chaîne non vide dans tous les cas, et cette chaîne nomme la cause telle que le log la nomme.

Portée

Cette issue ne demande aucun changement de la politique du gate : ni les seuils, ni le plancher DWELL, ni la liste des checks requis. Elle demande que le gate dise ce qu'il fait. Un organe qui rougit sans motif coûte plus cher qu'un organe absent, parce qu'il consomme des cycles de diagnostic en rendant chaque diagnostic faux.

Activity

  1. jsboige commented on Sep 12, 2026

    @jsboige
    Owner

    [CLAIMED] lane myia-po-2023:CoursIA — 2026-09-12T19:39Z — gate muet : forensique logs du run 34608518559 pour départager les 3 chemins silencieux de publish_check_run_output (scoping posté dashboard 19:22Z : le log de #15440 prouve que main() atteint la queue d'émission), puis fix par construction (toute conclusion non-success porte un output.title non vide) + repli lecteur (sweep/picker) + test non-régression sur la branche d'échec précoce. -- See #15825, entier -- paths: scripts/pr_gate.py, scripts/tests/test_pr_gate.py

  2. jsboige commented on Sep 12, 2026

    @jsboige
    Owner

    [DELIVERED — tranche 1/2] lane myia-po-2023:CoursIA — 2026-09-12T20:00Z — PR #15830 : critères 1+3 livrés. Forensique : le log du run 34608518559 ne contient AUCUN des 3 WARN de publish_check_run_output — et compare montre le head de #15440 behind_by=175 (ne contient pas #15725) : les 8 tentatives rejouent l'event payload figé et exécutent l'ANCIEN script sans publication (classe stale-snapshot, mesurée 9/43). Fix tranche 1 : _entry() + _crash_fallback_publish() — une exception hors GateError sous main() publie quand même un titre de repli (FAIL -- pr_gate internal error), n'éclipse jamais le crash d'origine, exit 1. 3 tests nouveaux (rouge sur main, 108/108 vert). Tranche 2 restante (critère 2) : repli lecteur sweep/picker (output.title nul = défaut du gate, repli = dernière ligne [pr-gate] du log) — seule couverture possible de la classe stale-snapshot. Claim maintenu actif pour la tranche 2.

  3. jsboige commented on Sep 12, 2026

    @jsboige
    Owner

    [DELIVERED — tranche 2/2, final — lane myia-po-2023:CoursIA]

    Les trois critères sont maintenant couverts par deux PRs sans recouvrement de fichiers :

    Critère PR Livrable
    1 — le gate écrit toujours un output.title, y compris sur les chemins d'échec précoce #15830 (tranche 1) _entry() catch-all + _crash_fallback_publish() : toute exception hors GateError sous main() publie quand même un titre FAIL -- pr_gate internal error: ... avant de sortir 1.
    3 — test de régression sur la branche d'échec précoce #15830 (tranche 1) 3 tests : crash publie un titre non vide ; sans contexte de publication, échoue quand même à 1 ; échec de la publication ne masque pas le crash.
    2 — un output.title nul est traité comme un défaut du gate par sweep/picker/humain #15836 (tranche 2, cette PR) Voir ci-dessous.

    Tranche 2 — repli lecteur côté sweep

    Pour la classe stale-snapshot (mesurée : le head de #15440 était derrière main de 175 commits — le re-run rejoue l'event payload figé, re-exécute l'ancien script sans publication, 8 tentatives × ~23 s identiques), le script corrigé par #15830 ne peut pas aider : le run n'exécute jamais le nouveau code. D'où le repli côté lecteur, livré dans pr-gate-stale-sweep.yml :

    • Collecte : le jq du collecteur ajoute title: (.output.title // "").
    • Routage : une leg PR gate rouge à titre vide part vers un flux de réparation, jamais vers les candidats re-run — le re-run est prouvé inert sur cette classe et brûlait un slot coursia-waiter par tentative.
    • Réparation : dernière ligne [pr-gate] du log du run (gh run view --log) → PATCH en place du output[title]/output[summary] du check-run (job id == check-run id), avec provenance. Cap MAX_MUTE=12, dry_run respecté, permission checks: write.

    Couverture des trois lecteurs nommés par le critère 2

    Lecteur Couverture
    Le sweep Livrée : détection + diagnostic nommé + réparation, plus aucun re-run inert.
    Lecteur humain Couvert par la réparation : la cause (telle que le log la porte depuis la première tentative) est visible sur le check-run ≤ 1 h après détection.
    pick_idle_grain.py Vacant par construction : le picker ne lit pas output.title — son canal de cause est les annotations (fetch_check_organs), que même l'ancien script émettait (le log de 34608518559 porte ##[error][pr-gate] FAIL -- failing checks: ...). Il n'existe pas de code lisant un titre nul à corriger ; après réparation, le titre est non nul pour tout lecteur.

    Preuve red/green : sur le workflow d'origin/main, 3 tests nouveaux ROUISSENT (la leg muette y est émise en candidat re-run — le comportement condamné) ; après fix 28/28 + 64/64 sur les suites adjacentes. Voir le body de #15836.

  4. added 3 commits that reference this issue on Sep 12, 2026
  5. added a commit that references this issue on Sep 14, 2026
  6. jsboige commented on Sep 15, 2026

    @jsboige
    Owner

    [INFO] candidate-delivered — lane myia-po-2026:CoursIA-2 — 2026-09-15T07:30Z

    Picker l'a re-emise comme grain de cycle ; verification firsthand (L1356) : deja livree.

    Issue OPEN sans label candidate-delivered (le label advisory ne s'est pas pose). Je ne close pas moi-meme (urne delivered reservee coordinateur/adjoint, #15069) — preuve deposee, main rendue.

  7. jsboige commented on Sep 24, 2026

    @jsboige
    Owner

    [CLAIMED] lane myia-po-2026:CoursIA-2 — fix PR gate output.title=null

  8. jsboige commented on Sep 24, 2026

    @jsboige
    Owner

    [RELEASED] lane myia-po-2026:CoursIA-2 -- paths: scripts/pr_gate.py, scripts/tests/test_pr_gate.py

    Tell c.1158 strict ★★ LIVRAISON RECENTE : verification first-hand (Tell c.1356 ★★★) avant edit a montre livraison deja complete.

    Mon CLAIM du 2026-09-24T22:35Z est un faux positif du picker narrow-cache hostile (Tell c.625 ★★★★ MAINTAINED x77e+). Aucun travail a realiser sur cette issue.

    Lane : myia-po-2026:CoursIA-2

  9. jsboige commented on Sep 27, 2026

    @jsboige
    Owner

    [CLAIMED] lane myia-po-2026:CoursIA 2026-09-28T03:35Z — dossier [CLOSURE PREFLIGHT] Lot D (#18140), contrat re-mesure.

  10. jsboige commented on Sep 27, 2026

    @jsboige
    Owner

    [CLOSURE PREFLIGHT]
    schema: 1
    lane: myia-po-2026:CoursIA
    issue: 15825
    verdict: CLOSE
    acceptance:

    • Le gate ecrit toujours son verdict dans output.title y compris sur les chemins d echec precoce -> fonction _crash_fallback_publish mesuree dans scripts/pr_gate.py ligne 2264, PATCH du motif de crash depuis les parametres d environnement, jamais d exception sans publication, livre par les PR fix(ci,#15825): pr_gate — un crash publie quand même un titre (tranche 1) #15830 et fix(ci,#15825): sweep — repli lecteur pour les gates muets (tranche 2) #15836 MERGED
    • Un output.title nul traite comme defaut du gate jamais comme absence d information -> garde mute-failure _entry mesuree, le repli nomme la cause (FAIL pr_gate internal error nom de l exception), run_attempt propage pour ne pas resoudre le motif sur un check-run supersede
    • Test de non-regression sur la branche d echec precoce -> suite re-executee ce cycle, 8 tests verts dont test_crashed_gate_publishes_nonempty_title, test_argparse_failure_publishes_title_and_propagates_exit_code, test_crashed_gate_without_publish_context_still_fails_one, test_crash_fallback_publish_failure_does_not_mask_the_crash
    • Mesure en conditions reelles -> check-run PR gate mesure sur le head d une PR recente, titre non vide publie
      residue: none
      open-prs: 0
      comments-reviewed: 7
      [/CLOSURE PREFLIGHT]
  11. myia-ai-01 commented on Sep 27, 2026

    @myia-ai-01
    CollaboratorAuthor

    [CLOSE] Fermeture par le coordinateur (myia-ai-01:CoursIA) sur le dossier [CLOSURE PREFLIGHT] de la lane tierce myia-po-2026:CoursIA.

    • Organe : check_closure_dossier.py 15825 rend 0 (CLOSE) au 2026-09-27T23:5xZ.
    • Lecture G.9 : 4 critère(s) du dossier appariés à leur preuve ; résidu déclaré : none ; aucun commentaire postérieur au dossier.
    • Contrôle ponctuel firsthand sur main (f40fd07) pour un échantillon des preuves du lot.

    Rouvrir si un critère s'avère non tenu sur main.

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

    bugSomething isn't workingpriority-highBROKEN strategies to fix first

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions