fix(eu-umbau): SQLite-Parallelitaet der Doppelspur abgesichert

busy_timeout von 5s auf 30s (zwei gleichzeitig schreibende Lanes) und
'database is locked' im Orchestrator als transienter Fehler mit Retry
statt als permanenter Abbruch. Gefunden im Doppelspur-Paralleltest
(Lage-Summary-Update kollidierte mit der Schreiblast der zweiten Lane).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-07-30 20:39:41 +00:00
Ursprung 5fffca614a
Commit 676741b1c4
2 geänderte Dateien mit 12 neuen und 2 gelöschten Zeilen

Datei anzeigen

@@ -616,6 +616,13 @@ class AgentOrchestrator:
def _is_transient_cli(err: Exception) -> bool:
return isinstance(err, ClaudeCliError) and err.error_type in ("rate_limit", "timeout")
def _is_db_locked(err: Exception) -> bool:
# Seit der Doppelspur (Phase 2) koennen sich parallele Lanes beim
# Schreiben auf die SQLite-DB kurz blockieren. Das ist transient
# und einen Retry wert, kein permanenter Fehler.
import sqlite3 as _sqlite3
return isinstance(err, _sqlite3.OperationalError) and "locked" in str(err).lower()
# Optionales globales Ventil (Default aus): begrenzt gleichzeitige Recherchen.
sem = self._global_sem
if sem is not None:
@@ -657,7 +664,7 @@ class AgentOrchestrator:
break
# Transiente Fehler: Retry bis 3x
if isinstance(e, TRANSIENT_ERRORS) or _is_transient_cli(e):
if isinstance(e, TRANSIENT_ERRORS) or _is_transient_cli(e) or _is_db_locked(e):
last_error = e
kind = e.error_type if isinstance(e, ClaudeCliError) else type(e).__name__
logger.warning(f"Transienter Fehler [{kind}] bei Lage {incident_id} (Versuch {attempt + 1}/3): {e}")