pocketbase/pocketbase · critical

failed to apply migration %s: %w

Error message

failed to apply migration %s: %w

What it means

Thrown when a migration's Up function returns an error while the runner applies it inside a transaction. The migration file name is included. Because it happens inside RunInTransaction, the whole batch's changes for that transaction roll back; the migration is not recorded as applied.

Source

Thrown at core/migrations_runner.go:154

					}

					shouldReapply, err := m.ReapplyCondition(txApp, r, m.File)
					if err != nil {
						return err
					}
					if !shouldReapply {
						continue
					}

					// clear previous history stored entry
					// (it will be recreated after successful execution)
					r.saveRevertedMigration(txApp, m.File)
				}

				// ignore empty Up action
				if m.Up != nil {
					if err := m.Up(txApp); err != nil {
						return fmt.Errorf("failed to apply migration %s: %w", m.File, err)
					}
				}

				if err := r.saveAppliedMigration(txApp, m.File); err != nil {
					return fmt.Errorf("failed to save applied migration info for %s: %w", m.File, err)
				}

				applied = append(applied, m.File)
			}

			return nil
		})
	})

	if err != nil {
		return nil, err
	}
	return applied, nil

View on GitHub (pinned to 5d217ddb50)

Solutions

  1. Read the wrapped error — it is whatever the migration's Up returned, pinpointing the failing statement.
  2. Make migrations idempotent (IF NOT EXISTS, IF EXISTS) so re-runs and drifted schemas don't break them.
  3. Fix the DB drift the migration assumed away (e.g. drop the manually created table or column) and re-run.
  4. Use 'migrate down' for the failing migration only if it has a Down and its changes partially applied outside a transaction context.

Example fix

// before: non-idempotent migration
m.Up = func(db core.App) error {
    _, err := db.DB().Exec("CREATE TABLE reports (...)")
    return err
}

// after
m.Up = func(db core.App) error {
    _, err := db.DB().Exec("CREATE TABLE IF NOT EXISTS reports (...)")
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: verify schema assumptions the migration makes
var n int
app.DB().NewQuery("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='reports'").Row(&n)
if n > 0 && migrationCreatesReports {
    return fmt.Errorf("table reports already exists; fix drift before migrating")
}

Try / catch

_, err := runner.Up()
if err != nil {
    var me *migrationApplyError // if you wrap it; otherwise match the message prefix
    if errors.As(err, &me) || strings.Contains(err.Error(), "failed to apply migration") {
        // transaction rolled back; fix the wrapped cause (often constraint/drift),
        // make the migration idempotent, then re-run migrate up
    }
}

Prevention

When it happens

Trigger: Running migrate up where a migration's Go code fails: invalid SQL inside m.Up(txApp), unique constraint violation, table already exists (migration not idempotent when re-applied), or external state the migration depends on being absent.

Common situations: Deploying a new migration to a DB where the schema was already changed by hand; migrations written against an older schema version; data-backfill migrations hitting duplicate keys; SQLite locking during long migrations.

Related errors


AI-assisted analysis of pocketbase/pocketbase@5d217ddb50 (2026-08-15). Data as JSON: /api/errors/17b89bfa8e642096. Report an issue: GitHub.