golang-migrate/migrate · error

database is dirty

Error message

database is dirty

What it means

ErrDatabaseDirty is a sentinel error returned by the sqlite driver when the schema_migrations table indicates a previous migration run did not complete (a row is marked dirty). The driver refuses further migrations to protect against an inconsistently migrated schema. It is returned from WithInstance/Open during RunMigrations until the dirty state is cleared.

Source

Thrown at database/sqlite/sqlite.go:24

	"fmt"
	"io"
	nurl "net/url"
	"strconv"
	"strings"
	"sync/atomic"

	"github.com/golang-migrate/migrate/v4"
	"github.com/golang-migrate/migrate/v4/database"
	_ "modernc.org/sqlite"
)

func init() {
	database.Register("sqlite", &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

  1. Inspect the schema to determine whether the last partial migration actually applied.
  2. If it applied, clean the flag: sqlite3 app.db "UPDATE schema_migrations SET dirty=0 WHERE version=<version>;"
  3. If it did not apply, manually undo the partial changes and delete the dirty row, then re-run migrations.
  4. Restore from a pre-migration backup and re-run migrations cleanly.
  5. Verify schema state before touching the migrations table; never blind-force the version.

Example fix

// before
// migrate.Up fails: ErrDatabaseDirty (version 7|1)
// after (sqlite3 shell)
// sqlite3 app.db "SELECT * FROM schema_migrations;"   -- 7|1
// sqlite3 app.db "UPDATE schema_migrations SET dirty=0 WHERE version=7;"
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, sqlite.ErrDatabaseDirty)
}

Try / catch

if err := m.Up(); err != nil {
    if errors.Is(err, sqlite.ErrDatabaseDirty) {
        // halt pipeline; inspect schema and clear dirty flag only after verification
        return fmt.Errorf("migration halted: dirty database: %w", err)
    }
    return err
}

Prevention

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) leaving the schema in an unknown state.

Common situations: Killing a deploy mid-migration (Ctrl+C, SIGKILL, OOM, container restart); power loss during a large ALTER TABLE; SQLite file lock timeout or disk-full aborting a migration halfway.

Related errors


AI-assisted analysis of golang-migrate/migrate@01a9643f14 (2026-09-02). Data as JSON: /api/errors/2a486a962c4bd8e3. Report an issue: GitHub.