{"record":{"id":"9e5f8cf3032da197","repo":"gastownhall/beads","slug":"checking-dirty-tables-against-pending-ignored-migr","errorCode":null,"errorMessage":"checking dirty tables against pending ignored migrations: %w","messagePattern":"checking dirty tables against pending ignored migrations: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/schema.go","lineNumber":695,"sourceCode":"\t// migration 0037 randomized per-clone, the same hazard class on the aux\n\t// tables. Gated on the clone-local ignored marker (recorded later in this\n\t// pass by ignoredSource.migrate) so it runs exactly once per clone instead\n\t// of churning synced rows on every later migration pass — and on the\n\t// pre-pass main cursor, so fresh clones of converged lineages record the\n\t// marker without re-running the rewrite (bd-578h9.4).\n\t// ...and the bd-ri8bd sibling: one more pass over the same tables for the\n\t// rows minted with random UUIDv7 ids between the initial backfill and the\n\t// switch to content-derived ids at insert time. Same machinery, own\n\t// marker/sentinel/shipped-version gates, one shared cursor read.\n\tauxRekeyed, err := rekeyAuxRowIDsAllPasses(ctx, db, mainVersionBefore)\n\tif err != nil {\n\t\treturn applied, fmt.Errorf(\"rekey aux row ids: %w\", err)\n\t}\n\tbackfilled = backfilled || auxRekeyed\n\n\ttouchedIgnoredDirtyTables, err := ignoredSource.pendingMigrationDirtyTables(ctx, db, dirtyBeforeAll)\n\tif err != nil {\n\t\treturn applied, fmt.Errorf(\"checking dirty tables against pending ignored migrations: %w\", err)\n\t}\n\tif len(touchedIgnoredDirtyTables) > 0 {\n\t\t// Deliberately a plain, untyped error (unlike the main-source guard\n\t\t// above, which returns *DirtyTablesError): this check fires mid-pass,\n\t\t// after the main-source migrations have already applied. A lenient\n\t\t// caller (embeddeddolt's openReadOnlyCommand / openWorkingSetReconcile\n\t\t// intents) skipping this and returning as if the open succeeded would\n\t\t// let a reconcile commit checkpoint a half-applied migration pass.\n\t\t// The ignored source also tracks bd-internal state (dolt_ignore'd\n\t\t// tables like ignored_schema_migrations), not expected user data, so\n\t\t// there is no dirty-commit recovery story to support here the way\n\t\t// there is for the main-source guard (#4566 scope).\n\t\treturn applied, fmt.Errorf(\"pending ignored schema migrations alter pre-existing dirty tables: %s\", strings.Join(touchedIgnoredDirtyTables, \", \"))\n\t}\n\n\tappliedIgnored, ignoredColumnAdded, err := ignoredSource.migrate(ctx, db, 0)\n\tif err != nil {\n\t\treturn applied, fmt.Errorf(\"ignored migrations: %w\", err)","sourceCodeStart":677,"sourceCodeEnd":713,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/schema.go#L677-L713","documentation":"This error wraps a failure in ignoredSource.pendingMigrationDirtyTables - the mid-pass re-check of whether pending ignored-source migrations would alter pre-existing dirty tables. Unlike a positive finding (which yields the plain table-list error at 4009), this is the check itself erroring out, after main migrations already applied.","triggerScenarios":"Calling MigrateUp/MigrateUpWithLock where the ignored-source dirty-table cross-check fails - Dolt query errors against dolt_ignore'd internal tables (ignored_schema_migrations), malformed ignored migration metadata, or engine errors mid-pass.","commonSituations":"Databases where the ignored source's tables were damaged or copied incompletely; concurrent writers invalidating the earlier dirty snapshot mid-pass; read-only opens that fail on internal writes.","solutions":["Inspect the wrapped cause for the failing ignored-source query","Repair or restore ignored_schema_migrations / dolt_ignore'd internal tables from a healthy clone","Eliminate concurrent writers for the duration of MigrateUp","Re-run MigrateUp once the internal tables are readable"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Ensure internal ignored tables are queryable before opening\nif _, err := db.QueryContext(ctx, \"SELECT 1 FROM ignored_schema_migrations LIMIT 1\"); err != nil {\n    return fmt.Errorf(\"internal migration tables unreadable: %w\", err)\n}","typeGuard":"func isIgnoredDirtyCheckErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"checking dirty tables against pending ignored migrations\")\n}","tryCatchPattern":"applied, err := schema.MigrateUp(ctx, db)\nif isIgnoredDirtyCheckErr(err) {\n    return fmt.Errorf(\"mid-pass check failed after %d main migrations: %w\", applied, err)\n}","preventionTips":["Never delete or hand-modify dolt_ignore'd internal tables","Copy complete databases including ignored tables","Run one bd process per repository during upgrades"],"tags":["database","migration","dolt","dirty-tables","ignored-source"],"backgroundTag":"migration-dirty-table-conflict","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}