{"record":{"id":"529b9b5035a2b6f9","repo":"gastownhall/beads","slug":"ignored-migrations-w","errorCode":null,"errorMessage":"ignored migrations: %w","messagePattern":"ignored migrations: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/schema.go","lineNumber":713,"sourceCode":"\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)\n\t}\n\tif err := unstageIgnoredTables(ctx, db); err != nil {\n\t\treturn applied, fmt.Errorf(\"unstaging ignored migration tables: %w\", err)\n\t}\n\n\tif applied == 0 && !backfilled && appliedIgnored == 0 && !mainColumnAdded && !ignoredColumnAdded {\n\t\treturn applied, nil\n\t}\n\tchangedDirtyTables, err := changedDirtyTableSignatures(ctx, db, dirtyBeforeSignatures)\n\tif err != nil {\n\t\treturn applied, fmt.Errorf(\"checking pre-existing dirty table diffs: %w\", err)\n\t}\n\tif len(changedDirtyTables) > 0 {\n\t\treturn applied, fmt.Errorf(\"pre-existing dirty tables changed during schema migration: %s\", strings.Join(changedDirtyTables, \", \"))\n\t}\n\n\tstaged, err := stageSchemaTables(ctx, db, dirtyBefore)\n\tif err != nil {","sourceCodeStart":695,"sourceCodeEnd":731,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/schema.go#L695-L731","documentation":"MigrateUp runs schema migrations in two passes: a main-source pass over user tables and an 'ignored source' pass that manages dolt_ignore'd internal tables (e.g. ignored_schema_migrations). This error wraps any failure from the ignored-source migrate step, so the caller knows the main migrations may have already applied but the ignored-table migrations did not complete. The error is deliberately fatal: callers must not treat the open/migrate as successful because a half-applied pass could be checkpointed by a reconcile commit.","triggerScenarios":"Calling MigrateUp/MigrateUpWithLock when ignoredSource.migrate fails — i.e. a DDL statement in the ignored-source migration set errors (SQL syntax/dialect failure, locked or corrupted internal dolt_ignore'd tables, connection dropped mid-pass, or a migration applied out of order on a non-converged clone).","commonSituations":"Opening a beads database whose Dolt working set is in a bad state (corrupt ignored_schema_migrations table), running an older binary's migration set against a newer database or vice versa, network/lock failures between the main pass and the ignored pass, or an interrupted prior upgrade leaving the ignored source mid-migration.","solutions":["Check the wrapped cause (%w) for the underlying SQL error and fix that first — this error is only a wrapper around the ignored-source migrate failure","Inspect the dolt_ignore'd internal tables (e.g. ignored_schema_migrations) with SHOW TABLES / dolt status and repair or restore from a backup if corrupted","Re-run MigrateUp after the underlying failure is resolved; the migration cursors determine which statements re-apply","If a clone is non-converged, re-clone from a healthy remote instead of hand-patching the ignored tables","Do not commit or reconcile on this error — retry from a state where the whole pass can run cleanly"],"exampleFix":"// before: treating open failure as soft and committing anyway\napplied, err := schema.MigrateUp(ctx, db)\nif err != nil {\n    log.Warn(\"migrate failed, continuing\")\n    reconcileCommit() // dangerous: half-applied pass\n}\n// after: abort on ignored-migration errors before any commit\napplied, err := schema.MigrateUp(ctx, db)\nif err != nil {\n    return fmt.Errorf(\"database open aborted: %w\", err)\n}\nreconcileCommit()","handlingStrategy":"try-catch","validationCode":"// Before migrating, check both sources are at a migratable state\nok, err := schema.MigrationWorkNeeded(ctx, db)\nif err != nil {\n    return fmt.Errorf(\"cannot assess migration state: %w\", err)\n}\nif ok {\n    log.Println(\"migration required; ensure backup exists and no dirty ignored tables\")\n}","typeGuard":null,"tryCatchPattern":"applied, err := schema.MigrateUp(ctx, db)\nif err != nil {\n    var msg string\n    if errors.Unwrap(err) != nil {\n        msg = errors.Unwrap(err).Error()\n    }\n    // Match the wrapper text to branch on the ignored-pass failure\n    if strings.Contains(err.Error(), \"ignored migrations:\") {\n        return fmt.Errorf(\"ignored-source migration failed (%s); aborting open, no commit attempted\", msg)\n    }\n    return err\n}","preventionTips":["Always migrate under the advisory lock via MigrateUpWithLock so no other session races the ignored pass","Take a backup/snapshot before running migrations on databases with modified dolt_ignore configuration","Keep binary and database migration lineage converged — avoid mixing versions across clones","Treat any migrate error as fatal to the open path; never commit or reconcile afterward"],"tags":["database","migration","dolt","schema"],"backgroundTag":"migration-half-applied","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}