gastownhall/beads · error

reading pre-migration schema version: %w

Error message

reading pre-migration schema version: %w

What it means

This error wraps a failure reading the current main-source schema version (mainSource.currentVersion) before any migration runs. The captured version lets the aux re-key distinguish a first rekey-aware pass from a fresh clone of an already-converged lineage (bd-578h9.4). Without it, MigrateUp cannot run safely and aborts with nothing applied.

Source

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

	}
	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)
	if err != nil {
		return applied, err
	}

	backfilled, err := ensureBackfilledCustomStatusesCustomTypes(ctx, db)
	if err != nil {
		return applied, fmt.Errorf("backfill custom tables: %w", err)
	}

	// #4259: rewrite any per-clone-random dependency ids (minted by 0043's
	// DEFAULT (UUID()) before this fix) to the deterministic key, so independently
	// migrated clones converge to byte-identical, merge-safe dependencies. Runs
	// here, after the schema migrations (0050 has asserted the canonical schema),
	// and only on a pass where migration work was needed.
	rekeyed, err := rekeyDependencyIDs(ctx, db)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix the wrapped cause (query failure on the version cursor table)
  2. Ensure the schema_migrations table exists and contains a valid version row; recreate it from a healthy clone if missing
  3. Never hand-edit migration cursors - let migrations own them
  4. Retry MigrateUp once cursor reads succeed
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the migration cursor exists before opening
row := db.QueryRowContext(ctx,
    "SELECT COUNT(*) FROM schema_migrations")
var n int
if err := row.Scan(&n); err != nil || n == 0 {
    return errors.New("schema_migrations missing or empty; restore from a healthy clone")
}

Type guard

func isVersionCursorErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "reading pre-migration schema version")
}

Try / catch

if _, err := schema.MigrateUp(ctx, db); err != nil {
    if isVersionCursorErr(err) {
        return fmt.Errorf("migration cursor broken, refusing to guess version: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling MigrateUp/MigrateUpWithLock when the schema version cursor cannot be read - missing or unreadable schema_migrations table, corrupt version row, or a Dolt query error on the cursor table.

Common situations: Databases copied out-of-band without the schema_migrations table; a manual edit/deletion of the migrations cursor; storage-level failures on a read-only or damaged repo.

Related errors


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