{"record":{"id":"951a7a53028632b6","repo":"gastownhall/beads","slug":"unstaging-ignored-migration-tables-w","errorCode":null,"errorMessage":"unstaging ignored migration tables: %w","messagePattern":"unstaging ignored migration tables: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/schema.go","lineNumber":716,"sourceCode":"\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 {\n\t\treturn applied, fmt.Errorf(\"staging migrations: %w\", err)\n\t}\n\tif !staged {","sourceCodeStart":698,"sourceCodeEnd":734,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/schema.go#L698-L734","documentation":"After the ignored-source migrations run, MigrateUp calls unstageIgnoredTables to remove internal dolt_ignore'd staging tables left behind by the migration pass. This error wraps a failure of that unstaging step. The migrations themselves have applied; only cleanup of the ignored staging tables failed, so the database is functional but the pass aborts before dirty-table verification and schema staging.","triggerScenarios":"Calling MigrateUp/MigrateUpWithLock when unstageIgnoredTables(ctx, db) returns an error — typically a SQL failure while dropping/altering the dolt_ignore'd staging tables (locked table, DML-on-ignored-table rejection, permissions, or a dropped connection mid-statement).","commonSituations":"Dolt's dolt_ignore configuration preventing the cleanup DDL from touching ignored tables, a stale advisory lock or other session holding the staging tables, low-level connection failures during long migrations, or permission-restricted database users that cannot alter/drop the internal tables.","solutions":["Read the wrapped cause (%w) for the exact SQL error from unstageIgnoredTables and address it (lock, permission, or dolt_ignore rule blocking the drop)","Check for other sessions/locks on the staging tables (dolt status / SHOW PROCESSLIST) and clear them, then re-run MigrateUp","Verify the database user has privileges to alter/drop the internal ignored tables","If a dolt_ignore pattern is swallowing the staging tables, adjust the ignore config so migration staging tables are exempt","Re-run MigrateUp once cleanup can succeed; the schema work already applied and only the post-pass steps were skipped"],"exampleFix":"// before: restricted DB user cannot drop ignored staging tables\nGRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'bd'@'%';\n// after: allow the DDL the migration cleanup needs\nGRANT ALL PRIVILEGES ON *.* TO 'bd'@'%';","handlingStrategy":"try-catch","validationCode":"// Pre-flight: confirm the session can run DDL on ignored/staging tables\nif _, err := db.ExecContext(ctx, \"SELECT 1 FROM dolt_status LIMIT 1\"); err != nil {\n    return fmt.Errorf(\"working set not inspectable: %w\", err)\n}\n// Verify dolt_ignore config will not block internal staging tables\nvar pat string\nif err := db.QueryRowContext(ctx,\n    \"SELECT pattern FROM dolt_ignore\").Scan(&pat); err == nil && pat == \"*\" {\n    return errors.New(\"dolt_ignore pattern blocks internal staging tables\")\n}","typeGuard":null,"tryCatchPattern":"applied, err := schema.MigrateUp(ctx, db)\nif err != nil {\n    if strings.Contains(err.Error(), \"unstaging ignored migration tables:\") {\n        cause := errors.Unwrap(err)\n        return fmt.Errorf(\"cleanup failed after migrations applied (%v); inspect ignored staging tables and re-run\", cause)\n    }\n    return err\n}","preventionTips":["Do not add broad dolt_ignore patterns (like *) that swallow migration staging tables","Grant the migration user full DDL privileges on the database","Run migrations on a stable connection with no competing sessions holding locks on internal tables","Re-run MigrateUp after transient cleanup failures; the migrations themselves already applied"],"tags":["database","migration","dolt","cleanup"],"backgroundTag":"migration-cleanup-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}