{"record":{"id":"d4541abbb3421ec2","repo":"hasura/graphql-engine","slug":"found-duplicate-migrations-for-version-d-s","errorCode":null,"errorMessage":"found duplicate migrations for version %d\n- %s\n- %s","messagePattern":"found duplicate migrations for version (.+?)\n- (.+?)\n- (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/migrate/source/migration.go","lineNumber":71,"sourceCode":"\t\tMigrations: make(map[uint64]map[Direction]*Migration),\n\t}\n}\n\nfunc (i *Migrations) Append(m *Migration) (err error) {\n\tvar op errors.Op = \"source.Migrations.Append\"\n\tif m == nil {\n\t\treturn errors.E(op, stderrors.New(\"migration cannot be nill\"))\n\t}\n\n\tif i.Migrations[m.Version] == nil {\n\t\ti.Migrations[m.Version] = make(map[Direction]*Migration)\n\t}\n\n\t// reject duplicate versions\n\tif migration, dup := i.Migrations[m.Version][m.Direction]; dup {\n\t\treturn errors.E(\n\t\t\top,\n\t\t\tfmt.Errorf(\n\t\t\t\t\"found duplicate migrations for version %d\\n- %s\\n- %s\",\n\t\t\t\tm.Version,\n\t\t\t\tm.Raw,\n\t\t\t\tmigration.Raw,\n\t\t\t),\n\t\t)\n\t}\n\n\ti.Migrations[m.Version][m.Direction] = m\n\ti.buildIndex()\n\n\treturn nil\n}\n\nfunc (i *Migrations) buildIndex() {\n\ti.Index = make(uint64Slice, 0)\n\tfor version := range i.Migrations {\n\t\ti.Index = append(i.Index, version)","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/hasura/graphql-engine/blob/724551b9ae87845594ef0408cff0e50eb6c90dc5/cli/migrate/source/migration.go#L53-L89","documentation":"Returned by source.Migrations.Append (used while scanning the migrations directory) when two migration files claim the same version number and the same direction. The CLI indexes migrations by version+direction, so a duplicate makes rollback/apply ambiguous and is rejected, listing both conflicting file names.","triggerScenarios":"Having two files like 12_create_table.up.yaml and 12_add_users.up.sql in migrations/ — same version 12, same direction (up or down), regardless of extension or name suffix; triggered by NewMigrate/Scan or any migrate command that reads the source.","commonSituations":"Copy-pasting a migration file and editing it instead of generating a new one; two developers creating migrations independently and merging branches; mixing .sql and .yaml for the same version; renaming files so versions collide.","solutions":["Rename one of the two files listed in the error to the next unused version number (both its .up and .down files)","If both are equivalent, delete one of them","Going forward, generate migrations with `hasura migrate create` so versions are allocated sequentially","After fixing, re-run `hasura migration status` to confirm a clean scan"],"exampleFix":"# before\nmigrations/12_create_table.up.yaml\nmigrations/12_add_users.up.sql   # duplicate version 12\n\n# after\nmigrations/12_create_table.up.yaml\nmigrations/13_add_users.up.sql   # bump to unused version","handlingStrategy":"validation","validationCode":"// Pre-scan for duplicate versions before running migrations\nseen := map[int64]bool{}\nentries, _ := os.ReadDir(\"migrations\")\nfor _, e := range entries {\n    parts := strings.SplitN(e.Name(), \"_\", 2)\n    if v, err := strconv.ParseInt(parts[0], 10, 64); err == nil {\n        if seen[v] { return fmt.Errorf(\"duplicate version %d\", v) }\n        seen[v] = true\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := src.Scan(); err != nil {\n    if strings.Contains(err.Error(), \"found duplicate migrations\") {\n        // renumber one of the listed files\n    }\n}","preventionTips":["Generate migrations only via `hasura migrate create`","Review migrations directory in code review for version collisions after merges","Keep both .up and .down files for a version together"],"tags":["migrations","duplicate-version","filesystem","hasura"],"backgroundTag":"duplicate-migration-version","analyzedSha":"724551b9ae87845594ef0408cff0e50eb6c90dc5","analyzedAt":"2026-08-28T07:32:55.105Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}