Skip to content

fix(ci,#14507): SKIP_CURRENT etait inatteignable sous Windows -- --apply pouvait retirer son propre repertoire - #14508

Merged
myia-ai-01 merged 1 commit into
mainfrom
fix/14195-skip-current-unreachable-on-windows
Sep 3, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
fix/14195-skip-current-unreachable-on-windows

Conversation

@jsboige

@jsboige jsboige commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Grain: MED/guard — lane myia-ai-01:CoursIA — prev: DEEP/research-code #14471

Closes #14507. See #14195, #14476.

Le défaut

prune_merged_worktrees.py décidait is_current par une égalité de chaînes entre deux sources qui n'écrivent pas les chemins de la même façon :

current_path = 'D:\CoursIA\.worktrees\ai01-wtprune'   # str(Path(cwd).resolve())
wt_path      = 'D:/CoursIA/.worktrees/ai01-wtprune'   # git worktree list --porcelain

Sous Windows l'égalité est toujours fausse, donc SKIP_CURRENT n'était jamais atteint.

Ce garde n'est pas fail-closed. Il ne refuse pas trop : il ne refuse jamais. Le worktree depuis lequel le script tourne était traité comme n'importe quel autre, et si sa PR est MERGED il passait en REMOVE — c'est-à-dire qu'un --apply aurait tenté git worktree remove sur le répertoire courant du process.

Contrôle positif (ai-01, 2026-09-03, 64 worktrees)

.worktrees/ai01-gate-current est en HEAD détaché, PR #14459 MERGED, donc WOULD REMOVE. Le lancer depuis lui doit rendre SKIP_CURRENT.

Avant :

$ python scripts/ci/prune_merged_worktrees.py --path "D:/CoursIA/.worktrees/ai01-gate-current"
WOULD REMOVE D:/CoursIA/.worktrees/ai01-gate-current  no_branch  pr=#14459(MERGED)
total=64  removable=23  refused=41  failed=0  skipped=0

Après :

SKIP        D:/CoursIA/.worktrees/ai01-gate-current  reason=current_worktree
total=64  removable=22  refused=41  failed=0  skipped=1

Identique en passant le chemin en antislash — Path.resolve() renormalise l'argument dans la forme native quelle que soit celle reçue, donc les deux orthographes échouaient avant et passent après.

Contrôle négatif — lancé d'ailleurs, le même worktree redevient candidat, ce qui vérifie que le correctif n'a pas simplement rendu le prédicat permissif :

$ python scripts/ci/prune_merged_worktrees.py --path "D:/CoursIA"
WOULD REMOVE D:/CoursIA/.worktrees/ai01-gate-current  no_branch  pr=#14459(MERGED)
total=64  removable=23  refused=40  failed=0  skipped=1

(skipped=1 y désigne D:/CoursIA lui-même, désormais reconnu — il était auparavant refusé par le garde protected_branch:main, qui masquait le défaut sur le seul worktree où un autre garde couvrait la même erreur.)

Ce que les tests couvraient, et ce qu'ils ne couvraient pas

TestDecisionContract::test_skip_current construit le statut avec is_current=True déjà posé et vérifie la décision qui en découle. Il pinne l'aval du drapeau ; son calcul n'avait jamais été mesuré. Le critère était pourtant écrit — point 8 de l'en-tête du module de test, hérité de l'acceptance de #14195.

C'est la forme exacte de #14476 sur ce même fichier : une acceptance écrite, une implémentation qui ne la réalise pas, et aucun contrôle positif pour distinguer les deux.

Quatre tests ajoutés (TestSameWorktreePath) :

Test Rôle
test_separator_mismatch_is_the_same_worktree le cas mesuré, avec assertion préalable que la paire testée est bien dissemblable sur cette plateforme
test_trailing_separator_is_the_same_worktree variante d'écriture
test_get_worktree_info_uses_the_comparison pinne le câblage — un retour à == en l.284 rougit ici
test_distinct_worktrees_are_not_current contrôle négatif

Falsification : sur le prédicat d'avant reconstitué, 3 failed, 1 passed — les trois premiers rougissent, le contrôle négatif passe des deux côtés, comme attendu d'un contrôle négatif.

Validation

$ python -m pytest scripts/tests/test_prune_merged_worktrees.py -q
34 passed, 1 skipped in 147.00s

Note de contexte

Ce défaut a été trouvé en cherchant à réparer lookup_pr_for_detached_head sur une copie périmée de l'arbre (D:\CoursIA était à 6d0bd0209, origin/main à 750ba5818). Ce défaut-là était déjà corrigé par #14481, et le diagnostic était donc caduc avant d'être écrit. La mesure qui l'a établi vaut d'être conservée : sous le code périmé, trois worktrees d'historiques distincts (CoursIA-wt/tpx, CoursIA-wt10496, dev/CoursIA-b0merge) résolvaient tous vers #14506, la PR ouverte la plus récente ; sous origin/main ils résolvent respectivement vers #10532, #10491 et #12779, toutes MERGED. #14481 fait ce qu'il annonce.

`is_current` comparait deux ecritures differentes du meme chemin :
`git worktree list --porcelain` rend des slash avant, `Path(cwd).resolve()`
rend la forme native. L'egalite de chaines ne pouvait donc jamais etre vraie
sous Windows.

Ce garde n'est pas fail-closed : il ne refuse jamais. Le worktree depuis
lequel le script tourne etait traite comme un autre, et si sa PR est MERGED
il passait en REMOVE -- soit un `--apply` tentant `git worktree remove` sur
son propre repertoire courant.

Mesure ai-01, 64 worktrees, lance depuis `.worktrees/ai01-gate-current`
(PR #14459 MERGED) : `skipped=0` et le repertoire courant programme au
retrait. Apres correctif : `SKIP  reason=current_worktree`, `skipped=1`,
dans les deux orthographes de chemin.

Les tests existants pinnaient l'aval du drapeau (`is_current=True` deja
pose) et jamais son calcul. Les trois tests ajoutes rougissent sur le
predicat d'avant, le quatrieme est le controle negatif.

See #14195, #14476.

Co-Authored-By: Claude-Code <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Bash Syntax Advisory — shebang / executable-bit warnings

See the Shebang + dry-run advisory job log for the per-file ::warning:: lines. Non-blocking.

@github-actions github-actions Bot added the variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) label Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-ai-01:CoursIA a deja consomme son budget LIGHT du jour (une LIGHT anterieure de cette lane).
G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour,
toutes categories LIGHT confondues
(guard, doc, refs, ... partagent un seul budget) :
c'est un RATIO, pas un plafond plat. La decision de merge reste au coordinateur.

@github-actions github-actions Bot added variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory) variation-genre-run >= 2 grains consecutifs du meme genre LIGHT pour la lane (#10020, advisory) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) labels Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-ai-01:CoursIA` voit ces signaux actifs sur les mergees du jour (UTC 2026-09-03) :

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=1 genre=5 cap=2)
  • GENRE-RUN : run consecutif d'un genre LIGHT (voir signals.runs dans le log du job)
  • CAP-EXCEEDED-BY-GENRE : light_genre > cap partage G-VAR-2 (tally : declared=1 genre=5 cap=2)

G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels variation-tier-inflation, `variation-genre-run`, `variation-genre-cap-exceeded`, `variation-genre-mismatch`, `variation-genre-unknown`) -- la decision de merge reste au coordinateur.

@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.

[NanoClaw] structural review — 2 fichiers (scripts/ci/prune_merged_worktrees.py +34/−1, scripts/tests/test_prune_merged_worktrees.py +50). Les deux fichiers lus au head, le défaut vérifié au base firsthand.

Vérifié firsthand

  • Le défaut décrit est réel et localisé : au base 750ba581, la ligne 284 portait "is_current": wt_path == current_path — égalité de chaînes brute entre Path(cwd).resolve() (antislashs natifs Windows) et git worktree list --porcelain (slashs avant toujours) : toujours fausse sous Windows, donc SKIP_CURRENT inatteignable et un --apply lancé depuis un worktree merged aurait tenté git worktree remove sur son propre cwd. Le risque était bien vivant.
  • Le fix est le bon instrument : same_worktree_path(a, b) compare Path(a).resolve() == Path(b).resolve() — normalise les séparateurs ET la casse sur Windows (l'affirmation du docstring sur Path.__eq__ insensible à la casse sous Windows est correcte), avec fallback textuel (replace("\\","/").rstrip("/").lower()) sur OSError. Le choix du fallback est réfléchi : rendre False réintroduirait exactement le défaut pour un lecteur démonté — et sa direction d'échec est skip plutôt que remove, soit fail-closed.
  • Les tests pinnent les trois niveaux : le mismatch de séparateurs avec contrôle positif (assert porcelain != native — le test refuse de passer trivialement là où le cas ne se reproduit pas), le slash final, la paire distincte (négatif), et un test de câblage qui pinne explicitement le call site (« un retour à == doit rougir ici ») — c'est ce dernier qui protège sous CI Linux.
  • Câblage décision intact (decision="SKIP_CURRENT" l.455, comptage l.620+), 0 secret dans les deux fichiers.

Observations (non bloquantes)

  • Sous CI Linux (os.sep = "/"), test_separator_mismatch... dégénère en paire identique : le pin du mismatch ne mesure que sur des runs Windows. Le test de câblage compense (il rougit partout sur régression ==), mais si un jour la CI Windows n'existe plus, le défaut d'origine redeviendrait invisible aux tests — à savoir en relisant ce garde dans un an.
  • Le fallback lower-case sous POSIX compare insensiblement à la casse un FS qui y est sensible — marginalement trop permissif, mais uniquement sur chemin inaccessible, et dans la direction sûre (skip).

Fix précis, documenté au mesureur près (64 worktrees, gate-current), testé aux trois niveaux. Aucune demande de changement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lane-claim-absent Closing issue carries no claim at all (#10223) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) variation-genre-run >= 2 grains consecutifs du meme genre LIGHT pour la lane (#10020, advisory) variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

prune_merged_worktrees.py : SKIP_CURRENT inatteignable sous Windows -- --apply peut retirer son propre repertoire courant

3 participants