gastownhall/beads · critical

schema migration: %w

Error message

schema migration: %w

What it means

This wraps any failure from schema.MigrateUpWithLock, which runs pending schema migrations on the pinned connection while holding a migration lock. The database may be left partially migrated ('dirty') if a migration itself failed midway, so this error should never be blindly retried without checking migration state.

Source

Thrown at internal/storage/dolt/store.go:2610

) (int, error) {
	conn, err := db.Conn(ctx)
	if err != nil {
		return 0, fmt.Errorf("schema: pin connection: %w", err)
	}
	defer conn.Close()

	var dbName string
	if err := conn.QueryRowContext(ctx, "SELECT DATABASE()").Scan(&dbName); err != nil {
		return 0, fmt.Errorf("schema: read database name: %w", err)
	}

	var opts []schema.MigrateLockOption
	if bootstrapHeal != nil {
		opts = append(opts, schema.WithFreshBootstrapHeal(bootstrapHeal, endpoint))
	}
	applied, err := schema.MigrateUpWithLock(ctx, conn, dbName, opts...)
	if err != nil {
		return applied, fmt.Errorf("schema migration: %w", err)
	}
	return applied, nil
}

func initSchemaOnDBWithRetry(ctx context.Context, db *sql.DB) (int, error) {
	return initSchemaOnDBWithRetryAndGate(ctx, db, nil)
}

// initSchemaOnDBWithRetryAndGate is initSchemaOnDBWithRetry with an optional
// pre-migration gate run INSIDE the retry loop. The gate's own reads
// (schema_migrations, dolt_remotes) can hit the same transient Dolt
// startup/catalog races the migration retry absorbs, so gate probe errors are
// retried with them instead of failing the open fast (bd-6dnrw.30); a
// *schema.RemoteMigrateGateError refusal stays permanent.
func initSchemaOnDBWithRetryAndGate(ctx context.Context, db *sql.DB, gate func(context.Context, *sql.DB) error) (int, error) {
	return initSchemaOnDBWithRetryAndGateBootstrapHeal(ctx, db, gate, nil, "")
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped error to see which migration step failed; inspect the schema_migrations/table state before retrying
  2. Ensure only one process runs migrations at a time (single-instance init or external lock/leader election)
  3. Fix or roll back the failed migration, then re-run
  4. Verify the Dolt SQL server supports every statement in the failing migration
Defensive patterns

Strategy: retry

Validate before calling

// check migration state before re-running
var dirty bool
var version int
row := conn.QueryRowContext(ctx,
    "SELECT version, dirty FROM schema_migrations LIMIT 1")
if err := row.Scan(&version, &dirty); err == nil && dirty {
    // resolve dirty state manually before retrying migrations
}

Try / catch

// Go: never auto-retry blindly; detect dirty state
applied, err := schema.MigrateUpWithLock(ctx, conn, dbName, opts...)
if err != nil {
    var lockErr *schema.LockError
    if errors.As(err, &lockErr) {
        // another migrator holds the lock: back off and retry
    } else {
        // migration itself failed: inspect schema_migrations, don't blind-retry
    }
    return err
}

Prevention

When it happens

Trigger: Calling schema init/migrate when: a migration SQL statement fails, the migration lock cannot be acquired (another process migrating), the schema_migrations table is inconsistent, or the connection drops mid-migration.

Common situations: Concurrent processes (two app instances) racing to migrate at startup, a previously failed migration left dirty state, Dolt server killed during migration, schema migration files referencing unsupported Dolt SQL features.

Related errors


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