gastownhall/beads · error

checking dirty tables against pending migrations

Error message

checking dirty tables against pending migrations: %w

What it means

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.

Solutions

  1. Inspect the wrapped cause for the failing query or bad migration metadata
  2. Fix or remove any migration file whose name lacks a numeric version prefix
  3. Ensure the database is opened read-write and the engine is healthy, then retry
  4. If only dirty-table detection is noisy, commit or stash unrelated working-set changes before migrating
Defensive patterns

Strategy: try-catch

Validate before calling

// Commit or verify a clean-enough working set before migrating
rows, _ := db.QueryContext(ctx, "SELECT table_name FROM dolt_status WHERE staged=0 AND committed=0")
// if suspicious user tables are dirty, resolve them before calling MigrateUp

Type guard

func isDirtyCheckErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "checking dirty tables against pending migrations")
}
var _ = isDirtyCheckErr

Try / catch

if _, err := schema.MigrateUp(ctx, db); err != nil {
    var dirty *schema.DirtyTablesError
    switch {
    case errors.As(err, &dirty):
        return fmt.Errorf("commit tables %v first", dirty.Tables)
    case isDirtyCheckErr(err):
        return fmt.Errorf("migration safety check failed: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/6770337018642c64. Report an issue: GitHub.

Appendix: source

Thrown at internal/storage/schema/schema.go:637

		return 0, fmt.Errorf("reading pre-migration status: %w", err)
	}
	delete(dirtyBefore, "dolt_ignore")
	// A previous pass that crashed mid-aux-rekey left its partial UPDATEs
	// dirty in the working set with the in-progress sentinel still recorded
	// (bd-578h9.16). Those tables are this pass's own migration state, not
	// pre-existing user writes: dropping them from dirtyBefore exempts them
	// from the changed-signature guard (the resumed rekey is about to change
	// them) and lets stageSchemaTables commit them with the rest of the pass.
	if resuming, err := anyAuxRekeyResumePending(ctx, db); err != nil {
		return 0, fmt.Errorf("reading aux rekey sentinel: %w", err)
	} else if resuming {
		for _, t := range auxRekeyTables {
			delete(dirtyBefore, t.name)
		}
	}
	touchedDirtyTables, err := mainSource.pendingMigrationDirtyTables(ctx, db, dirtyBefore)
	if err != nil {
		return 0, fmt.Errorf("checking dirty tables against pending migrations: %w", err)
	}
	if len(touchedDirtyTables) > 0 {
		return 0, &DirtyTablesError{Tables: touchedDirtyTables}
	}
	dirtyBeforeSignatures, err := dirtyTableSignatures(ctx, db, dirtyBefore)
	if err != nil {
		return 0, fmt.Errorf("reading pre-migration dirty table diffs: %w", err)
	}
	// Captured before the main migrations run: the aux re-key uses it to
	// distinguish the lineage's first rekey-aware migration (run the pass)
	// from a fresh clone of an already-converged lineage (record the marker
	// only, bd-578h9.4).
	mainVersionBefore, err := mainSource.currentVersion(ctx, db)
	if err != nil {
		return 0, fmt.Errorf("reading pre-migration schema version: %w", err)
	}

	applied, mainColumnAdded, err := mainSource.migrate(ctx, db, 0)

View on GitHub (pinned to 71377f2769)