MHSanaei/3x-ui · critical
clear destination tables: %w
Error message
clear destination tables: %w
What it means
Thrown when truncatePostgresTables fails: it runs a single `TRUNCATE TABLE "users", ... RESTART IDENTITY CASCADE` over every migrated table before the row copy, because a fresh PostgreSQL DB already holds an auto-seeded admin (id=1) that would collide on users_pkey. The clear runs inside the copy transaction, so any failure here rolls everything back and the destination keeps its pre-migration contents.
Source
Thrown at internal/database/migrate_data.go:124
}
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.
if err := truncatePostgresTables(tx, migrationModels()); err != nil {
return fmt.Errorf("clear destination tables: %w", err)
}
for _, m := range migrationModels() {
n, err := copyTable(src, tx, m)
if err != nil {
return fmt.Errorf("copy %T: %w", m, err)
}
totalRows += n
log.Printf(" %-32s %d rows", reflect.TypeOf(m).Elem().Name(), n)
}
return nil
})
if txErr != nil {
return txErr
}
// setval is never rolled back by PostgreSQL, so sequences are resynced only
// after the transaction has committed.View on GitHub (pinned to ad32144c42)
Solutions
- Stop the panel (and any other process) that is connected to the destination DB before running migrate-db.
- Confirm the DSN role owns or has TRUNCATE privilege on every panel table.
- Retry the migration — TRUNCATE is transactional in Postgres, so a failed run leaves the destination unchanged and a retry is safe.
- Prefer a fresh empty destination database; the pre-seeded admin is then the only row cleared.
Defensive patterns
Strategy: validation
Validate before calling
// ensure no other sessions hold locks on panel tables before migrating rows, _ := pgDB.Raw(`SELECT pid, query FROM pg_stat_activity WHERE datname = current_database() AND pid <> pg_backend_pid()`).Rows() defer rows.Close() // abort the migration if any session is active (e.g. a running panel)
Try / catch
if err := migrate(); err != nil {
if strings.Contains(err.Error(), "clear destination tables") {
log.Fatal("destination busy or privileges missing: stop the panel, check table ownership, then retry (destination is unchanged)")
}
} Prevention
- Stop the panel and any job runners connected to the destination DB before migrate-db.
- Migrate into an empty DB so the only cleared row is the auto-seeded admin.
- Remember TRUNCATE is transactional: a failed run leaves the destination untouched and a retry is safe.
When it happens
Trigger: Destination role lacks TRUNCATE privilege or table ownership; another session holds an ACCESS EXCLUSIVE-lock conflicting lock on one of the panel tables (a running panel connected to the same DB); lock_timeout/statement_timeout firing; a table listed in migrationModels() not existing on dst.
Common situations: Running migrate-db against the SAME database the live panel is currently using; migrating into a DB where tables were created by a different role; strict lock/statement timeouts configured on managed Postgres (RDS/Cloud SQL).
Related errors
- drop legacy foreign key: %w
- copy %T: %w
- destination DSN is required
- source sqlite not found at %s: %w
- open postgres destination: %w
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/ab6bb1bcefc51dae.
Report an issue: GitHub.