{"record":{"id":"0c8554d57f958c50","repo":"golang-migrate/migrate","slug":"database-is-dirty-0c8554","errorCode":null,"errorMessage":"database is dirty","messagePattern":"database is dirty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"database/postgres/postgres.go","lineNumber":41,"sourceCode":"\nfunc init() {\n\tdb := Postgres{}\n\tdatabase.Register(\"postgres\", &db)\n\tdatabase.Register(\"postgresql\", &db)\n}\n\nvar (\n\tmultiStmtDelimiter = []byte(\";\")\n\n\tDefaultMigrationsTable       = \"schema_migrations\"\n\tDefaultMultiStatementMaxSize = 10 * 1 << 20 // 10 MB\n)\n\nvar (\n\tErrNilConfig      = fmt.Errorf(\"no config\")\n\tErrNoDatabaseName = fmt.Errorf(\"no database name\")\n\tErrNoSchema       = fmt.Errorf(\"no schema\")\n\tErrDatabaseDirty  = fmt.Errorf(\"database is dirty\")\n)\n\ntype Config struct {\n\tMigrationsTable       string\n\tMigrationsTableQuoted bool\n\tMultiStatementEnabled bool\n\tDatabaseName          string\n\tSchemaName            string\n\tmigrationsSchemaName  string\n\tmigrationsTableName   string\n\tStatementTimeout      time.Duration\n\tMultiStatementMaxSize int\n}\n\ntype Postgres struct {\n\t// Locking and unlocking need to use the same connection\n\tconn     *sql.Conn\n\tdb       *sql.DB","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/golang-migrate/migrate/blob/01a9643f1475e75bb6d6224ddeaf9d8e2434ca8a/database/postgres/postgres.go#L23-L59","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the failed migration's partial changes, then fix manually and run migrate.Force(version) to set a clean version","Call migrate.Force with the actual applied version to clear the dirty flag, then re-run migrations","Add the missing migration manually (if it half-applied) and force the version forward","Prevent recurrence: run migrations from a single process with the advisory lock (drivers do this) and avoid hard kills"],"exampleFix":"// before\n// migration failed at version 3, dirty=true\n// after\nm, _ := migrate.New(sourceURL, dbURL)\nif err := m.Force(2); err != nil { log.Fatal(err) } // mark clean at version 2\nif err := m.Up(); err != nil { log.Fatal(err) }      // retry migration 3","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := m.Up(); err != nil {\n    if errors.Is(err, postgres.ErrDatabaseDirty) {\n        // inspect schema, then clear the dirty flag at the last good version\n        if ferr := m.Force(lastGoodVersion); ferr != nil {\n            log.Fatalf(\"force failed: %v\", ferr)\n        }\n        return m.Up()\n    }\n    panic(err)\n}","preventionTips":["Run migrations from a single serialized process (CI lock or leader election)","Avoid killing migrate mid-run; give migrations generous timeouts","After any failure, manually verify the schema before using migrate.Force","Alert on dirty state early: check version/dirty via m.Version() at startup"],"tags":["database-dirty","migration-state","recovery","sentinel-error"],"backgroundTag":"database-is-dirty","analyzedSha":"01a9643f1475e75bb6d6224ddeaf9d8e2434ca8a","analyzedAt":"2026-09-02T19:38:29.671Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}