From 0723407a6d556479591c570a5519a080dc2b0914 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Apr 2026 22:19:25 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20harden=20Railway=20startup=20=E2=80=94?= =?UTF-8?q?=20fault-tolerant=20lifespan=20+=20DB=20retry=20+=20credential?= =?UTF-8?q?=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wrap PostHog/DLQ init in try/except so startup survives missing services - Delay self-improvement worker 30s to reduce startup load - Run init_db() for ALL database types (was SQLite-only, skipping PostgreSQL) - Add 3-attempt retry with backoff in init_db() for Railway DB startup race - Fix FastAPI deprecation: regex → pattern in intelligence.py - Remove hardcoded Ultramsg credentials from auto_pipeline.py https://claude.ai/code/session_01W1rJthWDkasijTdXCfxVHs --- .../backend/app/api/v1/intelligence.py | 2 +- salesflow-saas/backend/app/database.py | 31 ++++++++---- salesflow-saas/backend/app/main.py | 48 +++++++++++-------- .../backend/app/services/auto_pipeline.py | 4 +- 4 files changed, 52 insertions(+), 33 deletions(-) diff --git a/salesflow-saas/backend/app/api/v1/intelligence.py b/salesflow-saas/backend/app/api/v1/intelligence.py index 1fed1941..2afff994 100644 --- a/salesflow-saas/backend/app/api/v1/intelligence.py +++ b/salesflow-saas/backend/app/api/v1/intelligence.py @@ -126,7 +126,7 @@ async def alert_stats(tenant_id: str): @router.get("/digest", summary="Generate Arabic alert digest") async def generate_digest(tenant_id: str, user_id: Optional[str] = None, - period: str = Query(default="daily", regex="^(daily|weekly)$")): + period: str = Query(default="daily", pattern="^(daily|weekly)$")): return await get_alert_delivery().generate_digest(tenant_id, user_id, period) diff --git a/salesflow-saas/backend/app/database.py b/salesflow-saas/backend/app/database.py index d962bfdb..77853a80 100644 --- a/salesflow-saas/backend/app/database.py +++ b/salesflow-saas/backend/app/database.py @@ -70,13 +70,24 @@ async def get_db(): async def init_db(): import app.models # noqa: F401 — register all models on Base.metadata before create_all - async with engine.begin() as conn: - if not IS_SQLITE: - for ext in ["CREATE EXTENSION IF NOT EXISTS vector", - "CREATE EXTENSION IF NOT EXISTS pg_trgm"]: - try: - await conn.execute(text(ext)) - except Exception: - pass - await conn.run_sync(Base.metadata.create_all) - print("✅ Database initialized") + for attempt in range(3): + try: + async with engine.begin() as conn: + if not IS_SQLITE: + for ext in ["CREATE EXTENSION IF NOT EXISTS vector", + "CREATE EXTENSION IF NOT EXISTS pg_trgm"]: + try: + await conn.execute(text(ext)) + except Exception: + pass + await conn.run_sync(Base.metadata.create_all) + print("✅ Database initialized") + return + except Exception as e: + if attempt < 2: + import asyncio + print(f"⚠️ DB init attempt {attempt + 1} failed: {e}, retrying in 3s...") + await asyncio.sleep(3) + else: + print(f"❌ DB init failed after 3 attempts: {e}") + raise diff --git a/salesflow-saas/backend/app/main.py b/salesflow-saas/backend/app/main.py index 1e5b9be4..a525ce9c 100644 --- a/salesflow-saas/backend/app/main.py +++ b/salesflow-saas/backend/app/main.py @@ -50,18 +50,22 @@ async def lifespan(app: FastAPI): stop_event = asyncio.Event() async def _self_improvement_worker(): + await asyncio.sleep(30) while not stop_event.is_set(): - self_improvement_flow.run( - tenant_id="system_tenant", - input_state={ - "signals": [], - "bottlenecks": [], - "experiments": [{"name": "always-on-ab-loop"}], - "ab_results": {}, - "governance_passed": True, - "promoted": True, - }, - ) + try: + self_improvement_flow.run( + tenant_id="system_tenant", + input_state={ + "signals": [], + "bottlenecks": [], + "experiments": [{"name": "always-on-ab-loop"}], + "ab_results": {}, + "governance_passed": True, + "promoted": True, + }, + ) + except Exception: + pass await asyncio.sleep(max(60, settings.SELF_IMPROVEMENT_INTERVAL_SECONDS)) worker_task = asyncio.create_task(_self_improvement_worker()) @@ -72,16 +76,20 @@ async def lifespan(app: FastAPI): print(f" LLM Primary: {settings.LLM_PRIMARY_PROVIDER}") print(f" LLM Fallback: {settings.LLM_FALLBACK_PROVIDER}") - # Initialize PostHog - from app.services.posthog_client import get_posthog - ph = get_posthog() - print(f" PostHog: {'enabled' if ph._enabled else 'disabled (no API key)'}") + try: + from app.services.posthog_client import get_posthog + ph = get_posthog() + print(f" PostHog: {'enabled' if getattr(ph, '_enabled', False) else 'disabled (no API key)'}") + except Exception as e: + print(f" PostHog: init failed ({e})") - # Initialize DLQ - from app.services.dlq import dlq - print(" DLQ: initialized") - if IS_SQLITE: - await init_db() + try: + from app.services.dlq import dlq # noqa: F841 + print(" DLQ: initialized") + except Exception as e: + print(f" DLQ: init failed ({e})") + + await init_db() yield # Shutdown stop_event.set() diff --git a/salesflow-saas/backend/app/services/auto_pipeline.py b/salesflow-saas/backend/app/services/auto_pipeline.py index 34e6c1cf..743ab573 100644 --- a/salesflow-saas/backend/app/services/auto_pipeline.py +++ b/salesflow-saas/backend/app/services/auto_pipeline.py @@ -115,8 +115,8 @@ class WhatsAppMessenger: """Send messages via Ultramsg API.""" def __init__(self): - self.instance_id = os.getenv("ULTRAMSG_INSTANCE_ID", "instance168132") - self.token = os.getenv("ULTRAMSG_TOKEN", "7azj2ss74wpg9jwp") + self.instance_id = os.getenv("ULTRAMSG_INSTANCE_ID", "") + self.token = os.getenv("ULTRAMSG_TOKEN", "") self.api_base = f"https://api.ultramsg.com/{self.instance_id}" async def send_message(self, phone: str, message: str) -> Dict: