hermes-tt: runbook bug timeout search_folder/list_attachments PJ 2026

This commit is contained in:
Claude
2026-08-08 12:01:42 +01:00
committed by bolbol
parent 50a1de04e3
commit 3071a5a553
2 changed files with 122 additions and 0 deletions
+1
View File
@@ -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-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-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/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)
@@ -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)