MHSanaei/3x-ui · critical
upgrade panel schema for %T: %w
Error message
upgrade panel schema for %T: %w
What it means
Thrown by PrepareSQLiteForMigration when AutoMigrate(m) fails while upgrading the old SQLite backup to the current panel schema (so the row copy can read newer tables/columns), and the error is not an ignorable duplicate-column condition. This runs on the SQLite source copy before migration starts; a failure aborts safely with nothing written to Postgres.
Source
Thrown at internal/database/migrate_data.go:341
func PrepareSQLiteForMigration(dbPath string) error {
gdb, err := gorm.Open(sqlite.Open(dbPath+"?_busy_timeout=10000"), &gorm.Config{Logger: logger.Discard})
if err != nil {
return err
}
sqlDB, err := gdb.DB()
if err != nil {
return err
}
defer sqlDB.Close()
for _, table := range []string{"users", "settings", "inbounds"} {
if !sqliteTableExists(sqlDB, table) {
return fmt.Errorf("not a 3x-ui panel database: required table %q is missing", table)
}
}
for _, m := range migrationModels() {
if err := gdb.AutoMigrate(m); err != nil && !isIgnorableDuplicateColumnErr(gdb, err, m) {
return fmt.Errorf("upgrade panel schema for %T: %w", m, err)
}
}
return nil
}
View on GitHub (pinned to ad32144c42)
Solutions
- Note the model %T in the message, open the backup with sqlite3, and compare that table's PRAGMA table_info against the model's expected columns/types.
- If the backup is several versions behind, first restore it into a temporary panel of an intermediate version, let it upgrade, back it up again, then migrate.
- Close other processes holding the file and retry.
- Keep an untouched copy of the backup — AutoMigrate mutates the source file, so always prepare a copy, never the only original.
Defensive patterns
Strategy: try-catch
Validate before calling
// snapshot the backup before PrepareSQLiteForMigration mutates it
cpSrc := dbPath + ".work"
if err := copyFile(dbPath, cpSrc); err != nil { return err }
if err := PrepareSQLiteForMigration(cpSrc); err != nil { /* original untouched */ } Try / catch
err := PrepareSQLiteForMigration(cpPath)
if err != nil && strings.Contains(err.Error(), "upgrade panel schema") {
// restore into an intermediate panel version first, re-backup, then retry with a fresh copy
} Prevention
- Operate on a copy — AutoMigrate modifies the source file in place.
- Step very old backups through intermediate panel versions.
- Keep no other process (SQLite browser) open on the file.
When it happens
Trigger: A backup from a panel version whose column types conflict with current models (e.g. an id or timestamp column stored with an incompatible type/affinity); a schema hand-edited at runtime; the file locked by another process; a duplicate-column error not matching isIgnorableDuplicateColumnErr's accepted shapes.
Common situations: Migrating backups skipped across many major versions (very old panel → current); DBs previously touched by forks or manual ALTERs; running the preparation while the file is open in a SQLite browser.
Related errors
- source sqlite not found at %s: %w
- open sqlite source: %w
- copy %T: %w
- open sqlite destination: %w
- not a 3x-ui panel database: required table %q is missing
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/8b40380cef4d1118.
Report an issue: GitHub.