real-debrid-downloader/tasks/lessons.md
Sucukdeluxe 288a0762a6 Renaming 100%: collect leitet sauberen Namen selbst ab (gemeinsame Entscheidungsfunktion + Wurzel-Schutz)
User-Report (aus dem Desktop-Rename-Log): 17 Dateien landeten ROH in der Library
("tvarchiv...s07e12-720.mkv", "4sf-...s04e01.mkv") — Auto-Rename hatte sie verpasst, der
MKV-Collect schob sie mit dem rohen Scene-Namen weg.

Root Cause 1: Auto-Rename und collectMkvFilesToLibrary sind entkoppelte Scans. Auto-Rename
benennt nur present-and-stable Dateien in extractDir um; eine verpasste Datei (verpasster
Zyklus ODER lag in "Downloader Unfertig" ausserhalb extractDir) wurde von collect roh
weggeschoben (collect behielt blind den Basename).
Root Cause 2: decideAutoRenameBaseName fabrizierte Namen fuer token-lose generische Ordner
("Mega-Direct-Pack" -> "Mega-Direct-Pack.S01E01") wegen eines hasSceneGroupSuffix-Falsch-
Positivs auf "-Pack" — derselbe latente Bug haette Auto-Rename getroffen.

Fix:
- Namens-Entscheidung in EINE pure Funktion extrahiert: decideAutoRenameBaseName (Single
  Source of Truth fuer Auto-Rename UND Collect — koennen nicht mehr divergieren).
- Wurzel-Schutz darin: Rename nur, wenn ein folderCandidate einen echten Season-/Episode-
  Token traegt (kein Fabrizieren aus token-losen Ordnern). Fixt beide Pfade.
- collectMkvFilesToLibrary leitet den sauberen Namen via dieser Funktion ab (gegated auf
  autoRename4sf4sj — respektiert die Umbenenn-Einstellung), inkl. Companion-Untertitel und
  Dedup gegen den sauberen Namen. mkvFiles traegt jetzt sourceRoot fuer die Ordner-Herleitung.
- Auto-Rename-Loop nutzt jetzt die gemeinsame Funktion (behebt nebenbei 2 latente
  use-before-declaration/TDZ-Fehler an resolveRenameItem).
- Latenter Bug: Casing-Zaehler renamedCount -> renamed (war undeklariert -> ReferenceError,
  vom catch verschluckt -> Casing-Korrekturen wurden still verworfen).

Verifiziert: tsc 6 (von 9 — 3 latente Fehler nebenbei behoben), 678 Tests + 9 neue (7
Charakterisierung der Entscheidung + 2 Collect-Integration: raw->clean + Companion/.srt folgt
+ Datei ausserhalb extractDir), Build gruen. Adversarialer Review-Workflow (4 Linsen) + Advisor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 01:09:21 +02:00

12 KiB
Raw Blame History

Lessons

2026-05-31 — Fix-Diagnose EMPIRISCH bestätigen, bevor man released (Timeout ≠ Account-Hänger)

Muster: "acc2/acc3 nie versucht" wurde als "acc1 hängt → Per-Account-Timeout + Rotation" diagnostiziert und als v1.7.168 released. Falsch: Mega-Debrid-Web ist eine 180s-Polling-Schleife (mega-web-fallback.ts) — acc1 pollte legitim, der 60s-Global- Timeout (nicht "Hängen") schnitt es ab. Mein 25s-Per-Account-Cap machte es SCHLIMMER (endlose 25s-Rotation, Datei nie aufgelöst). Erst der User-Log + Lesen der Provider- Impl deckte es auf. Revert v1.7.169.

Regel:

  • Ein Timeout bei einem langsam-pollenden Provider ist KEIN Account-Fehler → darf keine Rotation/kein Skippen auslösen. Vor "Account hängt"-Annahmen die Provider-Impl lesen (Polling? internes Ceiling? wie lange dauert ein Erfolg legitim?).
  • Bei zwei gegensätzlichen Diagnosen (hier: Timeout-zu-kurz vs. IP-Block — stand in der EIGENEN Memory!) NICHT die bequeme wählen + releasen. Erst empirisch diskriminieren (Env-Var auf Server, Beobachtung, oder gezielte User-Frage). Ein Symptom, das BEIDE Hypothesen gleich gut erklärt ("Timeout nach Xs"), beweist keine.
  • NICHT lokal "verifizieren" wenn das Problem umgebungsspezifisch ist (geblockte Server-IP) — lokaler Erfolg ist falsch-positiv.

2026-05-30 — Abgestürzten/„aufgehängten" Chat fortsetzen: zuerst reflog lesen

Muster: User bat, einen anderen, aufgehängten Chat-Strang „zu Ende zu bringen". Der Working Tree sah harmlos aus (nur untracked), aber der eigentliche Fortschritt lag in einem per reset --hard HEAD~1 weggesetzten Commit, der nur noch im reflog (dangling) lebte.

Regel: Bei „mach weiter wo es hing":

  1. git reflog + git log --oneline -20 zuerst — Ground Truth, NICHT der (evtl. stale) gitStatus-Snapshot oder Konversations-interne Annahmen.
  2. Reset-weggesetzte/dangling Commits (git fsck --lost-found, reflog) inspizieren (git show <sha>) — dort steckt oft die unfertige Arbeit.
  3. Verstehen WARUM weggesetzt, bevor man blind cherry-picked: hier brach ein bestehender Test (.toBe(signal)-Identitätscheck), den der Fix zwingend ändert. Der Reset war die Reaktion darauf, nicht „Fix war falsch". Erst die Reset-Ursache beheben (Test auf Verhalten umstellen), dann den Fix recovern.
  4. Eigene Memory (project_*) lesen — sie dokumentierte Bug + intendierten Fix exakt.

2026-05-30 — Release verifizieren BEVOR "fertig" gesagt wird; curl -F mit Leerzeichen im Pfad

Muster A (Edit ins Leere + trotzdem released): Ein Edit schlug fehl ("String not found"), ich habe es übersehen, committet und v1.7.165 released — die Datei enthielt das Feature NICHT. Erst der nächste Blick zeigte es. Regel: Nach jedem Feature-Edit VOR dem Release git show HEAD:datei | grep <marker> — bestätigen dass der Code wirklich im Release-Commit ist, nicht nur dass git commit durchlief.

Muster B (Gitea UNIQUE constraint): npm run release:gitea pusht erst den Tag, dann erstellt es den Release. Gitea legt beim Tag-Push automatisch einen Tag-Release- Eintrag an (name=null). fetchExistingRelease im Script matcht den nicht → POST create → UNIQUE constraint failed: release.repo_id, release.tag_name. Commit + Tag sind dann schon gepusht, nur der Release+Assets fehlen. Recovery: GET /api/v1/repos/.../releases/tags/<tag> → id holen → PATCH releases/<id> mit name/body/draft:false → Assets per POST releases/<id>/assets?name=<url-encoded> hochladen.

Muster C (curl -F Datei mit Leerzeichen): curl -F "attachment=@release/Datei mit Leerzeichen.exe.blockmap" lädt FALSCHEN Inhalt hoch (Server-Size != lokale Size). Regel: Datei mit Leerzeichen im Namen erst nach /tmp/leerzeichenfrei kopieren, DAS hochladen, Asset-Name über ?name=<url-encoded> setzen. Danach Server-Size gegen lokale Size prüfen.

2026-05-30 — Nicht in chaotische Parallel-Tool-Batches verfallen (User-Korrektur: "bist du in nem endless loop")

Muster: Bei einem großen Multi-File-Edit habe ich Dutzende Tool-Calls (Bash-Probes, Reads, Edits, Python-Inline-Skripte, mehrfache tsc-Läufe) in EINEN Message-Block gepackt. Resultat: Ein einzelner Fehler/Cancel hat die ganze parallele Kette abgebrochen, Edits landeten halb, ich verlor den Überblick welche Änderung wirklich auf Disk war, und es wirkte wie eine Endlosschleife. Dazu: wegwerf-scripts/_*.py/_*.txt als Workaround gegen Output-Encoding statt der dedizierten Tools.

Regel:

  • Edits über mehrere Dateien sequenziell, einer nach dem anderen, mit kurzer Verifikation dazwischen — nicht 20 spekulative Calls auf einmal.
  • Nach jedem Edit, der fehlschlagen kann (Anchor evtl. nicht eindeutig), das Ergebnis lesen, bevor der nächste folgt. Edit/Write erroren laut — darauf vertrauen.
  • KEINE Wegwerf-Python-Skripte ins Repo schreiben, um Shell-Output zu parsen. Grep/ Read/Edit nutzen. Wenn doch ein Temp nötig ist: nach os.tmpdir(), nie nach scripts/, und sofort wieder löschen.
  • Verifikation gebündelt am ENDE (1× tsc, 1× build, 1× vitest), nicht 10× zwischendrin.

2026-05-28 — Analyse-Befund gegen beobachtete Realität gaten (Advisor-Korrektur)

Muster: Meine Analyse sagte einen häufigen Bug voraus (jede letzte Datei im Standard-Modus + jede Nested-Datei landet unbenannt), während der User nur "1-2 pro Staffel" meldete. Ich habe die Diskrepanz bemerkt ("zu schwer um unbemerkt zu bleiben") und sie mit weiterem Timing-Argument wegrationalisiert.

Regel: Wenn die eigene Analyse etwas vorhersagt, das der beobachteten Realität widerspricht, NICHT die bequeme Lesart wählen — mit einem Reproduktions-Test gaten, bevor man fixt. Failing Test gegen den Ist-Stand zuerst (TDD/systematic-debugging Phase 4):

  • reproduziert → Bug bestätigt, mit Sicherheit fixen.
  • reproduziert nicht → Analyse hat eine Mitigation übersehen, kein Fix für Nicht-Bug.

2026-05-28 — Crash-Debris im Working Tree: stashen, nicht verwerfen

Muster: Eine abgestürzte Session (API 400) hinterließ ein uncommittetes Working Tree, das drei releaste Commits revertierte. Verlockung: git checkout/discard, um clean HEAD zu bekommen.

Regel: Fremde/unverstandene uncommittete Änderungen git stash (non-destruktiv, recoverable), nie blind verwerfen. Gibt clean HEAD, nichts geht verloren, kein Stall auf User-Rückfrage. Danach dem User sagen WAS gestasht wurde und WARUM.

Wiring-Lock vs. Mechanism-Test

Ein Test, der eine Hilfsfunktion mit dem richtigen Flag direkt aufruft, beweist nur, dass das Flag funktioniert — NICHT, dass der Produktionspfad das Flag setzt. Für echte Absicherung einen End-to-End-Test durch den realen Einstiegspunkt fahren und per Negativ-Gate (Flag temporär entfernen → Test muss fallen) verifizieren.

2026-05-31 — Log-Symptom ≠ User-Wortlaut: greppen, bevor man auf eine Meldung triggert

Muster: User meldete Mega-Debrid-Tageslimit als „Kein Server für diesen Hoster". Ich wollte den Fix an genau diese Meldung (MEGA_DEBRID_NO_SERVER_RE) hängen. Der Advisor stoppte: der Screenshot zeigte als Cooldown-Grund „Antwort leer", nicht „Kein Server".

Beweis (Support-Bundle gegrept): „Kein Server"/„Erreur"/„aucun serveur" = 0 Treffer im ganzen Bundle, „Antwort leer" = 20.861 Treffer. Der limitierte Account liefert im Web-Pfad NIE eine unterscheidbare Meldung — generate() findet ohne processDebrid-Code keinen Code → return null → der Aufrufer macht daraus „Antwort leer". Ein Trigger auf „Kein Server" wäre toter Code gewesen (= die v1.7.172-Falle, zum 2. Mal fast getreten).

Regel: Bevor man einen Fix an einen bestimmten Meldungstext hängt, in den ECHTEN Logs greppen, ob dieser Text dort überhaupt vorkommt (count-Mode, alt-Text vs. Ist-Text). Sind zwei Fälle auf Message-Ebene nicht unterscheidbar (Tageslimit vs. transienter Blip → beide „Antwort leer"), nicht raten — über ein Verhaltens-Signal klassifizieren: hier eine Streak (3× hintereinander leer → geparkt), nicht der einmalige Wortlaut.

Wiring-Test nicht vergessen (eigene Lesson): die Helfer-Unit-Tests beweisen nur den Zähler. Ein E2E-Test muss eine ECHTE leere Antwort durch den realen Einstiegspunkt (unrestrictWithAccountsclassifyAccountFailure → catch → Park) treiben, sonst bleibt unbewiesen, dass der Produktionspfad das Signal überhaupt setzt.

2026-06-01 — Ein Verifizierer muss dieselbe Pfad-Normalisierung nutzen wie die verifizierte Operation

Muster: Neues Renaming-Logging sollte nach jedem Rename verifizieren, ob die Datei wirklich unter dem Zielnamen liegt. verifyRename machte statSync/readdirSync auf den ROHEN Pfaden — der echte Rename lief aber über toWindowsLongPathIfNeeded (?-Prefix ab >=248 Zeichen). Bei langen Scene-Release-Pfaden (genau das, was die App routinemäßig umbenennt) scheiterten die rohen fs-Calls → falsches „Ziel nicht gefunden" UND — schlimmer — die Quell-Prüfung scheiterte ebenfalls → sourceGone fälschlich true → falsches „OK", das einen halb-fertigen Verschiebevorgang maskiert. Der Diagnose-Log hätte genau die schwersten Fälle vergiftet. (Adversarialer Review-Workflow fand es, Confidence 0.8.)

Regel: Wenn Code eine Operation VERIFIZIERT, muss er exakt dieselbe Pfad-/Encoding-/ Normalisierung verwenden wie die Operation selbst (hier: ?-Long-Path-Prefix). Sonst mis-reportet der Verifizierer still — und am verlässlichsten bei den Edge-Cases, die man eigentlich fangen wollte. Ein falsches OK in einem Diagnose-Log ist schlimmer als ein falsches ERROR. Zusatz: readdir-Fehler darf nicht zu „Schreibweise ok" degradieren (stilles False-OK) → eigenes WARN-Level „nicht verifizierbar".

Meta: Bei einem Feature, dessen ganzer Zweck Beobachtbarkeit/Verifikation ist, lohnt ein adversarialer Review mit Fokus „würde die Verifikation auf der ECHTEN Last (lange Pfade, case-insensitive FS, EXDEV) korrekt urteilen?" — nicht nur „kompiliert + Happy-Path-Test".

2026-06-03 — Renaming „nie 100%": entkoppelte Scans + Namens-Fabrikation aus token-losen Ordnern

Symptom (aus dem Desktop-Rename-Log diagnostiziert): 17 Dateien landeten ROH in der Library ("tvarchiv...s07e12-720.mkv", "4sf-...s04e01.mkv"). KEINE [ERROR]-Zeile — alle [INFO], weil die Verifikation nur „liegt die Datei am Zielnamen?" prüft, nicht „ist der Zielname sinnvoll?". Das Logging hat den Bug sichtbar gemacht (genau sein Zweck).

Root Cause 1 (entkoppelte Scans): Auto-Rename (scannt nur extractDir, nur present-and- stable Dateien, Freshness-Gate loggt nur via logger.info → keine Session-Spur) und collectMkvFilesToLibrary (verschiebt JEDE .mkv, behielt den rohen Basename) sind getrennte Scans. Eine von Auto-Rename verpasste Datei (verpasster Zyklus ODER lag in „Downloader Unfertig" außerhalb extractDir) wurde von collect roh weggeschoben. Fix: collect leitet den sauberen Namen SELBST ab — über dieselbe Funktion wie Auto-Rename (decideAutoRenameBaseName, single source of truth) → Race wird egal, beide Pfade können nicht mehr divergieren.

Root Cause 2 (latente Fabrikation, vom Advisor gefunden): decideAutoRenameBaseName fabrizierte „Mega-Direct-Pack.S01E01" für einen generischen Paketordner, weil hasSceneGroupSuffix("Mega-Direct-Pack") auf „-Pack" falsch-positiv matcht und Guard B dann die Quell-Episode an einen token-losen Ordner anhängt. Das hätte AUTO-RENAME genauso getroffen (nur dormant, weil echte Releases saubere Ordner haben). Fix an der Wurzel: Rename nur, wenn IRGENDEIN folderCandidate einen echten Season-/Episode-Token trägt — ein token-loser Ordner kann keine Episode autoritativ benennen.

Meta-Lektionen:

  1. Bei „X nie 100%": die Fehler aus dem ECHTEN Log ziehen (greppen), nicht raten. Hier: „Kein Server" 0×, „Antwort leer" 20k×; und 17 vs vermutete 12 (5 begannen mit Ziffer „4").
  2. Symptom-Fix vs Wurzel-Fix: ein collect-seitiger Guard (Quell-Auflösung+Codec) hätte das Symptom kaschiert + eine Restlücke gelassen; der Wurzel-Fix in der gemeinsamen Funktion schließt BEIDE Pfade + ermöglicht ehrliches 100%.
  3. Wenn ein (Sub-)Agent eine empirische Behauptung aufstellt, die der beobachteten Realität widerspricht (Review: „liefert no-target" vs Test: „benennt um"), NICHT raten — mit einem Wegwerf-Diagnose-Test die echte Rückgabe sichtbar machen, DANN entscheiden.
  4. „raw-keep ist der Boden" als Guard-Prinzip: ein Rename darf nie einen schlechteren Namen erzeugen als der Originalname.