gastownhall/beads · error
migration %s: %w
Error message
migration %s: %w
What it means
This error wraps a failure that occurred while executing a schema migration's SQL body during runMigrations (internal/storage/schema/schema.go:1672). The library applies each pending numbered migration via execMigrationBody and aborts the whole pass when a migration's SQL fails, returning the count of migrations applied so far. The wrapped inner error carries the underlying database failure (syntax, constraint, lock, timeout, etc.); the outer text identifies which migration file failed.
Source
Thrown at internal/storage/schema/schema.go:1672
// Snapshotting first makes repair-hook mutations count as this step's
// own newly-dirtied work, so they land in the same atomic commit as
// the migration and its cursor row.
var dirtyBeforeStep map[string]dirtyTableState
if commitEachStep {
dirtyBeforeStep, err = dirtyTables(ctx, db, true)
if err != nil {
return count, fmt.Errorf("snapshotting dirty tables before %s: %w", mf.name, err)
}
}
if err := src.preMigrationRepair(ctx, db, mf.version); err != nil {
return count, fmt.Errorf("pre-repair for migration %s: %w", mf.name, err)
}
fmt.Fprintf(stderr, "Applying migration %04d: %s…\n", mf.version, humanMigrationName(mf.name))
start := time.Now()
if err := execMigrationBody(ctx, db, string(data)); err != nil {
return count, fmt.Errorf("migration %s: %w", mf.name, err)
}
sum := sha256.Sum256(data)
contentHash := hex.EncodeToString(sum[:])
if _, err := db.ExecContext(ctx, "INSERT IGNORE INTO "+src.cursorTable+" (version, content_hash) VALUES (?, ?)", mf.version, contentHash); err != nil {
return count, fmt.Errorf("recording %s in %s: %w", mf.name, src.cursorTable, err)
}
count++
// commitEachStep's DOLT_ADD/DOLT_COMMIT is the expensive, fallible
// part of this step on the production embedded path. The "done" line
// (and its timing) must land after that commit succeeds, not before
// it: printing "done" and then hitting a commit error would show an
// operator a false completion, and timing that stopped before the
// commit would understate the step's real cost. A failed commit
// returns before either print statement below runs.
if commitEachStep {
if err := commitMigrationStep(ctx, db, src.cursorTable, mf.name, dirtyBeforeStep); err != nil {
return count, fmt.Errorf("committing migration %s: %w", mf.name, err)View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped inner error to identify the failing SQL statement; fix the root database error it names.
- Inspect the database state for debris from the failed migration (partially created tables/columns) and reconcile so the migration is idempotent or can re-run.
- Verify the `bd` binary and the database's schema_cursor versions are consistent; upgrade `bd` rather than downgrading past applied migrations.
- Restore the database from backup and retry the migration pass if the schema is in an unrecoverable dirty state.
- If a specific migration file is corrupt or wrong, fix or replace that migration file in the embedded migration source (report upstream if it shipped with the binary).
Example fix
// before: unclear which migration/statement failed, retries blindly bd migrate up # fails: migration 0104_add_index: syntax error at ... // after: inspect and reconcile the failing statement's schema state first mysql -d .beads/beads.db -e "SHOW TABLES; SHOW CREATE TABLE issues;" # drop the partial object, fix the DB state, then re-run the migration pass bd migrate up
Defensive patterns
Strategy: try-catch
Validate before calling
// Before running migrations, verify DB reachability and version alignment
if err := db.PingContext(ctx); err != nil {
return fmt.Errorf("database unreachable before migration: %w", err)
}
// ensure binary and DB schema versions are not inverted (newer DB than binary)
// e.g. check the latest recorded version in the cursor table vs src.latest() Type guard
var dbErr *database.DBError
if errors.As(err, &dbErr) {
// narrow to a database-level failure: inspect dbErr.Code / Query
log.Printf("migration failed with DB code %v on %s", dbErr.Code, dbErr.Query)
} Try / catch
count, err := runMigrations(ctx, db, src, min, upTo, commitEachStep)
if err != nil {
var migErr interface{ Unwrap() error }
if errors.As(err, &migErr) {
log.Printf("migration pass aborted after %d applied: %v", count, err)
}
// do NOT blindly retry: inspect the inner DB error and reconcile
// partial schema debris before re-running the pass
return fmt.Errorf("apply migrations (applied %d): %w", count, err)
} Prevention
- Never kill or Ctrl-C a running migration pass; let it finish or use the documented repair path.
- Keep the `bd` binary and database versions in lockstep; don't point an old binary at a newer database.
- Back up the .beads directory before upgrading.
- Let `bd doctor` clean up partial state before re-running migrations.
- Test upgrades on a copy of a production rig first.
When it happens
Trigger: A migration file's SQL fails when run by execMigrationBody during `bd` startup/upgrade: invalid or incompatible DDL for the current Dolt version, a statement violating constraints, a conflicting schema state (e.g. column/table already exists), or the DB connection/context erroring mid-statement.
Common situations: Upgrading beads after skipping intermediate migrations; a partially applied migration from a previous crashed run leaving objects behind; custom or hand-edited migration files; a stale or corrupted embedded Dolt database; running an old `bd` binary against a database already migrated by a newer version.
Related errors
- failed to initialize schema: %w
- ignored migrations: %w
- clone from %s succeeded, but the database needs %d schema %s
- failed to recompute is_blocked: %w
- failed to migrate credential keys: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b48d35c7e2152017.
Report an issue: GitHub.