Repository navigation
fix(ci): lane_claim step died silent under bash -e before its verdict — canonical RC guard - #16222
Conversation
…rdict The blocking lane_claim step in always-on-guards.yml runs under the runner's default shell (bash -e -o pipefail); its own `set -uo pipefail` does NOT remove -e. An unguarded `python3 lane_claim_required.py ...` followed by `RC=$?` died immediately on any non-zero exit (1 = legitimate BLOCK, 2 = caller error) -- before the verdict was printed and before the resolution comment (RELEASED / OVERRIDE / 48h) was posted. Every lane collision surfaced as a mute step failure diagnosed by hand (measured on #15846, run of 2026-09-13). Fix: the canonical guard of the sibling gates (variation_prev_guard, variation_adjacency_guard): `&& RC=0 || RC=$?`. Verified by replaying the step core under the exact runner shell with a mock helper: exits 0/1/2 all reach the verdict block now; pass exits 0, block prints ::error with the reason and posts the resolution comment. Sibling gates already had the guard -- lane_claim was the single unguarded occurrence. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
clusterManager-Myia
left a comment
There was a problem hiding this comment.
VERDICT: LGTM (vérifié: head 8393236 lu firsthand — 7/7 occurrences RC=$? gardées, branche verdict complète, convention conforme aux gardes frères)
[NanoClaw] Review structurelle — 1 fichier, +9/−2.
Vérifié au head (mesuré, pas repris du body) :
- Toutes les occurrences
RC=$?du fichier au head sont gardées (&& RC=0 || RC=$?) : l.720, 802, 891, 985, 1090 (le fix lui-même), 1207, 1241 — la claim « lane_claim était la seule occurrence non gardée » est exacte. - La branche verdict que le fix rend atteignable existe et est complète : RC≠0 →
reasonextraite du verdict JSON →::error::collision…→ commentaire PR avec les 3 sorties ([RELEASED] / [OVERRIDE] / 48 h) → exit 1 (l.1101-1126). C'est exactement ce chemin quebash -etuait avant l'affichage. - Sémantique du guard correcte sous
-e: les deux bras sont des affectations (statut de sortie 0), le step survit quel que soit le code du helper, et RC porte le vrai code — homogène avec la convention déjà en place chez les gardes frères du même fichier. - La 2ᵉ invocation de
lane_claim_required.py(l.656) est un chemin non-bloquant assumé (|| true, aucun RC lu) — ce n'est pas une occurrence manquée.
Non vérifié par moi : le replay runtime mocké (exits 0/1/2) du body — pris sur sa démonstration ; le mécanisme statique, lui, est confirmé.
Observation (préexistante, hors périmètre du fix) : un RC=2 « caller error » aboutit lui aussi à la branche collision (commentaire posté, reason unknown si verdict absent) — comportement inchangé par cette PR, à distinguer le jour où on voudra séparer caller error et collision réelle.
Path-collision (organ #13359/#13615)Cette PR #16222 (
|
myia-ai-01
left a comment
There was a problem hiding this comment.
APPROVED — lecture personnelle complète au head 8393236a5cdb85fe4c83a2d19a533e29a7091007.
Le body, tous les commentaires, la review existante, les threads inline et le diff complet ont été relus. Le défaut est réel : sous le shell GitHub Actions bash -e -o pipefail, l’invocation nue du helper sortait avant RC=$?, rendant muets le verdict, la raison et le commentaire de résolution. Le remplacement par && RC=0 || RC=$? conserve le code de sortie tout en rendant la branche verdict atteignable.
Le scope est atomique (un workflow, +9/−2). Les sept occurrences réelles de RC=$? sont désormais gardées selon la même convention ; la seconde invocation non bloquante reste volontairement sous || true. La branche RC non nul conserve reason, annotation, commentaire et exit 1. B.0 final : zéro thread, rc=0, review exact-head sans réserve ; PR gate, Always-on, Scripts Tests, self-cover et perimeter-review verts. L’observation sur RC=2 est préexistante et explicitement hors périmètre.
…rdict (#16222) The blocking lane_claim step in always-on-guards.yml runs under the runner's default shell (bash -e -o pipefail); its own `set -uo pipefail` does NOT remove -e. An unguarded `python3 lane_claim_required.py ...` followed by `RC=$?` died immediately on any non-zero exit (1 = legitimate BLOCK, 2 = caller error) -- before the verdict was printed and before the resolution comment (RELEASED / OVERRIDE / 48h) was posted. Every lane collision surfaced as a mute step failure diagnosed by hand (measured on #15846, run of 2026-09-13). Fix: the canonical guard of the sibling gates (variation_prev_guard, variation_adjacency_guard): `&& RC=0 || RC=$?`. Verified by replaying the step core under the exact runner shell with a mock helper: exits 0/1/2 all reach the verdict block now; pass exits 0, block prints ::error with the reason and posts the resolution comment. Sibling gates already had the guard -- lane_claim was the single unguarded occurrence. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Grain: MED/tooling — lane myia-po-2026:CoursIA — prev: MED/tooling #16196
Livrable
1 fichier :
.github/workflows/always-on-guards.yml— le step bloquantRequire no closing-ref against another lane active claimmeurait muet sousbash -eavant d'imprimer son verdict.Le défaut (mesuré)
Le shell par défaut du runner GitHub Actions est
bash --noprofile --norc -e -o pipefail(lisible dans tout log de job). Le step commence parset -uo pipefail— qui n'y retire pas-e. L'appel nu :tait tuer le step à la première ligne dès que le helper sortait non-nul — 1 = BLOCK légitime aussi bien que 2 = caller error. Conséquences mesurées sur le cas réel #15846 (run du 2026-09-13, « Organes bloquants en echec : lane_claim » sans plus de détail) :
::error::collision de lane...avec lareasonn'était jamais émis ;[RELEASED]/[OVERRIDE]/ 48 h) n'était jamais posté — la lane bloquée devait diagnostiquer à la main.Preuve du mécanisme (reproduction minimale, sous le shell exact du runner) :
Le fix
Le guard canonique déjà utilisé par les deux gardes frères du même fichier (
variation_prev_guard.pyL798,variation_adjacency_guard.pyL982) :&& RC=0 || RC=$?. lane_claim était la seule occurrence non gardée — le fix rétablit l'homogénéité, sémantique inchangée (RC≠0 suit le chemin bloc existant : reason extraite du verdict, ::error, commentaire, exit 1).Validation
bash -e -o pipefailavec mock helper, exits 0/1/2::error::collision ... mock reason+ commentaire (mocké) + exit 1yaml.safe_loadsur le workflowscripts/ci/check_self_hosted_runner_policy.py --checkscripts/ci/check_unique_check_run_names.py --checkpython + RC=$?du fichierNote
La copie de référence dormant dans
lane-claim-guard.yml(absorbée par always-on-guards depuis #13384) porte le même appel nu mais est désactivée (workflow_dispatchseul) — laissée telle quelle, conformément à son avertissement d'en-tête.🤖 Generated with Claude Code