gastownhall/beads · warning

uow: migrate: %w

Error message

uow: migrate: %w

What it means

classifyInitSchemaError wraps errors that are serialization failures or migration-lock errors as 'uow: migrate: %w', returning them as retryable (bare) errors so the backoff loop retries the schema migration. It signals that another session's migration or a transaction serialization conflict blocked this attempt.

Source

Thrown at internal/storage/uow/dolt_sql_provider.go:87

func (e *bootstrapPreparationError) Error() string {
	return e.err.Error()
}

func (e *bootstrapPreparationError) Unwrap() error {
	return e.err
}

func classifyInitSchemaError(err error) error {
	var preparationErr *bootstrapPreparationError
	if errors.As(err, &preparationErr) {
		if preparationErr.retryable {
			return fmt.Errorf("uow: bootstrap preparation: %w", err)
		}
		return backoff.Permanent(err)
	}
	if isSerializationError(err) || schema.IsMigrationLockError(err) {
		return fmt.Errorf("uow: migrate: %w", err)
	}
	return backoff.Permanent(fmt.Errorf("uow: migrate: %w", err))
}

// ProviderOption tunes how a SQL-server unit-of-work provider opens. Options
// are variadic so the existing constructor call sites — every one of which
// wants the ordinary mutating open — stay unchanged.
type ProviderOption func(*providerOptions)

type providerOptions struct {
	// preview opens for a command that promised not to mutate anything
	// (--dry-run, --inspect). Such a command must reach its own RunE before
	// anything writes, so the open may neither CREATE DATABASE nor run
	// MigrateUpWithLock: both happen during root pre-run, before the flag the
	// user passed has had any effect. An absent or behind database is
	// reported by the preview's own query rather than repaired implicitly —
	// the same contract embeddeddolt.OpenForPreviewCommand gives the embedded
	// path.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Let the 60s backoff budget in initSchema retry — the peer's migration should finish and the retry converge.
  2. Reduce concurrent cold starts: stagger or serialize the first bd invocations against a new shared server.
  3. Confirm the peer holding the lock is healthy; a crashed peer can hold the lock until timeout.
  4. Inspect the wrapped error if retries exhaust — it names the conflicting session or lock.
Defensive patterns

Strategy: retry

Validate before calling

// before opening: confirm no peer currently holds the migration lock
rows, err := adminConn.QueryContext(ctx,
    "SELECT COUNT(*) FROM information_schema.processlist WHERE info LIKE '%MigrateUpWithLock%'")

Type guard

if schema.IsMigrationLockError(err) || uow.IsSerializationError(err) { /* retryable: re-attempt open */ }

Try / catch

err := provider.Open(ctx, cfg)
if err != nil {
    if uow.IsSerializationError(err) || schema.IsMigrationLockError(err) {
        // transient contention — retry with exponential backoff
        return backoff.Retry(openFn, bo)
    }
    return err
}

Prevention

When it happens

Trigger: initSchemaAttempt calls schema.MigrateUpWithLock, which fails with a serialization error (isSerializationError) or a migration lock error (schema.IsMigrationLockError); classifyInitSchemaError wraps it so backoff retries.

Common situations: Two bd processes starting simultaneously against a fresh shared Dolt server; a peer process mid-migration holding the schema migration lock; Dolt transaction serialization conflicts on a busy server.

Related errors


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