From c0eba9c6283ca61e74e7e28077170fca25cf1861 Mon Sep 17 00:00:00 2001 From: James Pine Date: Thu, 23 Apr 2026 19:10:00 -0700 Subject: [PATCH] fix(db): graceful fallback when SQLite < 3.35 on MCP bindings migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SQLite gained ALTER TABLE … DROP COLUMN in 3.35 (Mar 2021). Production PyInstaller builds bundle Python 3.12 which links to SQLite 3.40+ so that path is always safe, but a dev running the backend directly on Ubuntu 20.04 (3.31) or Debian 11 (3.34) would crash on first startup trying to drop the legacy default_intent column. Add _supports_drop_column(engine) — returns True on non-SQLite dialects (Postgres / MySQL have supported DROP COLUMN for decades) and gates on the runtime sqlite_version for SQLite. When unsupported, log a warning and leave the unused column in place: SQLAlchemy only maps declared columns, so a stray default_intent column does no reads or writes and can't interfere with runtime behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/database/migrations.py | 35 ++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/backend/database/migrations.py b/backend/database/migrations.py index fedba13a..a82e451a 100644 --- a/backend/database/migrations.py +++ b/backend/database/migrations.py @@ -252,10 +252,37 @@ def _migrate_mcp_bindings(engine, inspector, tables: set[str]) -> None: "default_personality", ) if "default_intent" in columns: - with engine.connect() as conn: - conn.execute(text("ALTER TABLE mcp_client_bindings DROP COLUMN default_intent")) - conn.commit() - logger.info("Dropped legacy default_intent column from mcp_client_bindings") + if _supports_drop_column(engine): + with engine.connect() as conn: + conn.execute(text("ALTER TABLE mcp_client_bindings DROP COLUMN default_intent")) + conn.commit() + logger.info("Dropped legacy default_intent column from mcp_client_bindings") + else: + # ALTER TABLE … DROP COLUMN on SQLite requires 3.35+ (Mar + # 2021). Production PyInstaller builds bundle Python 3.12 + # which links to SQLite 3.40+; this branch only fires for + # dev environments running the backend directly against an + # old system SQLite (Ubuntu 20.04 = 3.31, Debian 11 = 3.34). + # Leaving the unused column in place is harmless — the ORM + # only maps declared columns, so a stray one does no work + # and gets no reads or writes. + import sqlite3 + + logger.warning( + "SQLite %s too old to DROP COLUMN (need 3.35+); leaving unused default_intent column on mcp_client_bindings in place.", + sqlite3.sqlite_version, + ) + + +def _supports_drop_column(engine) -> bool: + """Whether ``ALTER TABLE … DROP COLUMN`` is supported by the dialect + + runtime. Non-SQLite dialects (Postgres, MySQL) have supported it for + decades; SQLite only gained the feature in 3.35.""" + if engine.dialect.name != "sqlite": + return True + import sqlite3 + + return tuple(int(p) for p in sqlite3.sqlite_version.split(".")[:3]) >= (3, 35, 0) def _normalize_storage_paths(engine, tables: set[str]) -> None: