ory/hydra · error

migration version %s does not exist

Error message

migration version %s does not exist

What it means

During Down, when a migration version is shorter than or equal to 14 characters (so no legacy variant applies) and its row is not present in schema_migration, the migrator aborts with this error. It refuses to roll back a migration that is not recorded as applied.

Source

Thrown at oryx/popx/migrator.go:386

			l := mb.l.WithField("version", mi.Version).WithField("migration_name", mi.Name).WithField("migration_file", mi.Path)
			l.Debugf("handling migration %s", mi.Name)
			exists, err := c.Where("version = ?", mi.Version).Exists(mtn)
			if err != nil {
				return errors.Wrapf(err, "problem checking for migration version %s", mi.Version)
			}

			if !exists && len(mi.Version) > 14 {
				legacyVersion := mi.Version[:14]
				legacyVersionExists, err := c.Where("version = ?", legacyVersion).Exists(mtn)
				if err != nil {
					return errors.Wrapf(err, "problem checking for legacy migration version %s", legacyVersion)
				}

				if !legacyVersionExists {
					return errors.Errorf("neither normal (%s) nor legacy migration (%s) exist", mi.Version, legacyVersion)
				}
			} else if !exists {
				return errors.Errorf("migration version %s does not exist", mi.Version)
			}

			if err := mi.Valid(); err != nil {
				return errors.WithStack(err)
			}

			if mb.shouldNotUseTransaction(mi) {
				err := mi.Runner(mi, c)
				if err != nil {
					return errors.WithStack(err)
				}

				// #nosec G201 - mtn is a system-wide const
				if err := c.RawQuery(fmt.Sprintf("DELETE FROM %s WHERE version = ?", mtn), mi.Version).Exec(); err != nil {
					return errors.Wrapf(err, "problem deleting migration version %s. YOUR DATABASE MAY BE IN AN INCONSISTENT STATE! MANUAL INTERVENTION REQUIRED!", mi.Version)
				}
			} else {
				if err := mb.isolatedTransaction(ctx, "down", func(conn *pop.Connection) error {

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Compare the migrations listed in your schema_migration table with your migration files
  2. Re-insert the missing version row if the migration was actually applied, then re-run Down
  3. Confirm the correct database/DSN; the row may exist in another database
  4. Skip/patch the migration set so Down only targets recorded versions

Example fix

// before
// DELETE FROM schema_migration WHERE version='20221012'; -- accidental
// after: re-insert then Down
// INSERT INTO schema_migration (version) VALUES ('20221012');
Defensive patterns

Strategy: validation

Validate before calling

// confirm the exact version exists in the tracking table before Down
var ok bool
if err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM schema_migration WHERE version=?)", version).Scan(&ok); err != nil || !ok {
    return fmt.Errorf("version %s not recorded; refusing rollback", version)
}

Try / catch

if err := box.Down(ctx, 1); err != nil {
    if strings.Contains(err.Error(), "does not exist") {
        // reconcile schema_migration with migration files before retrying
    }
    return err
}

Prevention

When it happens

Trigger: Calling Down for a candidate migration whose version row is absent from schema_migration: count-based selection picked it because other rows exist, but this specific version was never recorded or was deleted.

Common situations: Manually deleted schema_migration rows; database restored from a partial backup; running Down on a database migrated with a different tool; legacy-version rows present but count-based selection referencing a short version migration not in the table.

Related errors


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/b300d5530fb311ed. Report an issue: GitHub.