Skip to content

fix(ci): lane_claim step died silent under bash -e before its verdict — canonical RC guard - #16222

Merged
myia-ai-01 merged 1 commit into
mainfrom
fix/lane-claim-silent-verdict
Sep 15, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
fix/lane-claim-silent-verdict

Conversation

@jsboige

@jsboige jsboige commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Grain: MED/tooling — lane myia-po-2026:CoursIA — prev: MED/tooling #16196

Livrable

1 fichier : .github/workflows/always-on-guards.yml — le step bloquant Require no closing-ref against another lane active claim meurait muet sous bash -e avant 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 par set -uo pipefail — qui n'y retire pas -e. L'appel nu :

python3 scripts/ci/lane_claim_required.py ... > /tmp/verdict.json 2>/tmp/verdict.err
RC=$?

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) :

  • le verdict JSON n'était jamais affiché ;
  • le ::error::collision de lane... avec la reason n'était jamais émis ;
  • le commentaire PR de résolution (les 3 sorties : [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) :

$ bash -e -c 'set -uo pipefail; python3 -c "import sys; sys.exit(1)" >/dev/null 2>&1; RC=$?; echo "VERDICT RC=$RC"'
exit=1            # le echo N'EST JAMAIS atteint

Le fix

Le guard canonique déjà utilisé par les deux gardes frères du même fichier (variation_prev_guard.py L798, variation_adjacency_guard.py L982) : && 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

Vérification Résultat
Replay du cœur du step sous bash -e -o pipefail avec mock helper, exits 0/1/2 les trois atteignent le bloc verdict : 0 → « job passes » exit 0 ; 1 et 2 → verdict affiché + ::error::collision ... mock reason + commentaire (mocké) + exit 1
Replay du pattern NON gardé (avant) mort muette : aucun verdict, exit 1 sans sortie
yaml.safe_load sur le workflow OK
scripts/ci/check_self_hosted_runner_policy.py --check OK — 162 workflows, 205 jobs
scripts/ci/check_unique_check_run_names.py --check OK — 84 jobs PR, 0 doublon
Scan des autres python + RC=$? du fichier 0 autre occurrence non gardée

Note

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_dispatch seul) — laissée telle quelle, conformément à son avertissement d'en-tête.

🤖 Generated with Claude Code

…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 clusterManager-Myia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 → reason extraite 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 que bash -e tuait 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.

@github-actions

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #16222 (fix(ci): lane_claim step died silent under bash -e before its verdict — canonical RC guard) touche au moins un chemin de fichier aussi modifie par d'autres PRs ouvertes. Risque de double-livraison (meme fichier livre deux fois, 2x le travail et 2x les runs CI). Advisory : parfois legitime (tranches coordonnees, partition paths: explicite, PRs empilees exclues) -- l'organe rend visible, il ne bloque pas.

@myia-ai-01 myia-ai-01 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@myia-ai-01
myia-ai-01 merged commit abce432 into main Sep 15, 2026
20 of 21 checks passed
jsboige added a commit that referenced this pull request Sep 16, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants