Report hasn't been filed before.
What version of drizzle-orm are you using?
^1.0.0-beta.2-0f52822
What version of drizzle-kit are you using?
^1.0.0-beta.2-0f52822
Other packages
No response
Describe the Bug
The v3 migration format has a bug in migrator.js where migrations.sort() is called on an array of objects without a comparator function. This causes migrations to run in filesystem order rather than chronological order.
Problem
In migrator.js line 47-48:
const migrations = readdirSync(migrationFolderTo)
.map((subdir) => ({ path: join(migrationFolderTo, subdir, "migration.sql"), name: subdir }))
.filter((it) => existsSync(it.path));
migrations.sort(); // <-- BUG: does nothing
Since .sort() without a comparator converts elements to strings for comparison, and all objects stringify to "[object Object]", the sort is effectively a no-op:
const arr = [
{ name: '20251210095918_second' },
{ name: '20251210095732_first' }
];
arr.sort();
// Result: unchanged - still second, first
Impact
- macOS/Windows:
readdirSync typically returns entries in alphabetical order, so this bug is hidden
- Linux:
readdirSync returns entries in inode order (non-deterministic), causing migrations to run out of order
This leads to failures like:
Error: Failed query: ALTER TABLE "some_table" ADD COLUMN "new_col" ...
...because the table creation migration ran after the alter migration.
Possible test case to trigger failure on some platforms
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, expect, test } from 'vitest';
import { readMigrationFiles } from '~/migrator.ts';
let tempDir: string;
beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'drizzle-migrator-test-'));
});
afterEach(() => {
fs.rmSync(tempDir, { recursive: true, force: true });
});
test('readMigrationFiles should return migrations sorted by folder name', () => {
// Create migrations in non-alphabetical order on disk
// On Linux, readdirSync may return these in creation/inode order
const secondMigration = '20251210095918_add_column';
const firstMigration = '20251210095732_create_table';
// Create "second" migration first to increase chance of wrong order on Linux
fs.mkdirSync(path.join(tempDir, secondMigration));
fs.writeFileSync(
path.join(tempDir, secondMigration, 'migration.sql'),
'ALTER TABLE users ADD COLUMN email TEXT;',
);
fs.mkdirSync(path.join(tempDir, firstMigration));
fs.writeFileSync(
path.join(tempDir, firstMigration, 'migration.sql'),
'CREATE TABLE users (id INT);',
);
const migrations = readMigrationFiles({ migrationsFolder: tempDir });
// Migrations must be sorted chronologically by folder name
// 20251210095732 should come before 20251210095918
expect(migrations).toHaveLength(2);
expect(migrations[0]!.sql[0]).toBe('CREATE TABLE users (id INT);');
expect(migrations[1]!.sql[0]).toBe('ALTER TABLE users ADD COLUMN email TEXT;');
});
Fix
We use this as a local patch to fix it.
migrations.sort((a, b) => a.name.localeCompare(b.name));
Report hasn't been filed before.
What version of
drizzle-ormare you using?^1.0.0-beta.2-0f52822
What version of
drizzle-kitare you using?^1.0.0-beta.2-0f52822
Other packages
No response
Describe the Bug
The v3 migration format has a bug in
migrator.jswheremigrations.sort()is called on an array of objects without a comparator function. This causes migrations to run in filesystem order rather than chronological order.Problem
In
migrator.jsline 47-48:Since
.sort()without a comparator converts elements to strings for comparison, and all objects stringify to"[object Object]", the sort is effectively a no-op:Impact
readdirSynctypically returns entries in alphabetical order, so this bug is hiddenreaddirSyncreturns entries in inode order (non-deterministic), causing migrations to run out of orderThis leads to failures like:
...because the table creation migration ran after the alter migration.
Possible test case to trigger failure on some platforms
Fix
We use this as a local patch to fix it.