Skip to content

check_exec_ratchet : sortir en « instrument indisponible » à l'épuisement EAGAIN au lieu d'un faux vert fail-open #16164

Closed

Description

@jsboige

Suivi de #16157 (Finding B de la review NanoClaw), demandé explicitement en issue de suivi nommée plutôt qu'élargi dans la PR d'origine.

Le défaut

scripts/notebook_tools/check_exec_ratchet.py distingue mal deux situations qui n'ont pas la même signification :

  1. le ratchet a mesuré et le résultat est « 0 notebooks changés » — verdict légitime, l'arbre de travail n'a pas bougé ;
  2. le ratchet n'a pas pu mesurer — le spawn de git a échoué (BlockingIOError(EAGAIN) sous contention de processus, cf fix(nb-tools,#16111): check_twin_parity — _repo_root sans fork + repli borne sur EAGAIN #16125), et l'échec est avalé par except OSError: return None.

Dans les deux cas le CLI imprime changed notebooks : 0 et sort en succès. Le second cas est un faux vert : un garde annonce « rien à signaler » sur un arbre qu'il n'a jamais lu. C'est exactement la classe de défaut diagnostiquée sur #16125.

#16157 a corrigé la cause immédiate (reprise bornée EAGAIN : 3 tentatives, backoff 0,05/0,15 s) en conservant les sémantiques d'origine des deux côtés — ce qui était le bon choix pour un diff minimal. Mais la reprise ne fait que repousser le faux vert : au-delà d'environ 0,2 s de contention soutenue, l'épuisement retombe sur le même return None silencieux. Le canon (check_kernel_suffix_canon.py) est fail-closed sur le même épuisement, donc les deux gardes divergent toujours sur le cas limite.

Correctif proposé

Un code de sortie distinct pour « instrument indisponible » (par ex. exit 2), au lieu de confondre l'échec de mesure avec une mesure nulle :

  • le CLI imprime un message qui nomme l'échec de spawn (pas changed notebooks : 0) ;
  • le code de sortie est distinguable de « 0 changements » et de « régression détectée » ;
  • le contrat lenient actuel (None = pas de verdict) peut être préservé côté appelant, mais devient explicite au lieu d'être déduit d'un 0 ambigu.

Critères d'acceptation

  • Un échec de spawn git après épuisement des tentatives produit un code de sortie et un message distincts de « 0 changements ».
  • Un test couvre le cas « épuisement → instrument indisponible » et vérifie qu'il n'est pas confondu avec « 0 changements ».
  • Les appelants existants du contrat lenient sont recensés, et leur comportement sur le nouveau code est décidé explicitement (pas hérité par accident).
  • La décision « fail-open ou fail-closed sur instrument indisponible » est tranchée et écrite — les deux gardes doivent converger sur ce point.

Hors périmètre

La contention EAGAIN elle-même (pourquoi le spawn échoue sous -n 4) : #16157 la traite par reprise bornée, ce n'est pas remis en question ici.

Découvert en traitant #16125 / #16157.

Activity

  1. myia-po-2023 commented on Sep 14, 2026

    @myia-po-2023
    Collaborator

    [CLAIMED] #16164 — myia-po-2023:CoursIA 2026-09-14T21:55Z

  2. myia-po-2023 commented on Sep 14, 2026

    @myia-po-2023
    Collaborator

    Claim levé (myia-po-2023) : le critère « à l'épuisement des tentatives » présuppose la reprise EAGAIN de #16157, PR encore OPEN (rouge sur un organe, non mergée). Stacker dessus = piège #16194 (PR empilée perd les gardes always-on) ; réimplémenter la reprise ici = dupliquer le travail d'une autre lane et créer un conflit garanti sur git() des deux gardes. Re-claim naturel après merge de #16157. — 2026-09-14T21:58Z

  3. jsboige commented on Sep 14, 2026

    @jsboige
    OwnerAuthor

    [CLAIMED] #16164 — myia-po-2023:CoursIA 2026-09-15T01:26Z. Re-claim naturel : #16157 MERGÉ (22:47Z) — la reprise EAGAIN prérequise est sur main. Plan : exit 2 « instrument indisponible » distinct de « 0 changements », convergence fail-closed écrite avec le canon, appelants du contrat lenient recensés.

  4. added a commit that references this issue on Sep 15, 2026
  5. added 2 commits that reference this issue on Sep 15, 2026
  6. myia-ai-01 commented on Sep 16, 2026

    @myia-ai-01
    Collaborator

    Evidence

    The four acceptance criteria are now verified on PR #16255 head 27c4e79cf6e6663e0187f728f4663a1e61a9d96a:

    • exhausted git spawn retries raise InstrumentUnavailable; the CLI exits 2 with an explicit message and never prints changed notebooks : 0;
    • regression tests cover exhausted EAGAIN, non-transient OSError, the hidden git show path, and the distinct CLI exit;
    • the caller census is explicit: the workflow fails closed on exit 2, internal None retains only the “git answered negatively” meaning, and there are no external Python importers outside tests;
    • the fail-closed decision converges with the kernel-suffix canon.

    Validation: exact-head review APPROVED; latest-by-name CI checks green; closingIssuesReferences contains only #16164. The issue body checklist is updated before merge, as required by the closure ratchet. Closure itself will be verified at least five minutes after the eventual merge before being reported.

  7. added a commit that references this issue on Sep 16, 2026
  8. myia-ai-01 commented on Sep 17, 2026

    @myia-ai-01
    Collaborator

    Fermeture autorisée par le user le 2026-09-17 (relais du registre coordinateur ai-01, « OK pour toutes tes recos »).

    L'autorisation porte sur la fermeture de cette issue une fois la PR #16255 mergée (portes techniques satisfaites : current main, checks, approval exact-head, merge vérifié, bloc Evidence, relecture ≥ 5 min après fermeture). Q16 ne porte que la fermeture, pas le merge de la PR.

  9. added a commit that references this issue on Sep 17, 2026
  10. myia-ai-01 commented on Sep 17, 2026

    @myia-ai-01
    Collaborator

    Evidence technique — livraison #16255 (issue maintenue OPEN)

    La PR #16255 a été mergée exact-head le 2026-09-17T18:22:43Z, squash cffd4556021b2af89e7fcc9b17ea02d186a175f8. Son body porte See #16164, pas Closes, donc le merge ne ferme pas cette issue user-originated.

    Critères vérifiés firsthand :

    • git() délègue la reprise EAGAIN bornée à fork_retry.run_with_fork_retry ;
    • un échec de spawn épuisé/non transitoire lève InstrumentUnavailable, au lieu de rendre une fausse mesure vide ;
    • le CLI sort 2 avec un message explicite et ne publie jamais changed notebooks : 0 dans ce cas ;
    • le chemin masqué git show échoue fermé au lieu de classer un notebook de base comme ABSENT ;
    • tests ciblés et checks requis verts au head exact ;
    • class InstrumentUnavailable vérifiée sur origin/main après merge.

    Cette Evidence documente la livraison technique. Elle ne ferme pas l’issue et ne remplace pas l’autorisation user directe encore attendue pour la fermeture manuelle.

  11. jsboige commented on Sep 18, 2026

    @jsboige
    OwnerAuthor

    Fermeture sur autorisation user directe (2026-09-18), après vérification adverse indépendante.

    Livré par #16255 (squash cffd455602, ancêtre de origin/main = fc8da0912e).

    Critère Preuve re-mesurée sur origin/main
    Épuisement du spawn → code et message distincts de « 0 changements » class InstrumentUnavailable l.47, levée dans git() l.73 ; main() rend sur stderr « instrument indisponible… ne pas lire ceci comme « 0 changements » » l.174 puis sys.exit(2) l.176 — 0/1/2 séparés. Preuve live : CLI du blob main exécutée sans git au PATH → EXIT=2, message explicite, et aucun « changed notebooks : 0 » en sortie
    Test d'épuisement non confondu avec zéro test_eagain_epuise_leve_instrument_indisponible, test_autre_oserror_sans_retry (ENOENT), test_verdict_at_base_ne_dit_pas_absent, test_cli_exit_2 (assert "changed notebooks" not in out/err) — 20/20 passed
    Appelants du lenient recensés notebook-exec-sequence-ratchet.yml:88 appelle le CLI sans continue-on-error ni always() → l'exit 2 rougit l'étape ; git grep des imports hors tests → 0 résultat. None ne survit plus que sur returncode != 0
    Fail-open/fail-closed tranché et écrit trois endroits : docstring InstrumentUnavailable l.47-58, docstring TestInstrumentIndisponible, section « Décision arbitrée » du body. Convergence vérifiée avec le canon : check_kernel_suffix_canon.py:281-292 fait retry EAGAIN borné puis raise — même fail-closed

    Résidu documentaire seul, zéro impact comportemental : scripts/notebook_tools/fork_retry.py:28 (docstring mergée par #16217 le 15/09, donc avant #16255) décrit encore check_exec_ratchet comme fail-open « arbitrage en cours dans #16164 ». Phrase devenue fausse — à rafraîchir au prochain passage sur ce module, ne justifie pas de garder l'issue ouverte.

  12. added a commit that references this issue on Sep 29, 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