Error Recovery & Cleanup: Stuck Tickets, Resource Leaks, fehlende Aufräumlogik #78

Open
opened 2026-03-30 20:19:41 +00:00 by David · 0 comments
Collaborator

Beschreibung

Mehrere Stellen in der Pipeline haben unvollständige Fehlerbehandlung:
Tickets bleiben in Zwischenstatus stecken, Git-Branches und Lock-Files
werden nicht aufgeräumt, Subprozesse laufen nach Timeout weiter,
und DB-Status kann durch parallele Writes inkonsistent werden.

Hintergrund

Bei Fehlern in der Pipeline (Timeout, Netzwerkfehler, Git-Konflikte) muss
das System in einen definierten Zustand zurückkehren können. Aktuell
führen Partial Failures zu Zuständen, aus denen weder Retry noch
manuelles Eingreifen zuverlässig funktioniert.

Findings

1. Watchdog überschreibt laufende Pipeline (HIGH)

Datei: backend/main.py Zeile 68-93 + backend/services/pipeline.py

  • Watchdog setzt "running" Tickets nach 15min auf "failed"
  • Pipeline läuft aber ggf. noch weiter und committet danach
  • Status springt zwischen "failed" und "mr_created" hin und her
  • Fix: Atomare Status-Updates via UPDATE ... WHERE status = expected

2. Retry räumt Remote-Branch nicht auf (HIGH)

Datei: backend/services/pipeline.py Zeile 167-183

  • retry_ticket() setzt Status zurück, aber Branch existiert noch auf Remote
  • Nächster Run versucht denselben Branch → Git-Fehler oder doppelte Commits
  • Fix: Branch auf Remote löschen vor Retry, branch_name zurücksetzen

3. context_file._sync_to_db() überschreibt Status (HIGH)

Datei: backend/services/context_file.py Zeile 21-40

  • Sync liest Ticket frisch aus DB, modifiziert ticket_context, committet
  • Dabei werden zwischenzeitliche Status-Änderungen überschrieben
  • Fix: Gezieltes UPDATE ticket SET ticket_context = ... WHERE id = ... statt Query-Modify-Commit

4. Race Condition bei Branch-Erstellung (HIGH)

Datei: backend/services/preparation_engine.py Zeile 82-95

  • Kein DB-Lock auf Ticket vor Git-Operationen
  • Zwei parallele Requests können denselben Branch erstellen
  • Fix: SELECT FOR UPDATE auf Ticket vor Branch-Erstellung

5. kontext.md-Write nicht atomar (HIGH)

Datei: backend/services/preparation_engine.py Zeile 138-140

  • Direkte File-Write ohne Temp-File → Crash = korrupte kontext.md
  • Claude liest dann fehlerhafte Kontextdaten
  • Fix: Write to temp file + os.replace() (atomar auf POSIX)

6. Claude Runner: stdout unbegrenzt in Memory (HIGH)

Datei: backend/services/claude_runner.py Zeile 201-216

  • proc.communicate() buffert gesamten Output im RAM
  • Bei 50 Max-Turns und großen Repos: Hunderte MB möglich
  • Fix: Output in Temp-File streamen statt Memory-Buffer

7. Claude Runner: Child-Prozesse laufen nach Kill weiter (HIGH)

Datei: backend/services/claude_runner.py Zeile 208-216

  • proc.kill() tötet nur den Hauptprozess
  • Child-Prozesse (git, compiler) laufen weiter, hinterlassen Lock-Files
  • Fix: os.killpg(os.getpgid(proc.pid), SIGKILL) für Process-Group-Kill

8. Kein Cleanup nach Partial Failure (MEDIUM)

Datei: backend/services/pipeline.py

  • Branch erstellt + Commits gemacht, aber MR-Erstellung schlägt fehl
  • Working Tree bleibt dirty, Lock-Files bleiben
  • Fix: _cleanup_after_failure() Methode: git checkout ., git clean -fd, Branch löschen

9. WebSocket-Connections nicht aufgeräumt (MEDIUM)

Datei: backend/services/pipeline.py Zeile 35-47

  • Nur Connections die bei send_text() scheitern werden entfernt
  • Silent Disconnects bleiben im Set → Memory-Leak über Zeit
  • Fix: Periodische Cleanup-Logik oder Heartbeat-basierte Erkennung

10. Ungültige Status-Übergänge erlaubt (MEDIUM)

Datei: backend/api/tickets.py Zeile 212-233

  • /start Endpoint prüft nicht den aktuellen Status
  • Ticket mit Status "mr_created" kann erneut gestartet werden
  • Fix: Whitelist erlaubter Ausgangsstatus: nur "scored" und "klärfall"

11. Fehlende Git Return-Code-Validierung (MEDIUM)

Datei: backend/services/repo_analyzer.py Zeile 159-188

  • Zwischenschritte in create_branch() ignorieren Fehlercodes
  • checkout, pull --ff-only können still fehlschlagen
  • Fix: Return-Code prüfen und bei Fehler Exception werfen

12. Error-Messages zu generisch (MEDIUM)

Datei: backend/services/pipeline.py Zeile 118-127

  • User sieht nur "Pipeline error (see logs)" — Logs sind oft nicht zugänglich
  • Fix: Fehler-Step (scoring/prep/execution/review) und Error-Type in Message aufnehmen

Akzeptanzkriterien

  • Status-Updates sind atomar (UPDATE WHERE status = expected)
  • Retry löscht Remote-Branch und setzt branch_name zurück
  • context_file._sync_to_db() verwendet gezieltes SQL-UPDATE
  • Branch-Erstellung nutzt SELECT FOR UPDATE auf Ticket-Row
  • kontext.md wird atomar geschrieben (temp file + rename)
  • Claude Runner Output wird in Temp-File gestreamt
  • Timeout killt gesamte Process-Group, nicht nur Hauptprozess
  • _cleanup_after_failure() räumt Working Tree und Branches auf
  • /start-Endpoint validiert erlaubte Ausgangsstatus
  • Error-Messages enthalten Step-Name und Error-Type
  • Tests: Atomarer Status-Update bei Concurrent Access
  • Tests: Retry nach Failed Ticket (Branch Cleanup verifizieren)
  • Tests: /start mit ungültigem Status gibt 409 zurück
  • Tests: Timeout-Cleanup verifiziert dass keine Prozesse übrig bleiben

Technische Hinweise

  • Betroffene Dateien:
    • backend/services/pipeline.py (atomare Updates, Cleanup, Error-Messages)
    • backend/services/context_file.py (SQL-UPDATE statt ORM)
    • backend/services/preparation_engine.py (DB-Lock, atomarer Write)
    • backend/services/claude_runner.py (File-Streaming, Process-Group-Kill)
    • backend/services/repo_analyzer.py (Return-Code-Validierung)
    • backend/api/tickets.py (Status-Validierung)
    • backend/main.py (Watchdog-Interaktion mit atomaren Updates)
  • Ansatz: Schrittweise — erst atomare Status-Updates (#1, #3), dann Cleanup (#2, #5, #8),
    dann Resource-Management (#6, #7)
  • Migration nötig: Nein

Aufwand: L

## Beschreibung Mehrere Stellen in der Pipeline haben unvollständige Fehlerbehandlung: Tickets bleiben in Zwischenstatus stecken, Git-Branches und Lock-Files werden nicht aufgeräumt, Subprozesse laufen nach Timeout weiter, und DB-Status kann durch parallele Writes inkonsistent werden. ## Hintergrund Bei Fehlern in der Pipeline (Timeout, Netzwerkfehler, Git-Konflikte) muss das System in einen definierten Zustand zurückkehren können. Aktuell führen Partial Failures zu Zuständen, aus denen weder Retry noch manuelles Eingreifen zuverlässig funktioniert. ## Findings ### 1. Watchdog überschreibt laufende Pipeline (HIGH) **Datei:** `backend/main.py` Zeile 68-93 + `backend/services/pipeline.py` - Watchdog setzt "running" Tickets nach 15min auf "failed" - Pipeline läuft aber ggf. noch weiter und committet danach - Status springt zwischen "failed" und "mr_created" hin und her - **Fix:** Atomare Status-Updates via `UPDATE ... WHERE status = expected` ### 2. Retry räumt Remote-Branch nicht auf (HIGH) **Datei:** `backend/services/pipeline.py` Zeile 167-183 - `retry_ticket()` setzt Status zurück, aber Branch existiert noch auf Remote - Nächster Run versucht denselben Branch → Git-Fehler oder doppelte Commits - **Fix:** Branch auf Remote löschen vor Retry, `branch_name` zurücksetzen ### 3. context_file._sync_to_db() überschreibt Status (HIGH) **Datei:** `backend/services/context_file.py` Zeile 21-40 - Sync liest Ticket frisch aus DB, modifiziert `ticket_context`, committet - Dabei werden zwischenzeitliche Status-Änderungen überschrieben - **Fix:** Gezieltes `UPDATE ticket SET ticket_context = ... WHERE id = ...` statt Query-Modify-Commit ### 4. Race Condition bei Branch-Erstellung (HIGH) **Datei:** `backend/services/preparation_engine.py` Zeile 82-95 - Kein DB-Lock auf Ticket vor Git-Operationen - Zwei parallele Requests können denselben Branch erstellen - **Fix:** `SELECT FOR UPDATE` auf Ticket vor Branch-Erstellung ### 5. kontext.md-Write nicht atomar (HIGH) **Datei:** `backend/services/preparation_engine.py` Zeile 138-140 - Direkte File-Write ohne Temp-File → Crash = korrupte kontext.md - Claude liest dann fehlerhafte Kontextdaten - **Fix:** Write to temp file + `os.replace()` (atomar auf POSIX) ### 6. Claude Runner: stdout unbegrenzt in Memory (HIGH) **Datei:** `backend/services/claude_runner.py` Zeile 201-216 - `proc.communicate()` buffert gesamten Output im RAM - Bei 50 Max-Turns und großen Repos: Hunderte MB möglich - **Fix:** Output in Temp-File streamen statt Memory-Buffer ### 7. Claude Runner: Child-Prozesse laufen nach Kill weiter (HIGH) **Datei:** `backend/services/claude_runner.py` Zeile 208-216 - `proc.kill()` tötet nur den Hauptprozess - Child-Prozesse (git, compiler) laufen weiter, hinterlassen Lock-Files - **Fix:** `os.killpg(os.getpgid(proc.pid), SIGKILL)` für Process-Group-Kill ### 8. Kein Cleanup nach Partial Failure (MEDIUM) **Datei:** `backend/services/pipeline.py` - Branch erstellt + Commits gemacht, aber MR-Erstellung schlägt fehl - Working Tree bleibt dirty, Lock-Files bleiben - **Fix:** `_cleanup_after_failure()` Methode: `git checkout .`, `git clean -fd`, Branch löschen ### 9. WebSocket-Connections nicht aufgeräumt (MEDIUM) **Datei:** `backend/services/pipeline.py` Zeile 35-47 - Nur Connections die bei `send_text()` scheitern werden entfernt - Silent Disconnects bleiben im Set → Memory-Leak über Zeit - **Fix:** Periodische Cleanup-Logik oder Heartbeat-basierte Erkennung ### 10. Ungültige Status-Übergänge erlaubt (MEDIUM) **Datei:** `backend/api/tickets.py` Zeile 212-233 - `/start` Endpoint prüft nicht den aktuellen Status - Ticket mit Status "mr_created" kann erneut gestartet werden - **Fix:** Whitelist erlaubter Ausgangsstatus: nur "scored" und "klärfall" ### 11. Fehlende Git Return-Code-Validierung (MEDIUM) **Datei:** `backend/services/repo_analyzer.py` Zeile 159-188 - Zwischenschritte in `create_branch()` ignorieren Fehlercodes - `checkout`, `pull --ff-only` können still fehlschlagen - **Fix:** Return-Code prüfen und bei Fehler Exception werfen ### 12. Error-Messages zu generisch (MEDIUM) **Datei:** `backend/services/pipeline.py` Zeile 118-127 - User sieht nur "Pipeline error (see logs)" — Logs sind oft nicht zugänglich - **Fix:** Fehler-Step (scoring/prep/execution/review) und Error-Type in Message aufnehmen ## Akzeptanzkriterien - [ ] Status-Updates sind atomar (`UPDATE WHERE status = expected`) - [ ] Retry löscht Remote-Branch und setzt `branch_name` zurück - [ ] `context_file._sync_to_db()` verwendet gezieltes SQL-UPDATE - [ ] Branch-Erstellung nutzt `SELECT FOR UPDATE` auf Ticket-Row - [ ] kontext.md wird atomar geschrieben (temp file + rename) - [ ] Claude Runner Output wird in Temp-File gestreamt - [ ] Timeout killt gesamte Process-Group, nicht nur Hauptprozess - [ ] `_cleanup_after_failure()` räumt Working Tree und Branches auf - [ ] `/start`-Endpoint validiert erlaubte Ausgangsstatus - [ ] Error-Messages enthalten Step-Name und Error-Type - [ ] Tests: Atomarer Status-Update bei Concurrent Access - [ ] Tests: Retry nach Failed Ticket (Branch Cleanup verifizieren) - [ ] Tests: `/start` mit ungültigem Status gibt 409 zurück - [ ] Tests: Timeout-Cleanup verifiziert dass keine Prozesse übrig bleiben ## Technische Hinweise - Betroffene Dateien: - `backend/services/pipeline.py` (atomare Updates, Cleanup, Error-Messages) - `backend/services/context_file.py` (SQL-UPDATE statt ORM) - `backend/services/preparation_engine.py` (DB-Lock, atomarer Write) - `backend/services/claude_runner.py` (File-Streaming, Process-Group-Kill) - `backend/services/repo_analyzer.py` (Return-Code-Validierung) - `backend/api/tickets.py` (Status-Validierung) - `backend/main.py` (Watchdog-Interaktion mit atomaren Updates) - Ansatz: Schrittweise — erst atomare Status-Updates (#1, #3), dann Cleanup (#2, #5, #8), dann Resource-Management (#6, #7) - Migration nötig: Nein ## Aufwand: L
Sign in to join this conversation.
No description provided.