golang-migrate/migrate · error
database is dirty
Error message
database is dirty
What it means
ErrDatabaseDirty is a sentinel error returned by the sqlite3 driver when the schema_migrations table indicates a previous migration run did not complete (a row marked dirty). The driver blocks further migrations to avoid operating on an inconsistently migrated schema. It is returned from WithInstance/Open during RunMigrations until the dirty state is resolved.
Source
Thrown at database/sqlite3/sqlite3.go:24
"fmt"
"io"
nurl "net/url"
"strconv"
"strings"
"sync/atomic"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database"
_ "github.com/mattn/go-sqlite3"
)
func init() {
database.Register("sqlite3", &Sqlite{})
}
var DefaultMigrationsTable = "schema_migrations"
var (
ErrDatabaseDirty = fmt.Errorf("database is dirty")
ErrNilConfig = fmt.Errorf("no config")
ErrNoDatabaseName = fmt.Errorf("no database name")
)
type Config struct {
MigrationsTable string
DatabaseName string
NoTxWrap bool
}
type Sqlite struct {
db *sql.DB
isLocked atomic.Bool
config *Config
}
func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {View on GitHub (pinned to 01a9643f14)
Solutions
- Inspect the schema to determine whether the last partial migration actually applied.
- If it applied, clean the flag: sqlite3 app.db "UPDATE schema_migrations SET dirty=0 WHERE version=<version>;" (or use migrate force <version>).
- If it did not apply, manually undo partial changes and delete the dirty row, then re-run migrations.
- Restore from a pre-migration backup and re-run migrations cleanly.
- Verify schema state before modifying the migrations table; never blind-force a version.
Example fix
// before // migrate.Up fails: ErrDatabaseDirty (version 3|1) // after (CLI) // migrate -path ./migrations -database sqlite3://app.db force 3 // sqlite3 app.db "UPDATE schema_migrations SET dirty=0 WHERE version=3;"
Defensive patterns
Strategy: type-guard
Validate before calling
// before migrating
rows, err := db.Query("SELECT version, dirty FROM schema_migrations")
if err == nil {
for rows.Next() {
var v int; var d bool
rows.Scan(&v, &d)
if d { return fmt.Errorf("db dirty at version %d; resolve before migrating", v) }
}
} Type guard
func isDirtyErr(err error) bool {
return errors.Is(err, sqlite3.ErrDatabaseDirty)
} Try / catch
if err := m.Up(); err != nil {
if errors.Is(err, sqlite3.ErrDatabaseDirty) {
// halt pipeline; use `migrate force <v>` only after verifying schema
return fmt.Errorf("migration halted: dirty database: %w", err)
}
return err
} Prevention
- Never SIGKILL a running migrator; use graceful shutdown with migration-aware timeouts.
- Back up the database file before each migration run.
- Run migrations in a dedicated deploy step, not alongside app boot in crash-looping containers.
- Monitor schema_migrations for dirty=true.
When it happens
Trigger: Calling WithInstance or Open (then Up/Down) on a SQLite database whose migrations table has dirty=true — a previous migration was interrupted mid-run (process killed, crash, disk full, lost lock).
Common situations: Deploy pipeline killed mid-migration (Ctrl+C, SIGKILL, OOM, pod restart); power loss during a large ALTER TABLE; disk-full or SQLite lock timeout aborting a migration halfway.
Related errors
AI-assisted analysis of golang-migrate/migrate@01a9643f14 (2026-09-02).
Data as JSON: /api/errors/7639c4b37470f60a.
Report an issue: GitHub.