remix-run/remix · error · Error

Ambiguous migration target "{to}". Matches: {matches}

Error message

Ambiguous migration target "{to}". Matches: {matches}

What it means

The `to` target matched more than one registered migration. This happens when one migration's bare id equals another's full `id_name` string — both comparisons succeed, so the runner refuses to guess and reports all matches.

Source

Thrown at packages/data-table/src/lib/migrations/runner.ts:81

  migrations: MigrationDescriptor[],
  to: string | undefined,
): string | undefined {
  if (to === undefined) {
    return undefined
  }

  // Accept either a bare migration id or the full `id_name` directory form and
  // normalize to the bare id so range filtering compares ids consistently.
  let matches = migrations.filter(
    (migration) => migration.id === to || migration.id + '_' + migration.name === to,
  )

  if (matches.length === 0) {
    throw new Error('Unknown migration target: ' + to)
  }

  if (matches.length > 1) {
    throw new Error(
      'Ambiguous migration target "' +
        to +
        '". Matches: ' +
        matches.map((migration) => migration.id + '_' + migration.name).join(', '),
    )
  }

  return matches[0].id
}

async function assertMigrationIntegrity(
  migrations: MigrationDescriptor[],
  journal: MigrationJournalRow[],
  direction: MigrationDirection,
): Promise<void> {
  let migrationMap = new Map(migrations.map((migration) => [migration.id, migration]))

  for (let row of journal) {

View on GitHub (pinned to 9696913134)

Solutions

  1. Rename one of the colliding migrations so no bare id equals another's `id_name` string.
  2. As a workaround, pass a target string that is unique among registered migrations.
  3. Audit `registry.list()` for id/name aliasing before targeting.

Example fix

// before
// migration A: id '20240101123045', name '42_add_index'
// migration B: id '20240101123045_42', name 'add_index'
await runMigrations(db, { to: '20240101123045' })

// after
// rename A to name 'add_users_index' (no aliasing)
await runMigrations(db, { to: '20240101123045' })
Defensive patterns

Strategy: validation

Validate before calling

let matches = registry
  .list()
  .filter((m) => m.id === to || m.id + '_' + m.name === to)
if (matches.length > 1) throw new Error('ambiguous target, pass a unique string')

Type guard

function isUnambiguousTarget(registry: MigrationRegistry, to: string): boolean {
  return (
    registry.list().filter((m) => m.id === to || `${m.id}_${m.name}` === to).length === 1
  )
}

Try / catch

try {
  await runMigrations(db, { direction: 'up', to })
} catch (error) {
  if (error instanceof Error && error.message.startsWith('Ambiguous migration target')) {
    // error message lists all matches; pick one and re-run
  }
  throw error
}

Prevention

When it happens

Trigger: A migration named such that its full `id_name` string collides with another migration's bare numeric id, e.g. migration A has id `X` and name `Y_Z`, and migration B has id `X_Y_Z`. Then `to: 'X'` or the colliding string matches both.

Common situations: Migration names that begin with another migration's id-like segment; unusual hand-written ids/names that alias each other.

Related errors


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