diff --git a/_INDEX.md b/_INDEX.md index facd25f..99adc45 100644 --- a/_INDEX.md +++ b/_INDEX.md @@ -196,3 +196,4 @@ - [hermes-tt/nyora-notes-tt-sweep-livelock-fix-08-08-2026.md](hermes-tt/nyora-notes-tt-sweep-livelock-fix-08-08-2026.md) -- run_batch_sweep_all_years.sh bloque (ORDER BY folder_path deterministe + abort apres 3 echecs consecutifs) : 3 notes qui timeoutent systematiquement au listing PJ OWA revenaient toujours en tete de requete et empechaient tout progres sur les 46 autres notes en attente (66->49 puis stuck 49 pendant 14 lots, ~1h40 perdues). Fix : colonne notes.scan_fail_count, ORDER BY scan_fail_count ASC puis folder_path, sink automatique des notes en echec. Rebuild + up -d (jamais restart), verifie sur le lot suivant. Bonus : rebuild a aussi fige en dur le fix embed_attachments.py (vec0) jusque-la seulement patche en live (08/08/2026) - [hermes-tt/nyora-notes-tt-ao-reference-endpoint-pj-archive-fix-08-08-2026.md](hermes-tt/nyora-notes-tt-ao-reference-endpoint-pj-archive-fix-08-08-2026.md) -- Suite session 08/08 : outil MCP search_linked_documents productionise (normalisation regex NN/AAAA, 3 modes d'entree ao_reference/attachment_id/note_id, teste reel 1082 correspondances). /mnt/pj-archive elucide : pas de montage manquant, bug de donnees (local_path avec un prefixe ATTACH_ROOT perime pre-bootstrap), fichiers reels presents sous /app/attachments -- corrige, 20/20 PJ ont desormais un content_hash. JOIN exact content_hash : 1re correspondance confirmee (0 le 07/08), hypothese de resorption progressive validee. Piege : UPDATE concurrent au sweep actif = database table is locked, fix PRAGMA busy_timeout au lieu de wal_checkpoint (08/08/2026) - [hermes-tt/nyora-notes-tt-phase2-embedding-relance-post-sweep-08-08-2026.md](hermes-tt/nyora-notes-tt-phase2-embedding-relance-post-sweep-08-08-2026.md) -- Sweep Phase1 toutes annees termine seul via son plafond MAX_BATCHES=15 (3 notes restantes, cas OWA permanents deja connus, aucune intervention necessaire). Phase 2 + embedding relances sur le volume complet via nouveau script run_phase2_embed_full.sh (boucle jusqu'a epuisement + embedding + audit) : 24/27 PJ telechargees extraites avec succes, 24/24 embeddees (100%), content_hash 27/27 (100% des telechargees), ao_reference 23/27. JOIN exact content_hash avec onedrive_documents : 3 correspondances (1 seul le 08/08 matin, confirme la resorption progressive). Piege : docker exec -i obligatoire pour un heredoc Python en stdin ; sqlite_vec.load(conn) obligatoire avant toute requete sur *_vec, y compris en audit ponctuel (08/08/2026) +- [hermes-tt/download-attachments-phase1-bug-timeout.md](hermes-tt/download-attachments-phase1-bug-timeout.md) -- Sweep PJ 2026 degradait la session OWA jusqu'a marquer des notes attachments_scanned=1 sans verification reelle : deux endpoints (search_folder par dossier, list_attachments par note) catchaient un timeout exactement comme un 404 et le mettaient en cache comme vide. Fix : contrat None (inconnu, a retenter) vs liste vide (verifie vide/404) strict + une retentative + circuit-breaker 3 echecs consecutifs. Filtre year_bucket ajoute (absent avant, bug distinct meme session), parametrable ATTACH_YEAR_BUCKET pour reprendre sur 2024/2025 apres 2026. Teste 2x5 notes (07/08/2026) diff --git a/hermes-tt/download-attachments-phase1-bug-timeout.md b/hermes-tt/download-attachments-phase1-bug-timeout.md new file mode 100644 index 0000000..0a56a1d --- /dev/null +++ b/hermes-tt/download-attachments-phase1-bug-timeout.md @@ -0,0 +1,121 @@ +# nyora-notes-tt / download_attachments_phase1.py -- timeout traite comme "vide" (07/08/2026) + +**Instance auteur** : hermes-tt (diagnostic + fix Claude, session chantier PJ mail O365 2026) +**Date** : 2026-08-07 +**Tags** : nyora-notes-tt, hermes-mail-browser, owa, timeout, attachments +**Statut** : valide + +--- + +## Probleme + +Le sweep des PJ 2026 (`download_attachments_phase1.py`) degradait progressivement la +session OWA partagee au fil du run (timeouts croissants), jusqu'a marquer des notes +`attachments_scanned=1` (traitees, 0 PJ) alors qu'elles n'avaient jamais ete reellement +verifiees. Symptome observe en test direct : sur un lot de 5 notes, 2 ont declenche +`Erreur listing PJ index N : timed out` sans que le run ne s'en trouve affecte -- les 5 +notes finissaient quand meme marquees scannees. + +--- + +## Contexte et contraintes + +`download_attachments_phase1.py` boucle sur les notes `attachments_scanned=0`, triees par +`folder_path`. Pour chaque dossier non encore vu, un seul appel `search_folder()` (cache en +memoire pour tout le run) resout l'index courant des messages ; `list_attachments()` va +ensuite chercher la liste des PJ pour le message matche. Les deux appels HTTP passent par +`hermes_mail_client.py`, qui pilote hermes-mail-browser (Edge reel via CDP sur session OWA +partagee). Code source a `/volume1/docker/nyora-notes-tt/`, **cuit dans l'image** (`COPY . .` +dans le Dockerfile, pas de volume mount sur les `.py`) -- toute modif exige rebuild + +recreate. + +--- + +## Ce qui NE fonctionnait PAS + +| Mecanisme | Comportement observe | Raison | +|-----------|----------------------|--------| +| `search_folder()` : `except HermesMailClientError` generique dans `process_note()` | Un timeout (60s) sur le dossier etait catche exactement comme un 404 (dossier reellement introuvable) -- `folder_items_cache[folder_path] = []` | `HermesFolderNotFoundError` (404) et `HermesFolderAmbiguousError` (409) heritent toutes deux de `HermesMailClientError` ; le `except` ne distinguait pas un vrai "dossier absent" d'un simple timeout de transport | +| Resultat vide mis en cache pour tout le run | Chaque note suivante du meme dossier tombait directement en `NO-MATCH` -> `attachments_scanned=1`, sans meme retenter l'appel | Le cache par `folder_path` ne stocke qu'une seule fois par run, sans TTL ni distinction succes/echec | +| `HermesMailClient.list_attachments()` : `except Exception` generique | Un timeout sur le listing PJ d'une note precise renvoyait aussi `[]` (silencieux), note marquee "0 PJ" a tort | Meme pattern que ci-dessus mais a l'echelle d'une note plutot que d'un dossier -- le client officiel ne fait pas la distinction 404 vs timeout, choix delibere pour son autre consommateur (backfill texte) mais dangereux tel quel pour ce script | + +--- + +## Solution validee + +**`_fetch_folder_or_none()`** (nouvelle fonction, remplace l'appel direct a +`client.search_folder()` dans `process_note`) : distingue 404 (`[]`, dossier reellement +absent), 409 ambigu (`None`, necessite resolution manuelle), et toute autre erreur +(`HermesMailClientError` generique = timeout/transport) avec **une retentative** apres +`RETRY_DELAY_ON_TRANSIENT_ERROR = 5s` avant d'abandonner (`None`). + +**`_list_attachments_or_none()`** (nouvelle fonction, bypasse +`client.list_attachments()` avec le meme appel HTTP brut) : meme principe a l'echelle +d'une note -- seul un vrai 404 devient `[]`, tout le reste (timeout inclus) devient `None`. + +**Contrat `None` propage jusqu'a `process_note()`** : `None` != `attachments_scanned=1`. +Une note dont le dossier ou le listing PJ n'a pas pu etre verifie n'est **jamais** marquee +scannee -- elle reste `attachments_scanned=0` et sera retentee au prochain passage. + +**Circuit-breaker dans `main()`** : `MAX_CONSECUTIVE_TRANSIENT_FAILURES = 3`. Des que 3 +notes d'affilee renvoient `None` (session probablement en train de se degrader), le run +s'arrete proprement (`break`) plutot que de continuer a empoisonner des dossiers pour rien. +Le run logue vu/telecharge/skippe separement au lieu d'un seul total ambigu. + +**Filtre `year_bucket` parametrable** : `YEAR_BUCKET = os.environ.get("ATTACH_YEAR_BUCKET", "2026")`. +Le SQL de `main()` n'avait aucun filtre annee avant ce fix (bug distinct, meme session) -- +`WHERE attachments_scanned = 0 AND year_bucket = ?` desormais, avec `ATTACH_YEAR_BUCKET=ALL` +pour lever le filtre une fois 2026 termine et enchainer sur 2024/2025 sans retoucher le code. + +**Orchestration en lots** (script wrapper separe, hors image) : lots de 20 notes, +redemarrage de hermes-mail-browser (cache Edge vide, cf ci-dessous) entre chaque lot, +pause de quelques minutes. Le run precedent avait deja mis en evidence qu'un profil Edge +de hermes-mail-browser peut accumuler plusieurs Go de cache disque (2,9 Go constate le +07/08 avant nettoyage) sans impact fonctionnel direct connu, mais un redemarrage periodique +repart sur un process Edge/CDP propre plutot que de laisser une session tourner des heures. + +--- + +## Verification + +```bash +docker exec nyora-notes-tt python3 download_attachments_phase1.py 5 +``` +Log attendu : `[START] N notes a scanner (year_bucket=2026, limit=5)`, puis en fin de run +`[DONE] N notes vues, X PJ telechargees, Y non verifiees`. Verifier en base que les notes +marquees `attachments_scanned=1` ont bien un contenu `attachments` (`[]` ou liste reelle), +pas juste le flag pose : +```python +import sqlean as sqlite3 +c = sqlite3.connect('/app/nyora-notes-tt.db') +c.execute("SELECT id, attachments, attachments_scanned FROM notes WHERE folder_path=?", (folder,)).fetchall() +``` + +--- + +## Pieges specifiques + +- **Le bug touchait deux endpoints differents avec le meme pattern** (`search_folder` a + l'echelle dossier, `list_attachments` a l'echelle note) -- en chercher d'autres + occurrences si `hermes_mail_client.py` est reutilise ailleurs avec un `except Exception` + qui renvoie `[]`/`None` sans distinguer 404 (vrai negatif) d'un timeout (inconnu). +- Le contrat `None` vs `[]` doit rester strict dans tout code qui consomme ces deux + fonctions -- `None` ne doit jamais etre traite comme une liste vide par accident (ex. + `fresh_attachments or []` effacerait la distinction et referait apparaitre le bug). +- Rebuild : `docker compose build` en SSH synchrone depuis mcp-nas fait tomber la connexion + (NAT TT) -- lancer en nohup + tail du log (`nohup docker compose build > build.log 2>&1 + < /dev/null &`, bien rediriger stdin sinon la commande backgroundee bloque quand meme la + session SSH parente). +- Sauvegarde systematique de l'ancien script avant remplacement + (`download_attachments_phase1.py.bak-20260807`) avant tout remplacement de fichier cuit + dans l'image. + +--- + +## References + +- Runbook associe : `hermes-tt/nyora-notes-tt-roadmap-pieces-jointes.md` (architecture 2 phases) +- Runbook associe : `hermes-tt/mail-o365-16-dossiers-error-etat-session-owa.md` (degradation + de session OWA, angle different -- degradation cote navigation plutot que cote script PJ) +- Note NyoraNotes : "Chantier PJ 2026 -- fix timeout search_folder/list_attachments + + lots de 20" (07/08/2026)