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.DB

View on GitHub (pinned to 01a9643f14)

Solutions

  1. Inspect the failed migration's partial changes, then fix manually and run migrate.Force(version) to set a clean version
  2. Call migrate.Force with the actual applied version to clear the dirty flag, then re-run migrations
  3. Add the missing migration manually (if it half-applied) and force the version forward
  4. 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

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.