golang-migrate/migrate · critical
database is dirty
Error message
database is dirty
What it means
ErrDatabaseDirty means the migrations table's 'dirty' flag was true when a migration attempt began: a previous migration failed mid-run and the schema may be partially migrated. The driver refuses to proceed to avoid corrupting schema state. The sentinel is re-declared in cassandra, mysql, and pgx drivers with the same meaning.
Source
Thrown at database/postgres/postgres.go:41
func init() {
db := Postgres{}
database.Register("postgres", &db)
database.Register("postgresql", &db)
}
var (
multiStmtDelimiter = []byte(";")
DefaultMigrationsTable = "schema_migrations"
DefaultMultiStatementMaxSize = 10 * 1 << 20 // 10 MB
)
var (
ErrNilConfig = fmt.Errorf("no config")
ErrNoDatabaseName = fmt.Errorf("no database name")
ErrNoSchema = fmt.Errorf("no schema")
ErrDatabaseDirty = fmt.Errorf("database is dirty")
)
type Config struct {
MigrationsTable string
MigrationsTableQuoted bool
MultiStatementEnabled bool
DatabaseName string
SchemaName string
migrationsSchemaName string
migrationsTableName string
StatementTimeout time.Duration
MultiStatementMaxSize int
}
type Postgres struct {
// Locking and unlocking need to use the same connection
conn *sql.Conn
db *sql.DBView on GitHub (pinned to 01a9643f14)
Solutions
- Inspect the failed migration's partial changes, then fix manually and run migrate.Force(version) to set a clean version
- Call migrate.Force with the actual applied version to clear the dirty flag, then re-run migrations
- Add the missing migration manually (if it half-applied) and force the version forward
- Prevent recurrence: run migrations from a single process with the advisory lock (drivers do this) and avoid hard kills
Example fix
// before
// migration failed at version 3, dirty=true
// after
m, _ := migrate.New(sourceURL, dbURL)
if err := m.Force(2); err != nil { log.Fatal(err) } // mark clean at version 2
if err := m.Up(); err != nil { log.Fatal(err) } // retry migration 3 Defensive patterns
Strategy: try-catch
Try / catch
if err := m.Up(); err != nil {
if errors.Is(err, postgres.ErrDatabaseDirty) {
// inspect schema, then clear the dirty flag at the last good version
if ferr := m.Force(lastGoodVersion); ferr != nil {
log.Fatalf("force failed: %v", ferr)
}
return m.Up()
}
panic(err)
} Prevention
- Run migrations from a single serialized process (CI lock or leader election)
- Avoid killing migrate mid-run; give migrations generous timeouts
- After any failure, manually verify the schema before using migrate.Force
- Alert on dirty state early: check version/dirty via m.Version() at startup
When it happens
Trigger: Running Up/Down after a prior migration crashed, panicked, or lost its connection between applying SQL and clearing the dirty flag; concurrently running migrate instances; manually setting version with dirty=true and not clearing it.
Common situations: Deploy pipelines killing migrate mid-migration (timeout/OOM); network drop during a long DDL; two CI jobs racing migrations; developer leaving the database dirty after debugging a failed migration.
Related errors
AI-assisted analysis of golang-migrate/migrate@01a9643f14 (2026-09-02).
Data as JSON: /api/errors/0c8554d57f958c50.
Report an issue: GitHub.