golang-migrate/migrate · error
database is dirty
Error message
database is dirty
What it means
ErrDatabaseDirty is a sentinel error returned by the sqlcipher driver when the schema_migrations table indicates a previous migration run did not complete (a record is missing a success/commit marker). The driver refuses to run 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/sqlcipher/sqlcipher.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/mutecomm/go-sqlcipher/v4"
)
func init() {
database.Register("sqlcipher", &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 did apply, manually set the migrations table row to clean: UPDATE schema_migrations SET dirty=false WHERE version=<version>;
- If it did not apply, manually drop the partially applied changes and the dirty row, then re-run migrate.Up.
- Restore the database from a backup taken before the failed migration, then re-run migrations.
- Never edit the migrations table while unsure of schema state — verify against the migration SQL first.
Example fix
// inspect then fix // before // panic: Dirty database version 5. Fix and force version. // after (sqlite/sqlcipher shell) // sqlite3 app.db "SELECT * FROM schema_migrations;" // -- 5|1 (dirty) // sqlite3 app.db "UPDATE schema_migrations SET dirty=0 WHERE version=5;"
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, sqlcipher.ErrDatabaseDirty)
} Try / catch
if err := m.Up(); err != nil {
if errors.Is(err, sqlcipher.ErrDatabaseDirty) {
// halt pipeline; inspect schema and fix schema_migrations manually
return fmt.Errorf("migration halted: dirty database: %w", err)
}
return err
} Prevention
- Run migrations in a supervised context; never SIGKILL a running migrator.
- Take a DB backup before running migrations so you can restore instead of hand-editing state.
- Run migrations as a dedicated deploy step with generous timeouts.
- Alert on dirty=true rows in schema_migrations.
When it happens
Trigger: Calling WithInstance or Open (then migrate.Up/Down) on a sqlcipher database whose migrations table contains a row with dirty=true — i.e., a previous migration was interrupted mid-run (process killed, crash, lost connection) leaving the schema in an unknown state.
Common situations: Migration process killed by Ctrl+C, SIGKILL, OOM, or deploy timeout halfway through an ALTER TABLE; power loss during migration; a partial migration in a container that was restarted; failed transaction rollback leaving a dirty flag.
Related errors
AI-assisted analysis of golang-migrate/migrate@01a9643f14 (2026-09-02).
Data as JSON: /api/errors/b095a4fda816518d.
Report an issue: GitHub.