Le defaut
Le bootstrap Body live de .github/workflows/always-on-guards.yml decide du repli sur la vacuite de la sortie de gh api :
LIVE=$(gh api "repos/$GH_REPO/pulls/$PR_NUMBER" --jq '.body // empty' 2>/dev/null || true)
if [ -n "$LIVE" ]; then BODY="$LIVE"; else BODY="${PAYLOAD_BODY:-}"; fi
Or gh api, sur une erreur HTTP, sort en rc=1 ET ecrit le document d'erreur sur stdout — et --jq n'y est pas applique. Mesure firsthand (2026-09-21) :
$ gh api "repos/jsboige/does-not-exist-xyz/pulls/1" --jq '.body // empty' 2>/dev/null
rc=1
stdout=[{"message":"Not Found","documentation_url":"https://docs.github.com/rest/pulls/pulls#get-a-pull-request","status":"404"}]
Le || true avale le code de sortie, $LIVE est non vide (c'est le document d'erreur), et le repli -- ecrit precisement pour la panne d'API, cf le commentaire du bloc : « Repli : appel API vide/echoue -> payload de l'evenement. Jamais bloquant sur une panne de lecture. » -- ne se declenche jamais. C'est l'intention declaree du bloc qui n'est pas tenue par son code.
Sur quota epuisi, PR_BODY recoit donc un document d'erreur a la place du corps de la PR.
Ce que ca coute, mesure
Le meme jour, quatre PRs de la lane myia-po-2023:CoursIA (#17037, #17135, #17136, #17238) portent Always-on guards rouge, et le log de la jambe est inonde de API rate limit exceeded for installation (17 a 18 occurrences par log). Les organes bloquants qui consomment PR_BODY en tirent un verdict de contenu :
| Organe |
Verdict rendu |
Ce qui s'est reellement passe |
tag_required |
{"required_pass": false, "reason": "Grain tag absent (no Grain: <TIER>/<GENRE> in body)"} |
le tag etait present ; le document 403 n'a pas de champ .body |
perimeter |
::error::A perimeter assertion on this PR ... contradicts the effective file list |
precedee de gh error: ... API rate limit exceeded for installation (HTTP 403) |
Une panne de quota est donc presentee comme une faute de lane. Le cout n'est pas seulement le rouge : le picker en derive ROUGE IMPUTE A LA BASE et la lane est dissuadee de chercher une cause qu'elle pourrait corriger.
Pourquoi l'organe de test ne l'a pas vu
scripts/tests/test_always_on_guards_live_body.py (livre par #15697) execute le bloc avec un gh factice. Ce faux sortait sur echec en exit 1 sans rien ecrire -- il modelisait « echoue => sortie vide », qui n'est pas le comportement de l'outil. Le test test_bootstrap_falls_back_to_payload_on_api_failure etait donc vert pendant que le defaut etait vivant : il validait le repli dans le seul cas ou le repli marchait.
Perimetre
Le motif (capturer la sortie de gh puis decider sur sa vacuite au lieu du code de sortie) est unique dans le depot : une seule occurrence, .github/workflows/always-on-guards.yml:211.
Correctif
Rendre le faux fidele (rc=1 et document d'erreur sur stdout), ce qui fait rougir le test existant, puis decider sur le code de sortie dans le workflow. Voir la PR liee.
Le defaut
Le bootstrap
Body livede.github/workflows/always-on-guards.ymldecide du repli sur la vacuite de la sortie degh api:Or
gh api, sur une erreur HTTP, sort en rc=1 ET ecrit le document d'erreur surstdout— et--jqn'y est pas applique. Mesure firsthand (2026-09-21) :Le
|| trueavale le code de sortie,$LIVEest non vide (c'est le document d'erreur), et le repli -- ecrit precisement pour la panne d'API, cf le commentaire du bloc : « Repli : appel API vide/echoue -> payload de l'evenement. Jamais bloquant sur une panne de lecture. » -- ne se declenche jamais. C'est l'intention declaree du bloc qui n'est pas tenue par son code.Sur quota epuisi,
PR_BODYrecoit donc un document d'erreur a la place du corps de la PR.Ce que ca coute, mesure
Le meme jour, quatre PRs de la lane
myia-po-2023:CoursIA(#17037, #17135, #17136, #17238) portentAlways-on guardsrouge, et le log de la jambe est inonde deAPI rate limit exceeded for installation(17 a 18 occurrences par log). Les organes bloquants qui consommentPR_BODYen tirent un verdict de contenu :tag_required{"required_pass": false, "reason": "Grain tag absent (no Grain: <TIER>/<GENRE> in body)"}.bodyperimeter::error::A perimeter assertion on this PR ... contradicts the effective file listgh error: ... API rate limit exceeded for installation (HTTP 403)Une panne de quota est donc presentee comme une faute de lane. Le cout n'est pas seulement le rouge : le picker en derive
ROUGE IMPUTE A LA BASEet la lane est dissuadee de chercher une cause qu'elle pourrait corriger.Pourquoi l'organe de test ne l'a pas vu
scripts/tests/test_always_on_guards_live_body.py(livre par #15697) execute le bloc avec unghfactice. Ce faux sortait sur echec enexit 1sans rien ecrire -- il modelisait « echoue => sortie vide », qui n'est pas le comportement de l'outil. Le testtest_bootstrap_falls_back_to_payload_on_api_failureetait donc vert pendant que le defaut etait vivant : il validait le repli dans le seul cas ou le repli marchait.Perimetre
Le motif (capturer la sortie de
ghpuis decider sur sa vacuite au lieu du code de sortie) est unique dans le depot : une seule occurrence,.github/workflows/always-on-guards.yml:211.Correctif
Rendre le faux fidele (rc=1 et document d'erreur sur stdout), ce qui fait rougir le test existant, puis decider sur le code de sortie dans le workflow. Voir la PR liee.