golang-migrate/migrate · critical
database is dirty
Error message
database is dirty
What it means
ErrDatabaseDirty in database/mysql is returned when the schema_migrations table contains a row with a non-empty dirty flag, meaning a previous migration failed midway and the database is in an unknown/partially-applied state. Migrate refuses further migrations until someone inspects the state and clears the flag. It is also defined identically in other drivers (cassandra, pgx), so the message can come from any of them.
Source
Thrown at database/mysql/mysql.go:33
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/go-sql-driver/mysql"
"github.com/golang-migrate/migrate/v4/database"
)
var _ database.Driver = (*Mysql)(nil) // explicit compile time type check
func init() {
database.Register("mysql", &Mysql{})
}
var DefaultMigrationsTable = "schema_migrations"
var (
ErrDatabaseDirty = fmt.Errorf("database is dirty")
ErrNilConfig = fmt.Errorf("no config")
ErrNoDatabaseName = fmt.Errorf("no database name")
ErrAppendPEM = fmt.Errorf("failed to append PEM")
ErrTLSCertKeyConfig = fmt.Errorf("to use TLS client authentication, both x-tls-cert and x-tls-key must not be empty")
)
type Config struct {
MigrationsTable string
DatabaseName string
NoLock bool
StatementTimeout time.Duration
}
type Mysql struct {
// mysql RELEASE_LOCK must be called from the same conn, so
// just do everything over a single conn anyway.
conn *sql.Conn
db *sql.DBView on GitHub (pinned to 01a9643f14)
Solutions
- Inspect the schema and the failing migration to manually repair or roll back partial changes
- Run migrate.Force(version) to reset the recorded version to the actual applied state
- Clear the dirty flag in schema_migrations (set dirty=0) once state is verified
- Fix the broken migration file before re-running migrations
Example fix
// before: blind retry fails with ErrDatabaseDirty
m.Steps(1)
// after: repair state then force
// manually verify/fix schema for version N
if err := m.Force(N); err != nil { return err }
if err := m.Up(); err != nil { return err } Defensive patterns
Strategy: try-catch
Validate before calling
var dirty bool
row := sqlDB.QueryRow("SELECT dirty FROM schema_migrations LIMIT 1")
if err := row.Scan(&dirty); err == nil && dirty {
return fmt.Errorf("database left dirty by a previous migration; inspect and force before migrating")
} Try / catch
if err := m.Up(); err != nil {
if errors.Is(err, mysql.ErrDatabaseDirty) {
// repair schema manually, then: m.Force(actualVersion); m.Up()
}
return err
} Prevention
- Run migrations in a single serialized pipeline (no concurrent instances)
- Wrap each migration in transactions where possible and alert on failure
- After a failed deploy, always repair state with Force before re-running
- Monitor the schema_migrations dirty flag
When it happens
Trigger: A migration previously failed after the version was recorded but before cleanup; running migrate.Up/Steps on a database left dirty by a crashed process; forcing a version with force but not fixing the schema.
Common situations: Deploy pipeline killed mid-migration, SQL migration with a syntax/permission error partway through, manual intervention during a migration, shared staging database left dirty by a colleague.
Related errors
AI-assisted analysis of golang-migrate/migrate@01a9643f14 (2026-09-02).
Data as JSON: /api/errors/b0bcb63b974a7a81.
Report an issue: GitHub.