{"record":{"id":"6770337018642c64","repo":"gastownhall/beads","slug":"checking-dirty-tables-against-pending-migrations","errorCode":null,"errorMessage":"checking dirty tables against pending migrations: %w","messagePattern":"checking dirty tables against pending migrations: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/schema.go","lineNumber":637,"sourceCode":"\t\treturn 0, fmt.Errorf(\"reading pre-migration status: %w\", err)\n\t}\n\tdelete(dirtyBefore, \"dolt_ignore\")\n\t// A previous pass that crashed mid-aux-rekey left its partial UPDATEs\n\t// dirty in the working set with the in-progress sentinel still recorded\n\t// (bd-578h9.16). Those tables are this pass's own migration state, not\n\t// pre-existing user writes: dropping them from dirtyBefore exempts them\n\t// from the changed-signature guard (the resumed rekey is about to change\n\t// them) and lets stageSchemaTables commit them with the rest of the pass.\n\tif resuming, err := anyAuxRekeyResumePending(ctx, db); err != nil {\n\t\treturn 0, fmt.Errorf(\"reading aux rekey sentinel: %w\", err)\n\t} else if resuming {\n\t\tfor _, t := range auxRekeyTables {\n\t\t\tdelete(dirtyBefore, t.name)\n\t\t}\n\t}\n\ttouchedDirtyTables, err := mainSource.pendingMigrationDirtyTables(ctx, db, dirtyBefore)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"checking dirty tables against pending migrations: %w\", err)\n\t}\n\tif len(touchedDirtyTables) > 0 {\n\t\treturn 0, &DirtyTablesError{Tables: touchedDirtyTables}\n\t}\n\tdirtyBeforeSignatures, err := dirtyTableSignatures(ctx, db, dirtyBefore)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"reading pre-migration dirty table diffs: %w\", err)\n\t}\n\t// Captured before the main migrations run: the aux re-key uses it to\n\t// distinguish the lineage's first rekey-aware migration (run the pass)\n\t// from a fresh clone of an already-converged lineage (record the marker\n\t// only, bd-578h9.4).\n\tmainVersionBefore, err := mainSource.currentVersion(ctx, db)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"reading pre-migration schema version: %w\", err)\n\t}\n\n\tapplied, mainColumnAdded, err := mainSource.migrate(ctx, db, 0)","sourceCodeStart":619,"sourceCodeEnd":655,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/schema.go#L619-L655","documentation":"This error wraps a failure in pendingMigrationDirtyTables, which cross-checks the set of pre-existing dirty (uncommitted) tables against the tables that pending migrations will alter. The check itself failed (as opposed to finding matches, which returns *DirtyTablesError instead). MigrateUp aborts because it cannot safely decide whether dirty tables would be clobbered.","triggerScenarios":"Calling MigrateUp/MigrateUpWithLock when pendingMigrationDirtyTables returns an error - typically a Dolt query failure while enumerating pending migrations or dirty table names, or malformed migration metadata (unparseable version names).","commonSituations":"A migration file with a name that fails parseVersion, database opened read-only while the check attempts reads that fail, or transient embedded-Dolt engine errors during a busy working set.","solutions":["Inspect the wrapped cause for the failing query or bad migration metadata","Fix or remove any migration file whose name lacks a numeric version prefix","Ensure the database is opened read-write and the engine is healthy, then retry","If only dirty-table detection is noisy, commit or stash unrelated working-set changes before migrating"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Commit or verify a clean-enough working set before migrating\nrows, _ := db.QueryContext(ctx, \"SELECT table_name FROM dolt_status WHERE staged=0 AND committed=0\")\n// if suspicious user tables are dirty, resolve them before calling MigrateUp","typeGuard":"func isDirtyCheckErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"checking dirty tables against pending migrations\")\n}\nvar _ = isDirtyCheckErr","tryCatchPattern":"if _, err := schema.MigrateUp(ctx, db); err != nil {\n    var dirty *schema.DirtyTablesError\n    switch {\n    case errors.As(err, &dirty):\n        return fmt.Errorf(\"commit tables %v first\", dirty.Tables)\n    case isDirtyCheckErr(err):\n        return fmt.Errorf(\"migration safety check failed: %w\", err)\n    }\n    return err\n}","preventionTips":["Do not leave hand-modified rows uncommitted in bd tables across upgrades","Name migration files strictly as <number>_<name> so version parsing never fails","Run migrations single-threaded under the advisory lock"],"tags":["database","migration","dolt","dirty-tables"],"backgroundTag":"migration-dirty-table-conflict","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}