From 55b53d5bb24eaea01b8183248b8a7554011af8c3 Mon Sep 17 00:00:00 2001 From: Thomas Faour Date: Wed, 22 Jul 2026 10:48:36 -0400 Subject: [PATCH] Fix migration runner crashing on a genuinely fresh database _migration_1() is Base.metadata.create_all() -- it already builds today's full schema straight from models.py. Every migration after it is an incremental ALTER/UPDATE meant to bring an *existing* install forward from an older version; replaying them against a brand-new database collided with columns create_all had already added ("duplicate column name"), crashing on first boot. Found while testing the device-status-bar change against a scratch DB. Every real deployment has been migrating forward incrementally since before this bug existed, so it never showed up in practice -- but any brand-new install would have hit it. Fresh databases now jump straight to the latest schema_version after create_all; existing databases keep applying whichever migrations are still pending, same as before. --- server/app/migration.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/server/app/migration.py b/server/app/migration.py index 17a130f..267c732 100644 --- a/server/app/migration.py +++ b/server/app/migration.py @@ -94,17 +94,23 @@ def run_migrations() -> None: with engine.begin() as conn: conn.execute(text("CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL)")) row = conn.execute(text("SELECT version FROM schema_version")).fetchone() - current = row[0] if row else 0 - for version, fn in MIGRATIONS: - if version > current: - logger.info("Applying schema migration %d", version) - fn(conn) - if row is None: - conn.execute( - text("INSERT INTO schema_version (version) VALUES (:v)"), {"v": version} - ) - row = (version,) - else: + if row is None: + # Brand new database: _migration_1's create_all() already + # produces today's full schema straight from models.py. + # Every migration after it is an incremental ALTER/UPDATE + # meant to bring an *existing* install forward -- replaying + # those here would just collide with columns create_all + # already added (e.g. "duplicate column name"). Jump + # straight to the latest version instead. + _migration_1(conn) + latest = MIGRATIONS[-1][0] + conn.execute(text("INSERT INTO schema_version (version) VALUES (:v)"), {"v": latest}) + else: + current = row[0] + for version, fn in MIGRATIONS: + if version > current: + logger.info("Applying schema migration %d", version) + fn(conn) conn.execute(text("UPDATE schema_version SET version = :v"), {"v": version}) _ensure_frame_one() _ensure_server_settings()