remix-run/remix · error · Error

Duplicate migration id "{id}" inferred from directory "{dire

Error message

Duplicate migration id "{id}" inferred from directory "{directoryName}"

What it means

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.

Source

Thrown at packages/data-table/src/lib/migrations-node.ts:38

 *
 * let migrations = await loadMigrations('./app/db/migrations')
 * ```
 */
export async function loadMigrations(directory: string): Promise<MigrationDescriptor[]> {
  let entries = await fs.readdir(directory, { withFileTypes: true })
  let directories = entries
    .filter((entry) => entry.isDirectory())
    .map((entry) => entry.name)
    .sort((left, right) => left.localeCompare(right))

  let migrations: MigrationDescriptor[] = []
  let seenIds = new Set<string>()

  for (let directoryName of directories) {
    let parsed = parseMigrationDirectoryName(directoryName)

    if (seenIds.has(parsed.id)) {
      throw new Error(
        'Duplicate migration id "' +
          parsed.id +
          '" inferred from directory "' +
          directoryName +
          '"',
      )
    }

    seenIds.add(parsed.id)

    let directoryPath = path.join(directory, directoryName)
    let upPath = path.join(directoryPath, 'up.sql')
    let downPath = path.join(directoryPath, 'down.sql')

    let up: string
    try {
      up = await fs.readFile(upPath, 'utf8')
    } catch (error) {

View on GitHub (pinned to 9696913134)

Solutions

  1. Rename one of the conflicting directories so its leading id is unique (bump the timestamp or add a suffix per the naming convention)
  2. If two migrations from merged branches collide, re-timestamp the newer one before running migrations
  3. Adopt unique, monotonic ids in your migration generator (date + sequence)

Example fix

# before
migrations/
  20240101000000_add_users/
  20240101000000_add_posts/   # duplicate id

# after
migrations/
  20240101000000_add_users/
  20240101000100_add_posts/
Defensive patterns

Strategy: validation

Validate before calling

let ids = directories.map(d => parseMigrationDirectoryName(d).id)
let dupes = ids.filter((id, i) => ids.indexOf(id) !== i)
if (dupes.length) throw new Error('Duplicate migration ids: ' + dupes.join(', '))

Type guard

function hasUniqueMigrationIds(directories: string[]): boolean {
  let ids = directories.map((d) => d.split('_')[0])
  return new Set(ids).size === ids.length
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/2c1ae460b64db45a. Report an issue: GitHub.