{"record":{"id":"2c1ae460b64db45a","repo":"remix-run/remix","slug":"duplicate-migration-id-id-inferred-from-direct","errorCode":null,"errorMessage":"Duplicate migration id \"{id}\" inferred from directory \"{directoryName}\"","messagePattern":"Duplicate migration id \"(.+?)\" inferred from directory \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/data-table/src/lib/migrations-node.ts","lineNumber":38,"sourceCode":" *\n * let migrations = await loadMigrations('./app/db/migrations')\n * ```\n */\nexport async function loadMigrations(directory: string): Promise<MigrationDescriptor[]> {\n  let entries = await fs.readdir(directory, { withFileTypes: true })\n  let directories = entries\n    .filter((entry) => entry.isDirectory())\n    .map((entry) => entry.name)\n    .sort((left, right) => left.localeCompare(right))\n\n  let migrations: MigrationDescriptor[] = []\n  let seenIds = new Set<string>()\n\n  for (let directoryName of directories) {\n    let parsed = parseMigrationDirectoryName(directoryName)\n\n    if (seenIds.has(parsed.id)) {\n      throw new Error(\n        'Duplicate migration id \"' +\n          parsed.id +\n          '\" inferred from directory \"' +\n          directoryName +\n          '\"',\n      )\n    }\n\n    seenIds.add(parsed.id)\n\n    let directoryPath = path.join(directory, directoryName)\n    let upPath = path.join(directoryPath, 'up.sql')\n    let downPath = path.join(directoryPath, 'down.sql')\n\n    let up: string\n    try {\n      up = await fs.readFile(upPath, 'utf8')\n    } catch (error) {","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/data-table/src/lib/migrations-node.ts#L20-L56","documentation":"Migrations are loaded from timestamped directories (e.g. 20240101000000_add_users), and the leading id must be unique. If two directories parse to the same migration id, an Error is thrown naming the duplicate and the offending directory, because ordering and applied-state tracking would become ambiguous.","triggerScenarios":"Two migration directories with the same timestamp prefix (copying a directory to rename it and keeping the id); generating migrations in the same second without a sequence suffix; case-insensitive filesystems treating different names as one.","commonSituations":"Copying an existing migration folder as a starting point and forgetting to change its timestamp; two developers/branches generating migrations with identical timestamps that collide after merge; automated generation without a monotonic clock.","solutions":["Rename one of the conflicting directories so its leading id is unique (bump the timestamp or add a suffix per the naming convention)","If two migrations from merged branches collide, re-timestamp the newer one before running migrations","Adopt unique, monotonic ids in your migration generator (date + sequence)"],"exampleFix":"# before\nmigrations/\n  20240101000000_add_users/\n  20240101000000_add_posts/   # duplicate id\n\n# after\nmigrations/\n  20240101000000_add_users/\n  20240101000100_add_posts/","handlingStrategy":"validation","validationCode":"let ids = directories.map(d => parseMigrationDirectoryName(d).id)\nlet dupes = ids.filter((id, i) => ids.indexOf(id) !== i)\nif (dupes.length) throw new Error('Duplicate migration ids: ' + dupes.join(', '))","typeGuard":"function hasUniqueMigrationIds(directories: string[]): boolean {\n  let ids = directories.map((d) => d.split('_')[0])\n  return new Set(ids).size === ids.length\n}","tryCatchPattern":null,"preventionTips":["Generate migration ids with timestamp + sequence to guarantee uniqueness","After merging branches, scan the migrations directory for duplicate prefixes","Never copy a migration directory without renaming its timestamp"],"tags":["data-table","migrations","duplicate-id","filesystem"],"backgroundTag":"duplicate-migration-id","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}