Skip to content

Supabase Migration History Does Not Match: How to Fix It

Published Sep 7, 2026 · 9 min read

When the Supabase CLI refuses to push because the migration history does not match, it is telling you that the bookkeeping table supabase_migrations.schema_migrations on your remote project disagrees with the files in supabase/migrations. It is not telling you anything about your schema. That distinction decides which fix you need: supabase migration repair corrects the bookkeeping and touches nothing else, supabase db pull writes a fresh migration file from the live schema, and if your goal is a second environment rather than a push-able project, you can skip the repair entirely.

What does "migration history does not match" mean?

Supabase tracks applied migrations in a table called supabase_migrations.schema_migrations. Every migration you apply through the CLI inserts a version there. supabase db push compares that list against your local files and refuses to run when the two disagree, because applying files on top of an unknown remote state is how you get half-applied schemas.

Checked against CLI 2.75.0, there are two messages you are likely to hit:

The remote database's migration history does not match local files in supabase/migrations directory.
Remote migration versions not found in local migrations directory.

Older CLI releases phrased the first one differently, at more length and with a list of suggested remedies, which is why search results and issue threads quote several variants of the same error. They all mean the same thing.

The important part: none of this is a statement about whether your tables, policies and functions are correct. The remote schema can be perfectly healthy while the history is a mess, and the history can be spotless while the schema has drifted. Migration files and live schema are two different sources of truth, and this error only ever compares the bookkeeping.

How do you see which migrations are out of sync?

supabase migration list --linked prints one row per known version with three columns, Local, Remote and Time (UTC). A version appears in one column, the other, or both, and that is your whole diagnosis:

  • Both columns filled. Applied remotely, file present locally. Nothing to do.
  • Local filled, Remote empty. You have a file that was never applied. This is the normal case that db push exists for.
  • Remote filled, Local empty. The remote knows a version you have no file for. Your Git checkout is behind, or the migration was applied outside the CLI.
  • Neither, but the schema changed anyway. Not visible here at all. Somebody changed the database in the dashboard, which never writes to the history table.

That last case is the one that catches people out, and it is why migration list is a history check, not a drift check. If you want to know whether your files still describe your schema, that is a different question with a different tool, and the comparison with Supabase branching walks through it.

Why does db push say "Remote migration versions not found"?

This is the narrower error: the remote history contains a version for which no local file exists. Three causes, in the order worth checking:

  1. Your checkout is behind. Somebody else pushed a migration and you have not pulled. Boring, common, fixed by git pull.
  2. The migration was applied outside the CLI. SQL run in the dashboard editor does not register in the history table, but a colleague running db push from their machine does. Your local directory never received the file.
  3. Mixed timestamp lengths. Supabase CLI issue #6036, opened on 2026-08-03 against CLI 2.105.0, reports that 8-digit versions like 20260420 and 14-digit versions like 20260420010000 sharing a prefix sort differently locally than remotely. The merge logic then classifies the shorter version as missing locally and blocks every subsequent push, with no wrong action on your part.

Cause three matters because it is the case where the standard advice fails: your Git tree is clean, nobody touched the dashboard, and the push is still blocked.

What does supabase migration repair actually change?

Less than most people assume. The CLI reference is explicit about the mechanism:

Marking as reverted will delete an existing record from the migration history table while marking as applied will insert a new record.

That is the entire operation. supabase migration repair --status applied <version> inserts a row. --status reverted deletes one. No SQL from your migration files is executed, nothing is rolled back, and your schema is byte for byte what it was before you ran the command.

The practical consequence is worth stating plainly, because it is the trap: after a repair, migration list goes green and you have learned nothing about whether your files reproduce your schema. You have declared the bookkeeping correct, not made it correct.

Repair is the right tool when you know the database state is fine and only the record is missing or spurious. The single file that was applied by hand, the version somebody reverted manually, the leftover row from an experiment. For those, it is the correct, free, thirty-second answer, and you should stop reading here.

When should you re-baseline with supabase db pull instead?

When the files no longer describe reality, repairing the record just locks in a lie. db pull generates a new migration file from the remote schema instead. The CLI reference describes two different behaviours depending on what it finds:

If no entries exist in the migration history table, pg_dump will be used to capture all contents of the remote schemas you have created. Otherwise, this command will only diff schema changes against the remote database, similar to running db diff --linked.

So an empty history gives you a full dump of your own schemas, and a non-empty history gives you only a diff. If your history is populated but wrong, the diff is computed against a baseline you do not trust.

Two limits are easy to miss:

  • Managed schemas are excluded. auth and storage are skipped unless you ask for them explicitly with --schema auth,storage.
  • Docker is required. db pull starts a local Postgres container to compute the diff. On a machine or CI runner without a Docker daemon, the official repair path is simply not available to you.

And db pull produces a file, not an environment. No data, no Storage objects, no Edge Functions, no auth configuration.

What if the history is too far gone to repair?

There is no official "ignore the history and take the live schema" flag for brownfield projects. Supabase discussion #40721, opened 2025-11-23 with 5 comments, describes exactly this dead end for an existing production database, and the workaround the thread converges on is manual: dump a baseline with supabase db dump, delete the rows from supabase_migrations.schema_migrations on the remote, and push the baseline as migration number one.

It works. It also destroys, irreversibly, the record of which migration ran when. Take the dump first, and only do this on a project whose history you genuinely do not need for audit or rollback. If you go this route and hit permission walls during the dump, the pg_dump and pg_restore guide covers the errors you will see.

Can you get a staging environment without fixing the history first?

Yes, and this is the part that usually gets conflated. "Make my project push-able again" and "give me a second environment that matches production" are two independent goals. Only the first one requires a working migration history.

Replaying migrations into a fresh project is one way to get environment number two. Copying the live database is another, and it does not care what state your files are in. SupaClone reads the source database directly with native pg_dump/pg_restore and restores into a fresh, empty Supabase project: schemas, tables, RLS policies, functions, triggers, indexes, views, enums, extensions, and the table data you select per run. Storage buckets and files, Edge Functions and auth configuration are selectable options on the same run. Because it reads the database rather than the files, a table created in the dashboard three months ago and never written as a migration comes along like everything else. Every run ends with a field-by-field verification and a report of what was cloned, skipped, failed, or needs a manual step.

Where it does not help, stated plainly:

  • It does not repair your migration history. It never writes to your source project and never touches schema_migrations. To make your existing project push-able you still need migration repair or db pull.
  • The target must be a fresh, empty project. There is no syncing into an existing staging project.
  • Secrets are never copied. They show up as manual steps in the report.

What it buys you is decoupling. You get a working second environment today, and you can reconstruct a clean baseline there, at your own pace, instead of blocking on months of drift archaeology first. Runs are one-time payments from $9.99, with no subscription (regional pricing may apply) - see pricing for the packs.

Repair, re-baseline, or clone?

Changes the history tableChanges your schemaGives you a second environmentBrings data
supabase migration repairYes, one row inserted or deletedNoNoNo
supabase db pullOptionally, a new rowNo, it writes a fileNoNo
Clone into a fresh projectNoNo, source is read-onlyYesYes, if you select it

How do you keep the history from breaking again?

Three habits cover most of it: never run schema changes in the dashboard on a project whose migrations you care about, apply migrations through CI rather than from laptops, and run supabase migration list --linked as a check before you push rather than after it fails. The environment side of this - one project per stage, migration-driven deploys, seeding - is its own topic, covered in the dev, staging and prod workflow guide. If you are still deciding how to create the environments themselves, the five clone methods compared lays out the options.

FAQ

Does supabase migration repair change my database schema?

No. Per the CLI reference, --status applied inserts a record into the migration history table and --status reverted deletes one. No SQL is applied or rolled back, so your schema is unchanged. A green migration list after a repair means the bookkeeping is consistent, not that your files reproduce your database.

Why does supabase db push say "Remote migration versions not found in local migrations directory"?

The remote history contains a version with no matching local file. Usually your checkout is behind or the migration was applied outside the CLI. There is also a reported CLI bug, issue #6036 from August 2026, where mixing 8-digit and 14-digit timestamps that share a prefix produces inconsistent sorting and blocks pushes even when nothing is actually missing.

Can I delete supabase_migrations.schema_migrations and start over?

Technically yes, and it is the workaround discussed in Supabase discussion #40721 for existing production databases, since there is no official baseline-from-live command. You permanently lose the record of which migration ran when, so dump a baseline first and only do it on a project whose history you do not need.

Do I have to fix my migration history before creating a staging environment?

No. Replaying migrations is only one route to a second project. Cloning reads the live database instead of the files, so it works regardless of the state of your history - including objects that were only ever created in the dashboard. Your source project still needs migration repair or db pull eventually if you want to push to it again.

Clone your Supabase project without the pg_dump pitfalls

SupaClone copies schemas, tables, RLS policies, functions, triggers, and data into a fresh project - verified after every run. Pay once, no subscription.

Clone your first project