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

  1. Read the wrapped inner error to identify the failing SQL statement; fix the root database error it names.
  2. 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.
  3. Verify the `bd` binary and the database's schema_cursor versions are consistent; upgrade `bd` rather than downgrading past applied migrations.
  4. Restore the database from backup and retry the migration pass if the schema is in an unrecoverable dirty state.
  5. 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

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


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