fix(database): skip create_all for Alembic-tracked databases to prevent OperationalError on webhook_configs

When init_db() called Base.metadata.create_all() before Alembic migrations,
the ORM model created the webhook_configs table. Alembic migration 009 then
failed with OperationalError: table webhook_configs already exists.

Fix: check for alembic_version table before calling create_all(). Tracked
databases skip create_all and let Alembic handle all schema changes instead.

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-02 10:25:34 +00:00
parent c0a3753e55
commit b87dd6083c
2 changed files with 98 additions and 10 deletions
+19 -6
View File
@@ -26,8 +26,14 @@ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def init_db() -> None:
"""
Ensures the SQLite database file and its parent directory exist (if using sqlite).
Then runs Base.metadata.create_all(bind=engine) to initialize tables and
applies any pending Alembic migrations.
Then initializes tables and applies any pending Alembic migrations:
- Fresh/legacy databases (no ``alembic_version`` table): creates all tables via
``Base.metadata.create_all()``, then stamps the Alembic version to ``head``.
- Alembic-tracked databases (``alembic_version`` present): skips ``create_all()``
and applies pending migrations via ``alembic upgrade head``. Skipping
``create_all()`` prevents an ``OperationalError`` when the ORM model defines a
table (e.g. ``webhook_configs``) that a pending migration also tries to create.
"""
# 1. Parse the DB URL to see if it's sqlite
url = make_url(DB_URL)
@@ -47,12 +53,19 @@ def init_db() -> None:
logger.info(f"Creating new SQLite database file at {database_path}")
open(database_path, "a").close()
# 5. Now create tables if they don't exist yet
# 5. Create tables only for fresh/legacy databases not yet tracked by Alembic.
# For Alembic-tracked databases, skip create_all to avoid conflicts where
# the ORM model would create a table (e.g. webhook_configs) that a pending
# Alembic migration also tries to create, causing an OperationalError.
try:
Base.metadata.create_all(bind=engine)
logger.info("Database initialization complete (tables created if not exist).")
from sqlalchemy import inspect
# 6. Run Alembic migrations for existing databases
table_names = inspect(engine).get_table_names()
if "alembic_version" not in table_names:
Base.metadata.create_all(bind=engine)
logger.info("Database initialization complete (tables created if not exist).")
# 6. Run Alembic migrations (stamps fresh/legacy DBs to head, upgrades tracked DBs)
_run_alembic_upgrade(engine)
except exc.SQLAlchemyError as e:
logger.error(f"Error initializing database: {e}")