diff --git a/BACKLOG.md b/BACKLOG.md index 1034ef8..1b1a587 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -281,6 +281,45 @@ Config MCP de référence : **But :** l'utilisateur ne voit plus « agent-vm » dans les messages, juste des références à « la VM isolée » / « le moteur de VM d'Albert Code ». **DoD :** tous les messages utilisateur dans `lib/phases.sh`, `lib/ui.sh`, `install.sh`, `README.md` sont reformulés. Le nom « agent-vm » reste dans les commentaires de code, la LICENSE et `vendor/vm/`. +### T7.6 🟠 Ne pas repasser les flags ressources au `run` si la VM projet tourne déjà `<- AC-R037` +**But :** sur une VM projet déjà `Running`, chaque `albert-code run` affiche le prompt agent-vm « VM is currently running. It must be stopped to apply new resource settings. Stop the VM and apply changes? [y/N] » alors que rien ne change (ressources demandées = ressources courantes). Faux positif agaçant à chaque lancement. + +**Cause :** `phase_run` passe toujours `--cpus/--memory/--disk` (`lib/phases.sh:236`), et le vendored ne compare jamais aux valeurs courantes : `vendor/vm/agent-vm.sh:374` déclenche dès qu'un flag ressource est présent ET que la VM tourne (le commentaire « if ... changed » est faux). Le vendored est figé (pin 6f20194) → correction côté Albert Code uniquement. + +> ⚠️ **Post-mortem v1 (tentative du 21/07 — NE PAS refaire) :** premier correctif = `if _agent_vm_running "$(_agent_vm_name)"; then _vm opencode; else _vm --cpus … ; fi`. **Échec en run réel** : le prompt revenait. Trace `bash -x` (`/tmp/tr.txt`) : le nom dérivé était bon (`agent-vm-albert-code-b9ed5d09`), mais `_agent_vm_running` renvoyait **faux** → branche `else` → flags → prompt. Cause : `_agent_vm_running` fait `limactl list --format … | grep -q`, et `bin/albert-code` tourne sous `set -o pipefail` (l.11). Quand `grep -q` matche la 1re ligne et ferme le pipe, `limactl` (encore en écriture de la 2e VM) prend un SIGPIPE, sort en 141, `pipefail` fait échouer tout le pipe → **faux négatif intermittent** (course, dépend de la charge). Preuve dans le trace : la MÊME fonction, même VM, renvoie **vrai** 100 lignes plus loin (`agent-vm.sh:376`) car appelée **dans** le sous-shell `_vm` où `set +o pipefail` est actif (l.31). C'est exactement pourquoi `_vm()` enveloppe le vendored dans `( set +u +e +o pipefail; … )`. +> **Leçon : ne jamais appeler un helper vendored en `… | grep -q` directement sous `pipefail`.** + +**Tâches (v2 — correctif retenu) :** +1. Dans `phase_run()` (`lib/phases.sh`), remplacer l'appel unique `_vm --cpus … --memory … --disk … opencode` par une détection **sans pipe** : capturer la liste des VM d'abord, puis test bash pur. Basculer de « déjà **Running** » vers « VM projet **déjà présente** » (les ressources sont figées à la création → inutile de les repasser quel que soit le statut ; évite aussi de dépendre du format `Name Status`). +2. Ne PAS réutiliser `_agent_vm_running` (fragile sous `pipefail`, cf. post-mortem). Réutiliser `_agent_vm_name` (`vendor/vm/agent-vm.sh:137`) pour le nom (OK, pas de pipe fragile). +3. Forme cible : + ```sh + local _vm_name _vm_list + _vm_name="$(_agent_vm_name)" + # Capture d'abord (aucun pipe → immunisé au SIGPIPE/pipefail, cf. post-mortem v1), + # puis test en bash pur. `|| true` : une sortie limactl vide ne casse pas set -e. + _vm_list="$(limactl list -q 2>/dev/null || true)" + case $'\n'"$_vm_list"$'\n' in + *$'\n'"$_vm_name"$'\n'*) + info "VM déjà créée — rattachement sans re-réglage des ressources." + apply "lancer la VM isolée" _vm opencode ;; + *) + apply "lancer la VM isolée" _vm --cpus "${EFF_CPUS}" --memory "${EFF_MEM}" --disk "${AC_VM_DISK}" opencode ;; + esac + ``` +4. Garder `compute_effective_vm_resources` appelé avant (message « Ressources hôte détectées ») : on ne fait que conditionner l'usage de `EFF_*`. + +**Règles :** +- Ne PAS toucher `vendor/vm/` (vendored figé). +- **Aucun `… | grep` dans le chemin de décision** : capture-first + `case`. C'est le cœur du fix. +- Bash 3.2 compatible / `set -euo pipefail` : `_agent_vm_name` (`${1:-$(pwd)}`) sûr ; `limactl … || true` neutralise `set -e` ; `case` en bash pur, pas de pipe. +- Ne pas canonicaliser `pwd` à la main (laisser `_agent_vm_name` faire). +- Accents FR corrects, pas de tiret cadratin, non-destructif, dry-run OK. + +**DoD :** VM projet **présente** (Running **ou** Stopped) → `albert-code run` n'affiche plus « must be stopped » et lance OpenCode directement (message « rattachement »). VM **inexistante** (1er run du projet) → flags passés, la VM projet est créée dimensionnée. Validation en run **réel** (le bug ne se voit pas en test isolé à froid : course SIGPIPE, cf. post-mortem). → `TESTS.md` S41 (VM running → plus de prompt, message rattachement) + S42 (VM inexistante → flags passés, création OK). + +**Extension (même racine SIGPIPE) :** `base_vm_exists()` portait le même anti-pattern `limactl list -q | grep -q '^agent-vm-base$'`, et il est appelé par `phase_run`/`check_base_vm` en amont → un faux négatif reproposait la création de la VM de base à chaque `run` (symptôme observé chez un bêta-testeur). Aligné sur le même correctif capture-first + `case` bash pur. → `TESTS.md` S43. + --- ## EPIC 6 — Interface 3 verbes & simplification profils `<- AC-R014, AC-R015, AC-R016, AC-R017` diff --git a/TESTS.md b/TESTS.md index 53cc020..3747646 100644 --- a/TESTS.md +++ b/TESTS.md @@ -335,6 +335,58 @@ et le bloc marqueur a disparu. 4. Coller un token valide. **Attendu :** le script dérive l'identité, affiche `✓ Compte GitHub : <+@users.noreply.github.com>` et persiste. Pas de fallback manuel, pas de prompts nom/email. +## S41 — VM présente → rattachement sans prompt ressource (T7.6, AC-R037, correctif v2) + +**Préconditions :** VM projet `agent-vm--` déjà présente dans `limactl list -q` +(Running **ou** Stopped). `bin/albert-code run` exécutable depuis un dossier projet. + +**Étapes :** +1. S'assurer que la VM projet est listée : `limactl list -q | grep 'agent-vm-'`. +2. Lancer `bash bin/albert-code run --dry-run`. +3. Observer la sortie. + +**Attendu :** la sortie contient `✓ lancer la VM isolée : _vm opencode` **sans** `--cpus`, +`--memory`, ni `--disk`. Le message `VM déjà créée — rattachement sans re-réglage des ressources.` +est affiché. Aucun prompt « must be stopped to apply new resource settings » ne provient +d'agent-vm — car la condition testée est **présence** (pas `Running`), et le chemin de décision +est capture-first + `case` bash pur (zéro pipe, immunisé SIGPIPE/pipefail). Exit 0. +**Validation réelle** (le bug est une course SIGPIPE, pas reproductible à froid) : +`albert-code run` sur la VM déjà Running ne doit **plus** afficher le prompt « must be stopped ». +Si le prompt réapparaît, tracer avec `bash -x bin/albert-code run 2>/tmp/tr.txt` → vérifier +que la branche `*)` (flags) n'est pas prise. + +## S42 — VM inexistante → flags ressources passés (T7.6, AC-R037, correctif v2) + +**Préconditions :** VM projet absente de `limactl list -q` (jamais créée pour ce projet). +`bin/albert-code run` exécutable depuis un dossier projet. + +**Étapes :** +1. Confirmer que la VM projet n'apparaît pas : `limactl list -q | grep -c 'agent-vm-'` → 0. +2. Lancer `bash bin/albert-code run --dry-run`. +3. Observer la sortie. + +**Attendu :** la sortie contient +`✓ lancer la VM isolée : _vm --cpus "${EFF_CPUS}" --memory "${EFF_MEM}" --disk "${AC_VM_DISK}" opencode` +avec les valeurs calculées de `EFF_CPUS`, `EFF_MEM`, `AC_VM_DISK`. Pas de message +« VM déjà créée ». Comportement identique à celui d'avant T7.6. + +## S43 — `base_vm_exists` détecte la VM de base sans faux négatif (T7.6, AC-R037, même racine SIGPIPE) + +**Préconditions :** VM de base `agent-vm-base` présente dans `limactl list -q` (Running ou Stopped). + +**Étapes :** +1. Vérifier que la base est listée : `limactl list -q | grep -c '^agent-vm-base$'` → 1. +2. Exécuter la fonction dans le contexte réel du binaire, en boucle pour attraper la course : + ```sh + source lib/phases.sh 2>/dev/null # ou reproduire base_vm_exists() + r=""; for i in $(seq 20); do ( set -euo pipefail; base_vm_exists ) && r="${r}T" || r="${r}F"; done; echo "$r" + ``` + +**Attendu :** `TTTTTTTTTTTTTTTTTTTT` (20/20). Aucun `F`. Avant correctif (`limactl list -q | grep -q`), +un `F` intermittent sous `set -o pipefail` reproposait la création de la VM de base à chaque `run`. +Le correctif capture d'abord (`_list="$(limactl list -q 2>/dev/null || true)"`) puis teste par `case` +bash pur — zéro pipe, donc pas de course SIGPIPE. + **S40e — Fallback quand token invalide au 3e essai** **Préconditions :** `install.sh` disponible, token invalide. **Étapes :** diff --git a/lib/phases.sh b/lib/phases.sh index b06c744..04e8afa 100644 --- a/lib/phases.sh +++ b/lib/phases.sh @@ -228,12 +228,24 @@ phase_run() { fi fi - # Lancer la VM + # Lancer la VM (ou rattachement si déjà créée — pas de pipe, cf. post-mortem v1) echo info "Ouverture de la bulle isolée…" info " Albert Code lance OpenCode dans la VM" echo - apply "lancer la VM isolée" _vm --cpus "${EFF_CPUS}" --memory "${EFF_MEM}" --disk "${AC_VM_DISK}" opencode + + local _vm_name _vm_list + _vm_name="$(_agent_vm_name)" + # Capture d'abord (aucun pipe → immunisé SIGPIPE/pipefail, cf. T7.6 post-mortem). + # `|| true` : sortie vide ne casse pas set -e. + _vm_list="$(limactl list -q 2>/dev/null || true)" + case $'\n'"$_vm_list"$'\n' in + *$'\n'"$_vm_name"$'\n'*) + info "VM déjà créée — rattachement sans re-réglage des ressources." + apply "lancer la VM isolée" _vm opencode ;; + *) + apply "lancer la VM isolée" _vm --cpus "${EFF_CPUS}" --memory "${EFF_MEM}" --disk "${AC_VM_DISK}" opencode ;; + esac } # ============================================================================= @@ -258,8 +270,19 @@ check_base_vm() { } # base_vm_exists — 0 si la VM de base existe +# Détection sans pipe : `limactl list -q | grep -q` est un faux négatif +# intermittent sous set -o pipefail (course SIGPIPE, cf. T7.6 post-mortem) — +# grep -q ferme le pipe, limactl prend un SIGPIPE, pipefail fait échouer le tout. +# base_vm_exists est appelé dans phase_run/check_base_vm : un faux négatif +# reproposait la création de la VM de base à chaque run. Capture d'abord, case pur. base_vm_exists() { - command -v limactl >/dev/null 2>&1 && limactl list -q 2>/dev/null | grep -q '^agent-vm-base$' + command -v limactl >/dev/null 2>&1 || return 1 + local _list + _list="$(limactl list -q 2>/dev/null || true)" + case $'\n'"$_list"$'\n' in + *$'\n'agent-vm-base$'\n'*) return 0 ;; + *) return 1 ;; + esac } # install_agent_vm — vérifie que le bundle vendored est présent (plus de clone).