Summary
supabase db push fails with "Remote migration versions not found in local migrations directory" when local migration files mix 8-digit and 14-digit version timestamps that share the same numeric prefix.
Environment
- Supabase CLI:
2.105.0 (Go implementation, apps/cli-go)
- OS: Windows
Minimal reproduction
Local supabase/migrations/ directory containing:
20260420_foo.sql
20260420010000_bar.sql
Remote supabase_migrations.schema_migrations containing both versions:
Running supabase db push --dry-run fails:
Remote migration versions not found in local migrations directory.
Make sure your local git repo is up-to-date. If the error persists, try repairing the migration history table:
supabase migration repair --status reverted 20260420
Expected behavior
Since both 20260420 and 20260420010000 exist locally and remotely, the CLI should recognize them as applied and report the remote database as up to date (or push only genuinely pending migrations). migration repair --status reverted 20260420 should not be suggested, because the version is present locally.
Actual behavior
The CLI reports version 20260420 as missing locally and suggests reverting it. Removing the row via repair --status reverted does not help — the same versions are then reported as unapplied local files instead (ErrMissingRemote), and there is no way to reconcile via db push.
Root cause
FindPendingMigrations (in apps/cli-go/pkg/migration/apply.go) merges two lists assuming both are in the same order:
-
Remote list: built from SELECT version FROM supabase_migrations.schema_migrations ORDER BY version (pkg/migration/list.go) — pure lexicographic string ordering of versions. With ORDER BY version, 20260420 < 20260420010000 (prefix string is smaller).
-
Local list: built via fs.ReadDir in ListLocalMigrations (pkg/migration/list.go), which sorts by full file name. 20260420010000_bar.sql sorts before 20260420_foo.sql because the 9th character '0' (0x30) is less than '_' (0x5F).
So the two lists disagree on relative order. In the merge loop:
if remote == local { j++; i++ }
else if remote < local { missing = append(missing, remote); i++ }
else { unapplied = append(unapplied, localMigrations[j]); j++ }
When remote = "20260420" is compared against the first local file 20260420010000_bar.sql (version 20260420010000), the branch remote < local marks 20260420 as missing. By the time the merge reaches the actual 20260420_foo.sql file, the remote cursor has already advanced past 20260420, so it is classified as unapplied instead of matching. The version ends up permanently "missing" and ErrMissingLocal is returned.
Impact
Projects that historically created migrations with 8-digit timestamps (as the legacy TypeScript CLI accepted) and later added 14-digit migrations sharing the same numeric prefix can never run db push again, because the ordering inversion produces a false "missing local migration" error for every affected 8-digit version.
Affected pattern (8-digit version + any 14-digit version with the same 8-digit prefix):
20260420 + 20260420010000
20260428 + 20260428010000
20260501 + 20260501010000
20260502 + 20260502010000
Suggested fix
In FindPendingMigrations (or in ListLocalMigrations), order the local migrations by their extracted version (migrateFilePattern group 1) instead of by raw file name, so both lists share a consistent ordering and the merge stays aligned. Alternatively, compare using the extracted version number in both sides and tolerate out-of-order file names.
Summary
supabase db pushfails with "Remote migration versions not found in local migrations directory" when local migration files mix 8-digit and 14-digit version timestamps that share the same numeric prefix.Environment
2.105.0(Go implementation,apps/cli-go)Minimal reproduction
Local
supabase/migrations/directory containing:Remote
supabase_migrations.schema_migrationscontaining both versions:Running
supabase db push --dry-runfails:Expected behavior
Since both
20260420and20260420010000exist locally and remotely, the CLI should recognize them as applied and report the remote database as up to date (or push only genuinely pending migrations).migration repair --status reverted 20260420should not be suggested, because the version is present locally.Actual behavior
The CLI reports version
20260420as missing locally and suggests reverting it. Removing the row viarepair --status reverteddoes not help — the same versions are then reported as unapplied local files instead (ErrMissingRemote), and there is no way to reconcile viadb push.Root cause
FindPendingMigrations(inapps/cli-go/pkg/migration/apply.go) merges two lists assuming both are in the same order:Remote list: built from
SELECT version FROM supabase_migrations.schema_migrations ORDER BY version(pkg/migration/list.go) — pure lexicographic string ordering of versions. WithORDER BY version,20260420<20260420010000(prefix string is smaller).Local list: built via
fs.ReadDirinListLocalMigrations(pkg/migration/list.go), which sorts by full file name.20260420010000_bar.sqlsorts before20260420_foo.sqlbecause the 9th character'0'(0x30) is less than'_'(0x5F).So the two lists disagree on relative order. In the merge loop:
When
remote = "20260420"is compared against the first local file20260420010000_bar.sql(version20260420010000), the branchremote < localmarks20260420as missing. By the time the merge reaches the actual20260420_foo.sqlfile, the remote cursor has already advanced past20260420, so it is classified as unapplied instead of matching. The version ends up permanently "missing" andErrMissingLocalis returned.Impact
Projects that historically created migrations with 8-digit timestamps (as the legacy TypeScript CLI accepted) and later added 14-digit migrations sharing the same numeric prefix can never run
db pushagain, because the ordering inversion produces a false "missing local migration" error for every affected 8-digit version.Affected pattern (8-digit version + any 14-digit version with the same 8-digit prefix):
20260420+2026042001000020260428+2026042801000020260501+2026050101000020260502+20260502010000Suggested fix
In
FindPendingMigrations(or inListLocalMigrations), order the local migrations by their extracted version (migrateFilePatterngroup 1) instead of by raw file name, so both lists share a consistent ordering and the merge stays aligned. Alternatively, compare using the extracted version number in both sides and tolerate out-of-order file names.