ory/hydra · error

migration %s has no corresponding down migration

Error message

migration %s has no corresponding down migration

What it means

MigrationBox.check() validates that every 'up' migration file has a matching 'down' (rollback) migration. When a migration with a given version exists in migrationsUp but no down migration with the same version is registered, this error is returned during NewMigrationBox construction, before any migrations run.

Source

Thrown at oryx/popx/migration_box.go:356

	return errors.WithStack(err)
}

// hasDownMigrationWithVersion checks if there is a migration with the given
// version.
func (mb *MigrationBox) hasDownMigrationWithVersion(version string) bool {
	for _, down := range mb.migrationsDown {
		if version == down.Version {
			return true
		}
	}
	return false
}

// check checks that every "up" migration has a corresponding "down" migration.
func (mb *MigrationBox) check() error {
	for _, up := range mb.migrationsUp {
		if !mb.hasDownMigrationWithVersion(up.Version) {
			return errors.Errorf("migration %s has no corresponding down migration", up.Version)
		}
	}

	for _, n := range mb.migrationsUp {
		if err := n.Valid(); err != nil {
			return errors.WithStack(err)
		}
	}
	for _, n := range mb.migrationsDown {
		if err := n.Valid(); err != nil {
			return errors.WithStack(err)
		}
	}
	return nil
}

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Add a corresponding .down.sql file with the same version prefix as the missing up migration
  2. Rename an existing down migration file so its version matches the up migration's version
  3. Temporarily bypass validation only if down migrations are intentionally unused (not recommended)
  4. Regenerate the migration pair with the library's scaffold/generate command to ensure both directions exist

Example fix

// before (migrations dir)
// 20240101120000_create_users.up.sql   <- no down file
// after
// 20240101120000_create_users.up.sql
// 20240101120000_create_users.down.sql  (DROP TABLE users; etc.)
Defensive patterns

Strategy: validation

Validate before calling

// Before constructing the box, verify paired files
func missingDownMigrations(dir string) ([]string, error) {
  files, err := os.ReadDir(dir)
  if err != nil { return nil, err }
  ups, downs := map[string]bool{}, map[string]bool{}
  for _, f := range files {
    name := f.Name()
    for _, suffix := range []string{".up.sql", ".down.sql"} {
      if strings.HasSuffix(name, suffix) {
        v := strings.TrimSuffix(name, suffix)
        if suffix == ".up.sql" { ups[v] = true } else { downs[v] = true }
      }
    }
  }
  var missing []string
  for v := range ups { if !downs[v] { missing = append(missing, v) } }
  return missing, nil
}

Prevention

When it happens

Trigger: Calling NewMigrationBox with a migration directory that contains an up file (e.g. 20240101120000_create_users.up.sql) but no matching down file (20240101120000_create_users.down.sql), so hasDownMigrationWithVersion(up.Version) returns false.

Common situations: Developers hand-writing migration files and only creating the .up.sql; deleting a .down.sql accidentally; tooling generating up-only migrations; copying migrations between projects without the rollback counterparts.

Related errors


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