MHSanaei/3x-ui · error
AutoMigrate %T: %w
Error message
AutoMigrate %T: %w
What it means
MigrateData creates the destination schema by looping migrationModels() through dst.AutoMigrate; a failure is wrapped with the offending model's type. The Postgres connection is fine but DDL failed — usually permissions, a pre-existing conflicting table shape, or an unsupported type/index on that PG version. The transaction copy phase never started, so no data was touched.
Source
Thrown at internal/database/migrate_data.go:104
return err
}
defer srcSQL.Close()
dst, err := gorm.Open(postgres.Open(dstDSN), &gorm.Config{Logger: logger.Discard})
if err != nil {
return fmt.Errorf("open postgres destination: %w", err)
}
dstSQL, err := dst.DB()
if err != nil {
return err
}
defer dstSQL.Close()
dstSQL.SetConnMaxLifetime(time.Hour)
log.Println("Creating destination schema...")
for _, m := range migrationModels() {
if err := dst.AutoMigrate(m); err != nil {
return fmt.Errorf("AutoMigrate %T: %w", m, err)
}
}
totalRows := 0
txErr := dst.Transaction(func(tx *gorm.DB) error {
// AutoMigrate re-creates the legacy client_traffics -> inbounds foreign key,
// but the running panel drops it (see dropLegacyForeignKeys) and tolerates
// client_traffics rows whose inbound was deleted. Drop it here too so copying
// such orphaned rows can't fail with an fk_inbounds_client_stats violation.
if err := tx.Exec("ALTER TABLE client_traffics DROP CONSTRAINT IF EXISTS fk_inbounds_client_stats").Error; err != nil {
return fmt.Errorf("drop legacy foreign key: %w", err)
}
// Empty the destination tables before copying: a fresh PostgreSQL DB
// already holds an auto-seeded admin (id=1) from any prior panel start,
// so a plain INSERT with explicit ids would collide on users_pkey. Only
// the panel's own tables are cleared, and a failure anywhere in this
// transaction rolls the clear back with everything else.View on GitHub (pinned to ad32144c42)
Solutions
- Read the wrapped error for the failing model and SQLSTATE — 42501 means permissions, 42P07/42701 mean pre-existing conflicting objects.
- Grant the migration role CREATE/ALTER on the target schema, or point dstDSN at the owner role.
- If retrying after a partial attempt, drop the panel's tables in the destination (data is disposable at this stage) and re-run the migration cleanly.
- Verify the Postgres version meets the panel's minimum (see docs) before migrating.
Example fix
-- before: role without DDL rights GRANT CONNECT ON DATABASE xui TO migrator; -- after GRANT CONNECT, CREATE ON DATABASE xui TO migrator; GRANT ALL ON SCHEMA public TO migrator;
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: the role must be able to create/alter tables in the target schema.
var canCreate bool
if err := dstSQL.QueryRowContext(ctx, `SELECT has_database_privilege(current_user, current_database(), 'CREATE')`).Scan(&canCreate); err != nil || !canCreate {
return errors.New("migration role lacks CREATE on target database")
} Try / catch
if err := database.MigrateData(src, dsn); err != nil {
if strings.Contains(err.Error(), "AutoMigrate") {
// schema DDL failed; no data copied. Fix grants or drop conflicting tables, then re-run.
}
return err
} Prevention
- Migrate with the destination database owner role, or grant CREATE/ALTER on the schema first.
- Start from an empty target database; drop half-migrated panel tables before re-running.
- Confirm the Postgres version meets the panel's minimum before migrating.
When it happens
Trigger: Destination DB user lacks CREATE/ALTER on the schema; migrating into a DB whose tables were created by an older panel version with incompatible column types; PG version too old for an index/construction AutoMigrate emits.
Common situations: Managed Postgres with a restricted role; re-running a migration into a half-migrated DB; PG 11/12 vs newer features.
Related errors
- destination DSN is required
- source sqlite not found at %s: %w
- open postgres destination: %w
- drop legacy foreign key: %w
- clear destination tables: %w
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/55d589237dd30f38.
Report an issue: GitHub.