{"record":{"id":"0c9b81c887d55943","repo":"ory/hydra","slug":"migration-s-has-no-corresponding-down-migration","errorCode":null,"errorMessage":"migration %s has no corresponding down migration","messagePattern":"migration (.+?) has no corresponding down migration","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/popx/migration_box.go","lineNumber":356,"sourceCode":"\treturn errors.WithStack(err)\n}\n\n// hasDownMigrationWithVersion checks if there is a migration with the given\n// version.\nfunc (mb *MigrationBox) hasDownMigrationWithVersion(version string) bool {\n\tfor _, down := range mb.migrationsDown {\n\t\tif version == down.Version {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// check checks that every \"up\" migration has a corresponding \"down\" migration.\nfunc (mb *MigrationBox) check() error {\n\tfor _, up := range mb.migrationsUp {\n\t\tif !mb.hasDownMigrationWithVersion(up.Version) {\n\t\t\treturn errors.Errorf(\"migration %s has no corresponding down migration\", up.Version)\n\t\t}\n\t}\n\n\tfor _, n := range mb.migrationsUp {\n\t\tif err := n.Valid(); err != nil {\n\t\t\treturn errors.WithStack(err)\n\t\t}\n\t}\n\tfor _, n := range mb.migrationsDown {\n\t\tif err := n.Valid(); err != nil {\n\t\t\treturn errors.WithStack(err)\n\t\t}\n\t}\n\treturn nil\n}\n","sourceCodeStart":338,"sourceCodeEnd":372,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/popx/migration_box.go#L338-L372","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (migrations dir)\n// 20240101120000_create_users.up.sql   <- no down file\n// after\n// 20240101120000_create_users.up.sql\n// 20240101120000_create_users.down.sql  (DROP TABLE users; etc.)","handlingStrategy":"validation","validationCode":"// Before constructing the box, verify paired files\nfunc missingDownMigrations(dir string) ([]string, error) {\n  files, err := os.ReadDir(dir)\n  if err != nil { return nil, err }\n  ups, downs := map[string]bool{}, map[string]bool{}\n  for _, f := range files {\n    name := f.Name()\n    for _, suffix := range []string{\".up.sql\", \".down.sql\"} {\n      if strings.HasSuffix(name, suffix) {\n        v := strings.TrimSuffix(name, suffix)\n        if suffix == \".up.sql\" { ups[v] = true } else { downs[v] = true }\n      }\n    }\n  }\n  var missing []string\n  for v := range ups { if !downs[v] { missing = append(missing, v) } }\n  return missing, nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["migrations","go","validation","sql"],"backgroundTag":"missing-down-migration","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}