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
- Rename one of the colliding migrations so no bare id equals another's `id_name` string.
- As a workaround, pass a target string that is unique among registered migrations.
- 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
- Avoid migration names that embed id-like numeric prefixes.
- CI-check the migration set for id/name aliasing collisions.
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
- expected promise to resolve, but it rejected with: ${stringi
- ${optionName} values must be package names. Received "${pack
- files.transforms keys must use "transform-name" format. Rece
- files.globalTransforms[${index}].name must be a string
- Cannot combine "to" and "step" migration options in the same
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/403b833db507276f.
Report an issue: GitHub.