Files
archivmail/features/PROJ-73-restliche-crash-haertung.md
sysopsandClaude Sonnet 5 d1b4497893 fix(PROJ-79): next lint kaputt seit Next-16-Upgrade repariert + 30 Findings gefixt
npm run lint rief next lint auf, das es in Next.js 16 nicht mehr gibt —
seit dem Next-16-Upgrade lief effektiv gar kein Lint mehr. Umgestellt auf
eslint . mit Flat-Config (eslint.config.mjs statt .eslintrc.json). Der
dadurch wieder sichtbare Lint-Lauf zeigte 30 Findings (25 Fehler,
5 Warnungen), alle gefixt:

- 19x react-hooks/set-state-in-effect: Loading-States wo möglich als
  echte Ableitung statt eigenem Effect-State (use-mobile.tsx komplett auf
  useSyncExternalStore umgebaut), sonst async-Wrapper mit Cancel-Guard um
  bestehende Loader — Timing/Ladeanzeige unverändert.
- react-hooks/refs (useSearch.ts): Ref-Schreibzugriff aus dem Render in
  einen Effect verschoben.
- 4x no-html-link-for-pages: <a href> durch next/link ersetzt in
  admin/login, forgot-password, signup.
- Rest (exhaustive-deps, no-img-element, unused disable) einzeln gefixt.
- 4 bewusst belassene disable-Kommentare mit Begründung (shadcn/ui-Datei,
  QR-Code-data-URL, Full-Reload nach Auth laut Projektregel).

eslint-Major-Upgrade auf 10 selbst bleibt blockiert: eslint-plugin-react/
jsx-a11y/import unterstützen ESLint 10 in ihrer aktuellen Latest-Version
noch nicht (Crash beim Laden), siehe Feature-Spec PROJ-79.

Verifiziert auf 132 (Build-Sandbox, kein Live-Deploy): npm ci/tsc/lint/
build grün, 8 Kern-Routen per Standalone-Server auf HTTP 200 geprüft.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
2026-08-05 21:14:17 +02:00

3.8 KiB

id, title, status, created
id title status created
PROJ-73 Restliche Crash-Härtung (Upload-Job-Status bei Panic, fehlende nil-Checks) Deployed 2026-08-05

Problem

Nach der großen Crash-Robustheit-Härtung (Commit 798cb28) sind zwei kleinere Lücken bewusst offen geblieben:

  1. runUploadJob (internal/api/upload.go) läuft jetzt zwar dank internal/safego nicht mehr prozessfatal bei einem Panic, der Job-Status bleibt aber dauerhaft auf "running" hängen — der Nutzer bekommt nie eine Fehlermeldung im Upload-UI, der Job wirkt wie ein hängender Prozess.
  2. internal/api/ocr_handlers.go:66 (s.users.GetByUsername(...)) und analoge Stellen dereferenzieren das Ergebnis ohne expliziten u == nil- Check. Aktuell liefert kein Store (nil, nil), daher kein akuter Bug — aber ungeschützt gegen künftige Store-Implementierungen oder Refactorings.

Lösung (Vorschlag)

  1. In runUploadJob: defer einbauen, der bei recover() != nil den Job-Status auf "error" setzt (inkl. Fehlermeldung), statt nur zu loggen.
  2. Alle GetByUsername-Aufrufstellen (grep GetByUsername über internal/api/) auf if u == nil { ... }-Guard nach dem err == nil- Check prüfen und ergänzen wo fehlend.

Implementation Notes

1. Upload-Job-Status bei Panicinternal/api/upload.go:148-162

runUploadJob bekommt direkt nach ctx := context.Background() ein defer func(){ if rec := recover(); rec != nil { ... } }():

  • setzt unter job.mu Status = "error" und ErrMsg,
  • gibt den Panic anschließend per panic(rec) weiter, damit safego.Run ihn wie bisher mit Task-Namen loggt (Prozess bleibt stabil).

Bewusste Abweichung vom Spec-Vorschlag: die ErrMsg enthält nicht den Panic-Wert, sondern den generischen Text „Import wegen eines internen Fehlers abgebrochen". Ein Panic-Wert kann Fragmente von Mail-Inhalten transportieren und ErrMsg wird über /upload/progress/{jobID} ans Frontend ausgeliefert (DSGVO). Die Details stehen im Server-Log.

Feldnamen sind konsistent zum bestehenden Muster (Status "running"/"done"/ "error", ErrMsg mit JSON-Tag error_msg), snapshot() liefert sie bereits unverändert ans Frontend — keine API-Änderung nötig.

2. nil-Guards nach GetByUsername — alle 13 Aufrufstellen in internal/api/, jeweils nur die vorhandene if err != nil-Bedingung erweitert (kein neuer Fehlerpfad, damit Statuscodes/Logging unverändert bleiben):

Datei:Zeile neue Bedingung
restore_handlers.go:43, :140 err != nil || user == nil (500)
search_handlers.go:429, :497 err != nil || u == nil || !mailBelongsToUser(...) (403)
export.go:373 err != nil || u == nil || !mailBelongsToUser(...) (403)
export.go:472 err != nil || u == nil (500)
smtpout_handlers.go:103 err != nil || u == nil || u.Email == "" (400)
ocr_handlers.go:66 err != nil || u == nil || !mailBelongsToUser(...) (403)
auth_handlers.go:106 err != nil || user == nil (500)
profile_handlers.go:36, :113, :183 err != nil || user == nil (500)
ediscovery.go:134 err != nil || u == nil (500)

Alle Pfade, die eine Zugriffsprüfung machen (Search/Export/OCR), fallen bei u == nil auf „access denied" zurück, nicht auf „erlaubt" — fail-closed.

Lokal kein go build möglich (kein Go-Toolchain), nur statische Prüfung.

Deployed auf 132 am 2026-08-05.

Deployed auf 131 (Produktiv) am 2026-08-05.

Acceptance Criteria

  • Upload-Job zeigt nach einem simulierten Panic im Verarbeitungspfad Status "error" mit Fehlermeldung statt dauerhaft "running".
  • Alle GetByUsername-Stellen in internal/api/ haben einen expliziten nil-Guard.
  • go build ./... && go vet ./... auf 132 fehlerfrei für geänderte Dateien.