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
- Let the 60s backoff budget in initSchema retry — the peer's migration should finish and the retry converge.
- Reduce concurrent cold starts: stagger or serialize the first bd invocations against a new shared server.
- Confirm the peer holding the lock is healthy; a crashed peer can hold the lock until timeout.
- 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
- Serialize first-run initialization across the team (one process migrates, others wait).
- Keep a 60s+ budget for cold starts against shared servers.
- Upgrade all bd clients together so no peer stalls mid-migration.
- Monitor for lock-holder crashes that leave the lock until timeout.
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
- ErrBusy
- %w for %s
- failed to migrate credential keys: %w
- failed to scan peer for migration: %w
- failed to iterate peers for migration: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/bfa853d5050e3f05.
Report an issue: GitHub.