fix: use Railway native PG env vars for Alembic migrations

Prior approach (ADMIN_DATABASE_URL first) broke PR preview environments: fresh
Railway PostgreSQL instances have no resolutionflow_admin role yet, so the admin
URL fails before the create_db_roles migration can run (bootstrap deadlock).

New priority order in _alembic_sync_url():
1. PGHOST/PGUSER/PGPASSWORD/PGDATABASE — Railway auto-links these from the
   PostgreSQL service per-environment, giving correct superuser creds for every
   env including fresh PR preview DBs where no custom roles exist yet.
2. ADMIN_DATABASE_URL (resolutionflow_admin, BYPASSRLS, asyncpg→sync) — local
   dev and stable envs where the role already exists.
3. DATABASE_URL_SYNC — legacy fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-04-11 03:35:04 +00:00
parent 0107d2d896
commit 4273ed0e5c

View File

@@ -29,19 +29,35 @@ from app.models.session_branch import SessionBranch # noqa: F401
from app.models.fork_point import ForkPoint # noqa: F401
from app.models.session_handoff import SessionHandoff # noqa: F401
from app.models.session_resolution_output import SessionResolutionOutput # noqa: F401
import os
from app.core.config import settings
def _alembic_sync_url() -> str:
"""Return a psycopg2-compatible sync URL for Alembic.
Prefers ADMIN_DATABASE_URL (resolutionflow_admin, BYPASSRLS) converted to
a sync driver. Falls back to DATABASE_URL_SYNC for local dev where the
admin URL may not be configured separately.
Priority order:
1. Railway native PG env vars (PGHOST/PGPASSWORD/PGUSER/PGDATABASE) — these
are auto-linked per-environment by the Railway PostgreSQL service, so they
always carry the correct superuser credentials for the current environment
(production, PR preview, etc.), including fresh DBs with no custom roles yet.
2. ADMIN_DATABASE_URL (resolutionflow_admin, BYPASSRLS) converted to a sync
driver — used for local dev and stable environments where the role exists.
3. DATABASE_URL_SYNC — last resort / legacy fallback.
"""
pg_host = os.getenv("PGHOST")
pg_user = os.getenv("PGUSER")
pg_password = os.getenv("PGPASSWORD")
pg_db = os.getenv("PGDATABASE")
pg_port = os.getenv("PGPORT", "5432")
if all([pg_host, pg_user, pg_password, pg_db]):
return f"postgresql://{pg_user}:{pg_password}@{pg_host}:{pg_port}/{pg_db}"
admin_url = settings.ADMIN_DATABASE_URL
if admin_url and "+asyncpg" in admin_url:
return admin_url.replace("postgresql+asyncpg://", "postgresql://")
return settings.DATABASE_URL_SYNC