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
- Add a corresponding .down.sql file with the same version prefix as the missing up migration
- Rename an existing down migration file so its version matches the up migration's version
- Temporarily bypass validation only if down migrations are intentionally unused (not recommended)
- 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
- Always generate migrations with the library's create command so up/down pairs are produced together
- Add a CI check that scans the migrations dir for unpaired .up.sql files
- Never delete a .down.sql without removing its .up.sql counterpart
- Code-review new migration folders for paired files
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
- could not parse template %s
- could not execute migration template %s
- problem checking for migration version %s
- cookiex: purpose must be non-empty and must not contain a pi
- invalid JWK key
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/0c9b81c887d55943.
Report an issue: GitHub.