Files
nas-runbooks/hermes-tt/mail-o365-toggle-expand-non-idempotent-fix.md

7.5 KiB

16 dossiers RAG en erreur — cause reelle : clic d'expansion non idempotent dans go_to_folder_path (PAS un etat de session degrade)

Instance auteur : Claude (session Nabil, hors hermes-tt) Date : 2026-08-02 Tags : mail, o365, owa, playwright, hermes-mail-browser, nyora-notes-tt, rag, checkpoint, error, race-condition Statut : corrige et deploye en production, verifie 22/22 sur 8 chemins distincts Corrige/remplace : hermes-tt/mail-o365-16-dossiers-error-etat-session-owa.md (diagnostic initial, hypothese "etat de session degrade" infirmee -- voir section dediee ci-dessous)

Resume pour hermes-tt

Le runbook mail-o365-16-dossiers-error-etat-session-owa.md a bien repere les symptomes (/folders a 0, non-determinisme /inbox et /search sur le meme chemin) mais la cause retenue (etat de session OWA degrade, volet de navigation replie, necessitant un redemarrage avec purge Singleton*) est incorrecte. Le vrai mecanisme est un bug de code precis, reproductible a volonte, sans lien avec l'etat de la session ni un redemarrage. Trois bugs distincts, corriges et deployes.

Les trois bugs reels (par ordre de decouverte)

Bug 1 — /folders utilisait inner_text() au lieu de snapshot_folders()

GET /folders lisait chaque treeitem via .inner_text(), methode que le fichier documente LUI-MEME comme non fiable dans le docstring de snapshot_folders() (ajoutee le 31/07 pour go_to_folder_path, jamais reportee sur /folders). inner_text() renvoie une chaine vide de facon intermittente selon l'etat de rendu -- le filtre if txt: eliminait alors tout, d'ou count:0 permanent. Ce n'etait PAS un signe d'arborescence non chargee : go_to_folder_path (qui utilise deja snapshot_folders()) naviguait correctement au meme instant ou /folders renvoyait 0.

Fix : /folders appelle desormais snapshot_folders(), comme le reste du fichier.

Bug 2 — Aucun verrou entre requetes sur la page Playwright partagee

Un seul onglet OWA est partage par toutes les requetes (design assume, cf commentaire state en tete de fichier). Un asyncio.Lock existait deja mais protegeait UNIQUEMENT l'etablissement de la connexion CDP (ensure_connected()), jamais la navigation elle-meme. Deux requetes proches dans le temps pouvaient s'entrelacer sur le meme onglet.

Fix : decorateur @serialize_owa (verrou global) applique aux 9 endpoints qui touchent la page (/folders, /inbox, /message/*, /search*). /draft-reply et /draft-reply/audit n'y touchent pas (SMTP direct / lecture de log), non concernes.

Bug 3 — LA cause reelle du non-determinisme observe : clic d'expansion non idempotent

Dans go_to_folder_path, chaque segment intermediaire du chemin declenche un clic sur le bouton d'expansion du dossier, sans jamais verifier aria-expanded au prealable. Or l'etat deplie/replie d'un dossier persiste cote session OWA d'un appel a l'autre (meme onglet, jamais ferme). Consequence : chaque appel sur un chemin traversant un dossier deja deplie le referme au lieu de le laisser tel quel -- le re-snapshot 1000ms plus tard voit alors 0 enfant -> 404 "Enfants disponibles : []".

Preuve empirique (02/08/2026) : 8 a 10 appels identiques sur 00 2026|Accor Project (/inbox et /search, memes parametres, rien modifie entre les appels) alternent succes/404 exactement un appel sur deux -- signature stricte d'un toggle, pas d'une lenteur de rendu ni d'un etat de session volatil. C'est ce meme mecanisme, et non une difference entre endpoints, qui explique l'observation initiale "/inbox 200 puis /search 404 sur le meme chemin" : les deux endpoints appellent la meme go_to_folder_path().

Fix : le clic n'a lieu QUE si match["expanded"] != "true". Rend l'operation idempotente. Verifie sur 22 appels consecutifs (8 chemins distincts, dont les 3 a / dans le nom et les dossiers-conteneurs multi-niveaux) : 22/22 succes, 0 echec.

Correction du diagnostic initial (etat de session degrade)

Le runbook precedent proposait : docker compose stop + purge SingletonLock/SingletonCookie/SingletonSocket + up -d. Cette sequence redemarre le navigateur dans un etat par defaut (probablement tout replie), ce qui peut faire disparaitre l'alternance PAR HASARD pendant quelques appels -- mais des qu'un chemin profond est resolu avec succes une premiere fois (dossier deplie), l'appel identique suivant le reclot et 404 a nouveau. Cette sequence ne corrige donc rien structurellement ; elle a pu simplement coincider avec une fenetre "chanceuse" lors d'un test isole. Le /folders -> count:0 n'etait pas non plus un signe de volet replie : c'etait le Bug 1 (inner_text()), independant de l'etat reel du volet.

Deploiement

docker-compose.yml de hermes-mail-browser utilise build: ./app SANS bind-mount du code source -- seul /data est monte. Un simple restart ne charge donc jamais un main.py modifie sur le NAS ; il faut soit rebuilder l'image (lent : 15-20 min, Xvfb+Edge+noVNC reinstalles a chaque fois, cache non reutilise via l'API Docker distante testee), soit copier le fichier directement dans le conteneur en cours d'execution (PUT /containers/{id}/archive?path=/app via l'API Docker, puis simple restart du process). C'est cette seconde methode qui a ete utilisee pour ce fix -- rapide, mais non perenne en soi : un futur rebuild ecrasera avec l'ancien code si le fichier source du build context n'est pas a jour. Ici les deux sont synchronises : /volume1/docker/hermes-mail-browser/app/main.py sur le NAS contient bien les trois fixes.

Verification

curl http://hermes-mail-browser:8000/folders
# count: 156 (avant fix : 0)

for i in 1 2 3 4 5 6 7 8; do
  curl -s -G http://hermes-mail-browser:8000/inbox \
    --data-urlencode "folder_path=00 2026|Accor Project" --data-urlencode "limit=1"
done
# 8/8 HTTP 200 (avant fix : alternance stricte 200/404)

22 appels testes sur 8 des 16 chemins originaux (dont les 3 a /, DR Zone Sud x2, 01 2025, 02 2024|Moi) : 22/22 succes.

Les 16 checkpoints ont ete remis a pending dans nyora-notes-tt.db (UPDATE checkpoint SET status='pending' WHERE status='error') pour reprise automatique par le pipeline.

Pieges specifiques

  • build: ./app sans bind-mount : tout futur fix de hermes-mail-browser doit soit rebuilder l'image (lent, prevoir 15-20 min), soit patcher a chaud via PUT .../archive -- dans ce cas, mettre A JOUR le fichier source sur le NAS en parallele pour que le prochain rebuild ne regresse pas.
  • Ne pas conclure a un probleme d'etat de session sur une alternance stricte 1 appel sur 2 : c'est la signature d'un toggle non idempotent, pas d'une session volatile. Un vrai probleme de session degradee se manifeste de facon plus erratique (echecs consecutifs, pas d'alternance parfaite).
  • aria-expanded sur les treeitems OWA persiste entre les appels HTTP tant que l'onglet Playwright n'est pas ferme/rouvert -- tout code qui clique un element togglable doit verifier l'etat courant avant de cliquer.
  • Le /folders corrige expose desormais les noms REELS (emojis + minuscules inclus, ex 🧠 consultations) -- confirme que le nommage n'a jamais ete le probleme : le matching sous-chaine insensible a la casse (needle in x["label"].lower()) les gere nativement.

References

  • Corrige/remplace : hermes-tt/mail-o365-16-dossiers-error-etat-session-owa.md (02/08/2026, diagnostic initial)
  • hermes-tt/mail-o365-folder-path-disambiguation.md (31/07/2026) -- introduction de snapshot_folders(), jamais reportee sur /folders (Bug 1)
  • hermes-tt/mail-o365-hermes-mail-browser-inbox-staleness.md (24/07/2026)