User-Report (Desktop-Log): "Kreuzfahrt ins Glück" — 25 Folgen "bet_kig_01_hdt.mkv" (obfuskiert,
KEIN SxxExx-Token) landeten roh in der Library, obwohl der Episoden-Ordner
"Kreuzfahrt.ins.Glueck.01.Hochzeitsreise.nach.Burma.2007.German.720p.HDTV.x264-BET" bereits der
saubere Name ist (Episode als "01" statt S01E01).
Ursache (vorbestehend, nicht v1.7.178/179): buildAutoRenameBaseName gibt null zurueck, sobald die
QUELLE keinen SxxExx-Token hat — das "Folge 01"-Nummernformat wurde nie unterstuetzt.
Fix: Fallback in decideAutoRenameBaseName — fehlt der Quell-Episode-Token und kann normal kein
Name abgeleitet werden, aber ein folderCandidate ist ein VOLLSTAENDIGER Scene-Release-Ordner
(Scene-Gruppe UND Aufloesung ODER Codec, kein reiner Season-Ordner), wird dieser Ordnername
direkt verwendet (note "folder-as-is"). Greift NUR ohne Quell-Episode-Token -> Mega-Direct
(mit Quell-Token) bleibt no-target. Aufloesung ODER Codec (nicht nur Aufloesung) deckt
DVDRip/XviD ohne 720p ab (Advisor-Punkt). Bonus/Sample werden vorher gefiltert.
Verifiziert: tsc 6, 682 Tests gruen (+3: Kreuzfahrt real, DVDRip-nur-Codec, Mega-Direct-bleibt-
no-target), Build gruen. Advisor + reproduzierter Diagnose-Test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User-Report (aus Desktop-Rename-Log): castle.s08e02.german.dl.720p.web.h264-idtv_int.mkv im
sauberen Episoden-Ordner "Castle.S08E02.GERMAN.DL.720p.WEB.H264-idTV_iNT" (Paket "scn2-cstl7")
wurde zu "scn2-cstl7.S08E02.mkv" VERSCHLIMMBESSERT (guter Quellname -> obfuskierter Paketname).
Ursache (vorbestehend, nicht durch v1.7.178): hasSceneGroupSuffix erkannte die Scene-Gruppe
"-idTV_iNT" nicht (SCENE_GROUP_SUFFIX_RE + Fallback verbieten Unterstriche). Der saubere
Episoden-Ordner wurde dadurch als Nicht-Scene-Ordner verworfen, und die Namensherleitung fiel
auf den obfuskierten Paket-Ordner "scn2-cstl7" zurueck -> "scn2-cstl7.S08E02".
Fix: hasSceneGroupSuffix nutzt jetzt zusaetzlich extractFlexibleSceneGroupSuffix (existierte
bereits, war aber nicht verdrahtet), das Unterstrich-Gruppen korrekt erkennt (splittet auf "_",
validiert jeden Teil). Der saubere Ordner wird akzeptiert -> idealer Name
"Castle.S08E02.GERMAN.DL.720p.WEB.H264-idTV_iNT". Mein v1.7.178-Folder-Token-Guard schuetzt
generische Paketordner (Mega-Direct) weiterhin.
Verifiziert: tsc 6, 679 Tests gruen (+1 Charakterisierung fuer den idTV_iNT-Fall), Build gruen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Beim Update parkte installUpdate() aktive Downloads via stop() -> deren Abbruch-
Continuation markierte die Items "cancelled"/"Gestoppt". autoResumeOnStart nimmt
nach dem Neustart aber nur "queued"/"reconnect_wait" auf, also liefen die gerade
ladenden Downloads nach dem Update nicht weiter (timing-abhaengig: "manchmal").
Jetzt: stop({parkForRestart:true}) bricht aktive Tasks mit Grund "shutdown" ab,
sodass sie als "queued" re-queued werden (wie bei normalem App-Shutdown). Das
schliesst zugleich den einzigen plausiblen Loesch-Pfad (all-cancelled-Pakete sind
ueber applyRetroactiveCleanupPolicy entfernbar). Stop-Button-Verhalten unveraendert.
Zusaetzliche Robustheit in storage.ts (enge Blast-Radien, nicht die Hauptursache):
- async-Save-Clobber: eine gequeuete, veraltete Payload konnte einen neueren
Sync-Save (persistNowSync/prepareForShutdown) ueberschreiben; Generation wird
jetzt zum Snapshot-Zeitpunkt erfasst und durch die Queue getragen.
- loadSession gab leer zurueck (und ignorierte ein gefuelltes .bak), wenn die
Primaerdatei fehlte; faellt jetzt auf die Backup/Temp-Recovery zurueck.
Regressionstests: tests/update-restart-resume.test.ts (echter Live-Download ->
Park -> Reload = queued, plus Charakterisierung plain stop() -> cancelled) und
tests/session-restart-loss.test.ts (Clobber + Backup-Fallback). Volle Suite gruen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User-Goal: bei kuenftigen Renaming-Problemen eine vollstaendige, sofort auffindbare Uebersicht —
JEDER Umbenenn-/Verschiebevorgang protokolliert UND danach verifiziert (liegt die Datei wirklich
unter dem Zielnamen? Quelle weg? richtige Schreibweise?).
- NEU desktop-rename-log.ts: pro Sitzung <Desktop>/Downloader-Log/rename-session_<ts>.txt; Ordner
selbstheilend (mkdir recursive vor jedem Write -> auch nach Loeschung zur Laufzeit sofort wieder
da). Synchroner Append, Schreibfehler verschluckt (bricht nie einen Download).
- verifyRename (sync) + verifyRenameAsync (Hot-Path): prueft Ziel-Existenz, echten On-Disk-Namen
(case-genau via readdir), Quell-Abwesenheit; Level INFO/WARN/ERROR. Nutzt denselben \?\-Long-
Path-Prefix wie der echte Rename (sonst falsche Urteile auf langen Scene-Pfaden).
- download-manager: renamePathWithExdevFallback = verifizierter Wrapper um die unveraenderte
Raw-Logik (deckt alle Media-Renames ab) + 3 Sync-Sites (startup-Dedup, Deobfuskation, Suffix-Fix)
via logVerifiedRenameSync; logRenameProcess spiegelt ins Desktop-Log.
- app-controller init/shutdown (getPath("desktop") gegen Startup-Crash abgesichert); support-bundle
packt das Log mit ein.
Adversarialer Review-Workflow (4 Linsen) fand + behoben: Long-Path-Verify-Bug (falsches OK
maskiert halb-fertigen Move), readdir-Fehler-False-OK, sync-I/O im Hot-Path, getPath-Guard,
Test-Temp-Cleanup. tsc 9 (Baseline), 663 Tests (+7 neue), Build gruen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beim Release v1.7.175 brach der Upload eines ~87MB-Assets transient ab (exit 4). Dank der
draft-first-Logik blieb das Release unsichtbar (kein kaputtes Live-Release), musste aber
manuell per curl nachgeladen + publiziert werden. Der Upload nutzte einen einmaligen
fetch-Stream ohne Retry.
Fix: je Asset bis zu 3 Versuche mit Backoff bei Netzwerk-Abbruch oder 5xx; pro Versuch ein
frischer createReadStream (konsumierter Stream ist nicht erneut sendbar). 4xx (ausser
409/422 = existiert bereits) brechen weiterhin sofort ab.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User-Entscheidung: ein Mega-Debrid-Account am Tageslimit soll bis zum Programm-Neustart
uebersprungen werden, nicht alle 20s/2min neu getestet.
Ground Truth (Support-Bundle gegrept): der limitierte Account liefert im Web-Pfad NIE eine
unterscheidbare Meldung — "Kein Server" = 0 Treffer, "Antwort leer" = 20.861. Tageslimit und
transienter Blip sind auf Message-Ebene nicht trennbar (generate() findet ohne processDebrid-
Code keinen Code -> return null -> "Antwort leer"). Ein Trigger auf "Kein Server" waere toter Code.
Loesung (Verhaltens-Signal statt Wortlaut):
- megaDebridEmptyResponseStreaks zaehlt aufeinanderfolgende "Antwort leer"/"Kein Server"-
Treffer je Account; ab 3 wird der Account bis Neustart geparkt (until=MAX_SAFE_INTEGER,
nur In-Memory -> Neustart loescht). Erfolg/anderer Fehler setzt zurueck.
- classifyAccountFailure markiert beide Signale als limitSignal (Symmetrie: ein einzelner
evtl. transienter Treffer parkt NICHT, behaelt kurzen Cooldown).
- Skip-Branch: "uebersprungen (bis Neustart gesperrt)", traegt nicht zu earliestCooldownUntil
bei (kein absurder Retry-Timer); Post-Loop wirft klare Endmeldung wenn alle geparkt.
- generate() surfacet "Kein Server" zusaetzlich als Page-Error (falls es doch im HTML steht).
- UI: Rotations-Verlauf zeigt "bis Neustart gesperrt".
Verifiziert: tsc 9 (Baseline), 655 Tests + 5 neue (inkl. Wiring-E2E der eine echte leere
Antwort durch unrestrictWithAccounts->classify->catch->Park treibt), Build gruen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User-Report: Auto-Update schlug mit "Setup-Asset nicht gefunden" fehl, wenn der Update-Check
waehrend des Asset-Uploads eines neuen Releases feuerte. Ursache: release:gitea pushte Tag +
erstellte das Release (draft:false) VOR dem Asset-Upload → das "latest"-Release war den ganzen
Upload ueber sichtbar, aber ohne latest.yml (Gitea-Assets haben keinen digest → der Updater
holt den Integritaets-Hash aus latest.yml, das zuletzt hochgeladen wird). Fix: Release als
draft:true anlegen → alle Assets hochladen → dann PATCH draft:false. Der Updater ueberspringt
Drafts und sieht das Release erst, wenn es komplett ist.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User-Report: Logs zeigten z.B. "17:29:43" obwohl es lokal 19:29:43 war (CEST/UTC+2), weil
alle Logger `new Date().toISOString()` (UTC "...Z") nutzten. Neuer Helper logTimestamp()
formatiert lokale Zeit mit explizitem Offset (ISO 8601, z.B. "2026-05-31T19:29:43.605+02:00")
— menschlich lokal UND weiterhin eindeutig/Date.parse-bar. Angewandt auf alle Log-Zeilen-
Writer: item-log, logger (rd_downloader.log), audit-log, rename-log, session-log,
package-log, account-rotation-log, trace-log. Interne/API-/Dateinamen-Zeitstempel
(debug-server, support-bundle, trace autoDisableAt-Config) bleiben absichtlich UTC.
Test: tests/log-timestamp.test.ts (Format + Round-Trip zum selben Instant + lokale Stunde,
TZ-unabhaengig). 650 Tests gruen, tsc 9, Build sauber.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root-Cause (verifiziert via Support-Bundle): Der Web-Unrestrict lief fuer JEDEN rotierten
Account mit den Creds des ersten/Legacy-Accounts (settings.megaLogin), weil MegaWebFallback
EINE geteilte Cookie-Session + festes getCredentials() nutzte UND megaWebUnrestrict ohne
Account-Bezug aufgerufen wurde. Item-Log-Beweis: "Account 2/2 (FabelDavid): Mega-Web Antwort
leer", obwohl FabelDavid real funktioniert — die Rotation nutzte FabelDavid nie wirklich,
sondern immer Account 1 (am Limit). Alle bisherigen Fixes (v1.7.169-172) lagen downstream
dieses Punkts und konnten den Bug nicht beheben.
Fix:
- MegaWebUnrestrictor bekommt optionalen `account`-Parameter; MegaDebridClient.unrestrictViaWeb
reicht this.login/this.password (den rotierten Account) durch; app-controller leitet ihn weiter.
- MegaWebFallback: Per-Login Session-Cache (Map<login,{cookie,setAt}>) statt einem geteilten
Cookie; login() gibt das Cookie zurueck, generate() bekommt es als Param. Jeder Account nutzt
seine eigene Session — kein Re-Login-Thrash unter Parallel-Last (maxParallel=8).
Tests: mega-web-fallback (Login-POST traegt den uebergebenen Account-Login, nicht den Default)
+ debrid-Rotation (jeder Account erhaelt SEINE Creds; Account 2 loest auf). 647 Tests gruen,
tsc 9, Build sauber.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User-Report: Account 1 am Tageslimit liefert "Kein Server fuer diesen Hoster verfuegbar".
Bisher lief das durch die volle Web-Retry-Maschine (generate->null -> re-Login -> 3x
REQUEST_RETRIES) und fraß ~40s des GETEILTEN 60s-Unrestrict-Budgets -> der funktionierende
naechste Account (FabelDavid) lief in den Timeout (aborted:debrid -> als fatal klassifiziert,
"abgebrochen (fataler Fehler)" im Rotations-Verlauf), obwohl er gehen wuerde.
Fix (3 Teile, gemeinsame MEGA_DEBRID_NO_SERVER_RE):
1. mega-web-fallback generate(): die "Kein Server"-Meldung wird surfacet (throw) statt
null zurueckzugeben -> kein re-Login + erneutes Pollen.
2. unrestrictViaWeb: bricht bei der Meldung ab (kein 3x-REQUEST_RETRIES) -> sofortige
Retries sind zwecklos (Limit bleibt) und verbrennen das geteilte Rotations-Budget.
3. classifyAccountFailure: erkennt die Meldung -> quota-Cooldown (2 min) -> naechster
Account, mit echter Meldung im Log statt generischem "Antwort leer".
So scheitert der limitierte Account schnell (1 Versuch) und der naechste Account bekommt
das volle Budget zum Aufloesen.
Tests: mega-web-fallback (throw + ajaxCalls=1) + debrid-Rotation (acc1 Limit -> acc2,
calls=2). 645 Tests gruen, tsc 9, Build sauber.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Folge-Fix zum Sofort-Check (efb5696): updateSettings uebernahm debridAccountStatuses
aus dem (evtl. veralteten) Renderer-Settings-Patch statt aus dem Manager. Speichern
direkt nach Hinzufuegen+Pruefen eines Accounts konnte so den frisch geprueften Status
ueberschreiben -> Badge sprang zurueck auf "Noch nicht geprueft". debridAccountStatuses
ist main-owned Runtime-State (nur via applyDebridAccountStatuses gesetzt) -> wird in
updateSettings jetzt aus dem Live-Manager bewahrt (wie die Usage-Counter). Schuetzt auch
den "Alle pruefen"+Settings-Save-Pfad. 643 Tests gruen, tsc 9.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beim "Hinzufuegen" eines Mega-Debrid-Accounts im Bearbeiten-Dialog wird der Account
jetzt sofort einzeln geprueft (connectUser) — Login-Gueltigkeit + Premium-Restlaufzeit
erscheinen direkt als Badge, ohne den Tab schliessen und "Alle pruefen" klicken zu
muessen. Waehrend der Pruefung zeigt das Badge "Pruefe…".
Neue Einzel-Check-IPC (Spiegel von checkDebridAccounts): CHECK_MEGA_DEBRID_ACCOUNT
-> app-controller.checkSingleMegaDebridAccount(login, password) baut den Account-Entry
(id via getMegaDebridAccountId), ruft die bestehende checkMegaDebridAccount() und merged
das Ergebnis via applyDebridAccountStatuses -> Snapshot -> Badge (gleicher Pfad wie
"Alle pruefen"). Funktioniert fuer den noch nicht gespeicherten Draft-Account, weil
applyDebridAccountStatuses merged (kein Pruning) + emitState.
Kern-Check-Logik unveraendert + weiterhin durch account-check.test.ts gedeckt.
643 Tests gruen, tsc 9 (unveraendert), Build sauber. GUI compile-/build-verifiziert,
im laufenden Electron noch nicht click-getestet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backend war bereits vorhanden (megaDebridDisabledAccountIds + Rotation-Skip +
Storage-Normalisierung); es fehlte nur das UI. Spiegelt das Debrid-Link-Muster:
im Account-Bearbeiten-Dialog bekommt jeder Mega-Account einen Aktivieren/
Deaktivieren-Toggle (+ "Deaktiviert"-Badge). Der Disabled-Zustand wird im Dialog-
Draft gehalten (megaDisabledIds) und beim Speichern via applyAccountDialogToSettings
in megaDebridDisabledAccountIds uebernommen (gefiltert auf vorhandene Accounts).
Kein Live-Persist mitten im Dialog -> kohaerent mit dem draft-then-Save-Modell.
Wirkt OHNE Neustart: DebridService.unrestrictLink liest this.settings live
(setSettings propagiert die Liste), unrestrictWithAccounts ueberspringt deaktivierte
Accounts (gleicher Mechanismus wie Daily-Limit/Cooldown-Skip).
Test: "skips a manually disabled Mega-Debrid account" — acc1 disabled -> acc2 loest
auf (beweist den ID-Seam getMegaDebridAccountId). 643 Tests gruen, tsc 9, Build sauber.
GUI-Toggle compile-/build-verifiziert, im laufenden Electron noch nicht click-getestet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Es gab Rotations-Tests fuer Debrid-Link, aber KEINEN fuer Mega-Debrid. Beweist die
vom User geforderte Rotationstatsache: liefert ein Account den Tageslimit-Fehler,
rotiert unrestrictWithAccounts zum naechsten Account (acc1 Limit-Fehler -> acc2 loest
den Link auf). Fehler-basiert (NICHT timeout-basiert — der reverted v1.7.168-Ansatz).
64 Tests in debrid.test.ts gruen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v1.7.168 fuehrte einen 25s-Per-Account-Timeout in die Rotation ein, Annahme: ein
"haengender" Account solle uebersprungen werden. Falsch: der Mega-Debrid-WEB-Unrestrict
ist eine Polling-Schleife (mega-web-fallback.ts: bis 60 Durchlaeufe, intern 180s-Ceiling)
— Mega-Debrid laedt die Datei erst auf den eigenen Server, das dauert legitim 30-180s.
Der 25s-Cap schnitt JEDEN Account mitten im Polling ab ("Account-Timeout nach 25s" in
Dauerschleife), die Datei wurde nie aufgeloest. Ein Timeout ist bei einem langsam-
pollenden Provider KEIN Account-Fehler und darf keine Rotation ausloesen.
Revert auf den Stand vor c4a49d9: Rotation nur noch bei echten Account-Fehlern
(Quota/Ban/ungueltig -> Cooldown -> naechster). debrid.ts + debrid.test.ts (inkl. des
dedizierten Per-Account-Timeout-Tests) zurueckgesetzt. 641 Tests gruen, tsc 9 (unveraendert).
WICHTIG: Behebt nur die von mir verursachte Regression — macht den Download NICHT von
selbst funktionsfaehig. Offene Frage (Mega-Web langsam-aber-funktioniert vs. Server-IP
geblockt) ist erst zu klaeren, bevor am eigentlichen Unrestrict-Timeout gedreht wird.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Schliesst die Coverage-Luecke zum v1.7.168-Fix (c4a49d9): der korrigierte Abort-Test
prueft nur Signal-Propagation, nicht die Kernlogik des Fix. Dieser Test (Fake-Timer)
faehrt den echten Rotationspfad: acc1 haengt bis sein Per-Account-Timeout feuert ->
30s-Cooldown -> Loop probiert acc2 -> Erfolg. Ohne den Fix (keine Abort-Quelle bei
fehlendem globalem Signal) wuerde acc1 ewig haengen -> Test-Timeout. 642 Tests gruen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Kernbug (User-Log, v1.7.168): "Unrestrict Timeout nach 60s" — Account 1 hing die
volle Zeit, acc2/acc3 wurden NIE versucht. Ursache: die gesamte Account-Rotation
lief unter EINEM geteilten ~60s-Signal (download-manager wickelt den ganzen
unrestrictLink in getUnrestrictTimeoutMs()); haengt acc1 bis es feuert, bricht die
ganze Rotation ab.
Fix (debrid.ts): jeder Account/Key bekommt im Rotations-Loop sein EIGENES Timeout
(PER_ACCOUNT_ATTEMPT_TIMEOUT_MS=25s, env RD_PER_ACCOUNT_TIMEOUT_MS, clamp 8-45s) via
AbortController + AbortSignal.any([global, attempt]). Catch: globaler signal.aborted
-> throw (Rotation stoppen); nur attemptController.signal.aborted -> 30s-Cooldown +
naechster Account. Ueber die Retry-Zyklen werden mit den Cooldowns alle Accounts erreicht.
Test: "aborts Mega web unrestrict when caller signal is cancelled" pruefte vorher
Objekt-Identitaet (.toBe(controller.signal)); der Per-Account-Timeout wrappt das Signal
aber zwingend (AbortSignal.any), die gereichte Instanz ist daher absichtlich nicht mehr
identisch. Umgestellt auf VERHALTEN: gereichtes Signal ist eine AbortSignal-Instanz und
propagiert das Caller-Cancel (aborted=true).
Recovered aus dem reset-weggesetzten Commit ae3ee1f (der andere Chat committete den Fix,
der Test brach, er resettete + hing). 641 Tests gruen, tsc unveraendert (9 pre-existing).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User sah im Item-Log nur "Link-Umwandlung gestartet" -> "Unrestrict Timeout
60s" -> "erneut Versuch 1/inf", aber nie welcher Account/Key wann probiert
wurde. Die Rotation lief nur in account-rotation.log + Panel.
Jetzt: AsyncLocalStorage-Item-Sink (parallel-sicher bei 8 gleichzeitigen
Unrestricts) leitet JEDEN Rotations-Event in das Log des betroffenen Items:
"Account-Rotation: Mega-Debrid Web - Account 1 (xy) wird versucht / fehl-
geschlagen (Timeout) -> Account 2". Damit ist im Item-Log direkt sichtbar,
ob acc2/acc3 ueberhaupt erreicht werden -> dient auch als Diagnose fuer den
vermuteten Timeout-Bug (kommt separat, falls das Log Stillstand zeigt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ergaenzt den Mechanism-Test um einen End-to-End-Test, der den echten
Produktionspfad runDeferredPostExtraction -> Rename -> Collect faehrt. Sperrt
die Verdrahtung: wuerde jemand das `true` an der Rename-Call-Site (12130)
entfernen, faellt dieser Test (frische Datei landet wieder unbenannt) — der
reine Mechanism-Test wuerde das nicht bemerken. Negativ-Gate verifiziert
(ohne `true` -> FAIL). 624 Tests gruen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Folge-Fund zu 18eada9 (Opus-Verifikation des deferFreshFiles-Konzepts):
18eada9 schloss den "frische Datei landet mit Original-Scene-Namen in der
Library"-Bug nur fuer den Hybrid-Pfad (deferFreshFiles=true + Mehrfach-Paesse).
Der finale Deferred-Pass blieb betroffen.
Root Cause (verifiziert via failing Test gegen HEAD):
- runDeferredPostExtraction macht Rename -> Collect (deferFreshFiles=false). Ist
eine Datei beim Deferred-Rename noch "frisch" (juenger als fileStabilizeMinAgeMs,
prod=2000ms) -- v.a. eine eben per Nested-Extraction geschriebene Datei --
ueberspringt der Frische-Gate sie, und der Collect moved sie mit Original-
Scene-Namen in die Library. collectMkvFilesToLibrary benennt selbst nicht um
(buildUniqueFlattenTargetPath, nur Flatten).
- Im Deferred-FINAL-Pass gibt es keinen concurrent Extractor-Write mehr
(Extraktion inkl. Nested ist awaited) -- der Frische-Gate ist dort ein False
Positive. Pre-existierender Gap (Frische-Skip aelter als 18eada9), auch
v1.7.162 betroffen.
Fix (minimal): treatFilesAsStable-Param durch autoRenameExtractedVideoFiles(Impl).
Der Deferred-Final-Pass ruft mit treatFilesAsStable=true -> Frische-Gate umgangen
-> alle Dateien werden umbenannt, bevor der Collect sie sammelt. Hybrid-Pfad
unangetastet (nutzt ...Impl mit Default false -> Frische-Skip bleibt aktiv).
Regressionstest: frische Datei im Deferred-Pass landet UMBENANNT in der Library.
623 Tests gruen, tsc unveraendert (9 pre-existing).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Das Bundle packte bisher alle logs-Unterordner rekursiv (addDirectoryIfExists)
→ tausende Per-Item-Logs → 200+ MB (User-Bundle: 4273 Item-Logs, 214 MB).
Zum Verschicken/Analysieren unhandlich.
Neue addRecentDirectoryFiles(): packt nur Dateien mit mtime in den letzten
8h. Package-Logs und Item-Logs nutzen das 8h-Fenster; Session-Logs (wenige
Dateien) weiterhin komplett. Haupt-Logs (rd_downloader.log, rename.log,
audit.log, session.log, trace.log) waren schon per addFileIfExists einzeln
gepackt und bleiben unveraendert. Live-Logs der aktiven Queue (laufende
Session) ebenfalls komplett.
Ergebnis: Bundle enthaelt alles fuer aktuelle Fehler + Rename-Probleme,
aber kein Bloat durch tausende alte Item-Logs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User-Report (verifiziert via Support-Bundle): pl3x-24hours.s01e07,
tmsf-burnnotice-s05e11-repack, -s05e15 landeten mit Original-Scene-Namen in der
Library statt umbenannt. Andere Episoden derselben Pakete (formatidentisch)
wurden korrekt umbenannt → kein Format-Problem, sondern Timing-Race.
Root Cause (aus Log-Timeline):
1. autoRenameExtractedVideoFilesImpl erfasste `now` EINMAL am Scan-Start. Bei
Hybrid-Extraktion werden weitere Dateien WÄHREND des Scans geschrieben →
deren mtime > now → negatives ageMs → der "Clock-Skew = stabil"-Zweig wertete
sie faelschlich als stabil → Rename mitten im Extractor-Write → EBUSY → 200ms-
Retry deferred.
2. Der MKV-Collect hatte KEINEN Frische-Skip und moved die Datei im Retry-Fenster
mit Original-Namen, bevor der Rename-Retry feuerte.
3. Rename + Collect liefen als zwei separate chainPackageFileOp-Ketten →
ueberlappende Hybrid-Runden konnten einen Collect zwischen Rename und Collect
einer anderen Runde einschieben.
Fix (3 Teile, scoped auf extractDir des Pakets — kein Shared-Library-Scan, nicht
das v1.7.107-Antipattern):
1. `now` wird PRO DATEI erfasst → frisch-geschriebene Dateien korrekt als "frisch"
erkannt und deferred (statt EBUSY-Rename mitten im Write).
2. collectMkvFilesToLibrary bekommt deferFreshFiles-Param: im Hybrid-Pfad werden
frische Dateien (juenger als fileStabilizeMinAgeMs) uebersprungen statt unbenannt
gemoved. Der finale Deferred-Pass (deferFreshFiles=false) sammelt sie nach
Stabilisierung ein (Safety-Net).
3. Hybrid-Pfad: Rename (Impl-Variante, kein Self-Chain) + Collect in EINER
chainPackageFileOp-Kette → atomar, kein Interleaving ueberlappender Runden.
Deferred-Pfad unangetastet (dort keine concurrent Extraktion). Regressionstest:
frische Datei wird im Hybrid-Collect deferred, vom finalen Pass gesammelt.
622 Tests gruen, tsc-Fehlerzahl unveraendert (9 pre-existing).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>