Behebt drei Fehler derselben Klasse: an jeder Stelle wurde derselbe Wert zweimal berechnet, statt einmal berechnet und weitergereicht - und die beiden Berechnungen liefen auseinander. 1. Instabiler Ersatzschluessel (bug-hashkey-instabil.md) Mails ohne Message-ID bekamen sha256 ueber die IMAP-Rohbytes, der Reindex hashte dieselbe Mail ueber die mbox-gespeicherten Bytes (>From-Quoting, andere Zeilenenden) -> zwei Schluessel, 4.024 Doppel-Eintraege in copied. Fix: canonicalMessageBytes() bringt beide Seiten auf eine Form (>From zurueckdrehen, CRLF->LF, Trailing-Newlines weg). Alle Pfade (Index, Migration, Viewer, Dedup) nutzen dieselbe Funktion. body_sha256 in copied + mbox_index, UNIQUE erweitert. 2. Index-Drift (bug-index-drift.md) Der plain-mbox-Reader las nach Datei-Position statt nach dem gespeicherten file_offset. Weil SQLITE_BUSY (busy_timeout=0) je Ordner einen Index-Eintrag verschluckt hatte, war ab dieser Luecke alles um 1 verschoben: Klick auf Mail X zeigte Mail X+1. Betroffen genau die 6 Ordner mit Busy-Fehler beim Rettungslauf. Fix: busy_timeout=10000, Lesen ueber file_offset, --reindex --rebuild. 3. HTML-Vorschau-Panic (bug-htmltotext-panic.md) replaceCaseInsensitive/stripHTMLBlock indizierten mit Offsets aus strings.ToLower(s) in s - ToLower ist nicht byte-laengen-erhaltend (z.B. U+0130). ~0,1% der Mails brachten die Vorschau zum Absturz. Fix: asciiFoldIndex() sucht direkt auf den Original-Bytes; zusaetzlich Rohtext-Fallback, damit keine archivierte Mail unsichtbar wird. Weiter: Archiv-zuerst-Reihenfolge (mbox_done/target_done) - ein sterbendes Ziel kostet keine Archiv-Kopie mehr; Dedup vergleicht zusaetzlich den Inhalt und schuetzt byte-verschiedene Varianten. WICHTIG: Die 62.073 alten Alias-Zeilen in copied bleiben bewusst erhalten. Sie sehen wie Muell aus, sind aber die Zeilen, auf die der alte Schluessel matcht - ein Loeschen wuerde Mails erneut in fremde Postfaecher kopieren. Verifiziert: go test ./... gruen; 58.051 Archivmails, 0 ohne copied-Zeile; Drift 0 ueber alle 69 Ordner; 2.000 HTTP-Stichproben ueber 8 Ordner: 0 falsche Zuordnung, 0 Panics; Watcher 220 + 60 Mails archived=0 target=0 errors=0. Live als Image fix-identity2-20260716. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.8 KiB
🔴 KRITISCH — Vorschau/Liste um 1 verschoben (Index-Drift durch SQLITE_BUSY)
Der Reader-Aliasing-Fix (6c4073a) war richtig, aber es gibt einen ZWEITEN,
unabhaengigen Bug. Klick auf „iTagPro" zeigt „Orivelle Pen" (die Nachbarzeile).
Root Cause — bewiesen, nicht vermutet
Zwei Fehler, die sich verketten:
-
6 Mails sind in der mbox-DATEI, aber nicht im
mbox_index. Ursache:PRAGMA busy_timeoutsteht immer noch auf 0 (derrettungslauf-fixes.md-Brief ist nicht umgesetzt). Beim Rettungslauf gab es pro betroffenem OrdnerSQLITE_BUSY:mbox.Appendschrieb den Record in die Datei, der darauffolgende Index-INSERT scheiterte sofort. Ergebnis: Datei-Record vorhanden, Index-Zeile fehlt. -
Der plain-Reader liest nach DATEI-POSITION statt nach
file_offset.06-mbox.goreadMboxMessageFromIndex: holt zwar die Index-Zeile (mit korrektemfile_offset), wirft sie fuer plain aber weg (if !strings.HasSuffix(path, ".zst") { return nil, false, nil }) und faellt aufmsgs[index]= Datei-Position zurueck. Ab der fehlenden Index-Zeile ist Position != seq → alles um 1 verschoben.
Der Beweis (drift-scan ueber alle 66 Ordner):
byrne/INBOX Datei 5869 Index 5868 +1 (Rettungslauf errors=1)
crystal/INBOX Datei 4874 Index 4873 +1 (errors=2 ->
crystal/INBOX.Sent Datei 2146 Index 2145 +1 genau 2 Ordner)
gb/INBOX Datei 6675 Index 6674 +1 (errors=1)
rb/INBOX Datei 8628 Index 8627 +1 (errors=1)
roby/INBOX Datei 2718 Index 2717 +1 (errors=1)
Drift-Anzahl == SQLITE_BUSY-Fehleranzahl, exakt. Die 60 fehlerfreien Ordner
haben 0 Drift. Und: Lesen ueber file_offset liefert fuer gb 4129/4129
Mails korrekt (0 echte Fehler) — die gespeicherten Offsets sind die Wahrheit.
Fix (drei Teile, in dieser Reihenfolge)
A. busy_timeout setzen — PFLICHT, sonst passiert es wieder
ConnectDB (02-database.go): PRAGMA busy_timeout = 10000; neben journal_mode=WAL.
B. Plain-mbox ueber file_offset lesen (der eigentliche Fix)
readMboxMessageFromIndex darf fuer plain nicht mehr auf die Positions-
Zerlegung zurueckfallen. Analog zum zstd-Pfad:
- Index-Zeile holen →
file_offsetseeken → Record bis zumfile_offsetder naechsten Zeile (bzw. EOF) lesen,TrimRight("\n"). - Die fragile Neu-Zerlegung
readMboxMessages+msgs[index]ganz aus dem Vorschau-/Export-/Copy-Pfad entfernen. Zwei getrennte Zerleger (Writer- Offsets vs. Reader-Resplit), die auseinanderlaufen koennen, sind die Wurzel. - Damit lesen Liste UND Vorschau aus derselben Quelle (
mbox_index) → sie koennen nicht mehr auseinanderlaufen.
C. Die 6 verlorenen Index-Zeilen nachziehen — VOLLER Reindex aus der Datei
Das bestehende --reindex haengt nur ab indexed_bytes an — es findet die
mittendrin fehlenden Mails nicht. Es braucht einen Full-Rebuild:
--reindex --rebuild <konto|all>:mbox_indexfuer den Ordner loeschen und aus der Datei komplett neu aufbauen (jeder Record → seq, file_offset, message_id, subject, date). Danach ist Index-Anzahl == Datei-Records.- Wichtig —
copiedmitziehen: Diese 6 Mails stehen auch nicht incopied(der Busy-Fehler hat auchMarkCopiedverhindert). Ein Watch-Lauf wuerde sie sonst erneut ins Ziel-Postfach und erneut in die mbox kopieren (Dublette in beiden!). Beim Rebuild deshalb pro Record die Message-ID (+ ggf.body_sha256) incopiedmitmbox_done=1eintragen.
Sekundaerbefunde (mitnehmen, kein Blocker)
- Ungueltiges UTF-8 in
mbox_index.subject(z.B. eine Tesla-Newsletter-Mail mit kaputtem Byte) — bricht sogarsqlite3-Abfragen. Subject vor dem Speichern nach UTF-8 sanitisieren (ungueltige Bytes ersetzen). - Envelope-Message-ID vs. Roh-Header: In ~8 gb-Faellen steht im Index ein
sha256:-Fallback, obwohl die Datei einen echtenMessage-ID-Header hat. Beim Indizieren zuerst den Roh-Header parsen, bevor auf Hash zurueckgefallen wird — sonst greift Dedup fuer diese Mails nicht.
Abnahme
go test ./...gruen;PRAGMA busy_timeout= 10000.- Test, der die Drift faengt: mbox mit N Records schreiben, eine Index-Zeile in der Mitte loeschen, dann Vorschau von seq > Luecke abrufen → muss die richtige Mail liefern (heute die Nachbarmail).
--reindex --rebuild all, danach drift-scan == 0 fuer ALLE 66 Ordner (Datei-Records == Index == Listen-Eintraege).- Watch-Lauf nach dem Rebuild:
copied=0fuer alle Konten (keine Neukopie der nachgezogenen Mails). - Voller Sweep (Betreff und Datum) ueber gb/INBOX und crystal/INBOX: 0 Abweichungen.
Ich (Claude) fahre nach dem Fix den drift-scan ueber alle Ordner UND einen
Voll-Sweep gegen mbox_index — diesmal ueber ALLE betroffenen Postfaecher,
nicht nur eines.