diff --git a/src/agents/orchestrator.py b/src/agents/orchestrator.py index fa060c2..735190a 100644 --- a/src/agents/orchestrator.py +++ b/src/agents/orchestrator.py @@ -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}") diff --git a/src/database.py b/src/database.py index 7afcc19..913ac83 100644 --- a/src/database.py +++ b/src/database.py @@ -392,7 +392,10 @@ async def get_db() -> aiosqlite.Connection: db.row_factory = aiosqlite.Row await db.execute("PRAGMA journal_mode=WAL") await db.execute("PRAGMA foreign_keys=ON") - await db.execute("PRAGMA busy_timeout=5000") + # 30s statt 5s seit der Doppelspur (EU-Umbau Phase 2). Zwei gleichzeitig + # laufende Refreshes derselben Organisation schreiben parallel in die DB, + # 5s reichten dabei nicht immer (database is locked beim Summary-Update). + await db.execute("PRAGMA busy_timeout=30000") return db