Summary
There is a behavioural divergence between local supabase db push --linked (CLI 2.95.2) and the hosted Supabase Branching CLI run by the Supabase GitHub App (slug supabase, client ID Iv1.b91a6d8eaa272168) on push to a production branch.
After fully canonicalizing supabase_migrations.schema_migrations on the remote project using supabase migration repair --linked --status applied <version> for every local migration version:
supabase migration list --linked shows every row with Local + Remote matched.
supabase db push --linked --dry-run prints Remote database is up to date.
- The
schema_migrations table holds 136 rows, each with version, name, and a non-null statements array (5–36 statements per row depending on migration).
…the hosted check still fails with:
ERROR: duplicate key value violates unique constraint "schema_migrations_pkey" (SQLSTATE 23505)
Key (version)=(001) already exists.
At statement: 0
INSERT INTO supabase_migrations.schema_migrations(version, name, statements) VALUES($1, $2, $3)
i.e. the hosted CLI is attempting to INSERT a tracking row for migration version 001 despite the canonical row already existing — it is not skipping already-applied migrations the way the local CLI does.
Reproduction
- Repo has 136 migration files in
supabase/migrations/:
- 81 in legacy format
001_*.sql … 081_*.sql
- 55 in canonical timestamp format
20260301000000_*.sql … 20260364000000_*.sql
- On the remote project, run
supabase migration repair --linked --status applied <every version>.
- Confirm locally:
$ supabase migration list --linked
# All rows show Local | Remote populated.
$ supabase db push --linked --dry-run
Remote database is up to date.
- Push any commit to the production branch on GitHub.
- The Supabase GitHub App's "Supabase Preview" check fails with the INSERT collision above.
Expected
Hosted CLI consults schema_migrations and skips already-applied versions exactly like local db push does. After migration repair --status applied, no further apply work should be needed.
Actual
Hosted CLI appears to either (a) bypass the "is this version already applied?" check, or (b) interpret the row's statements column or some other field differently than the local CLI. It then attempts a plain INSERT (no ON CONFLICT) for version = '001' and aborts on the duplicate-key error.
Evidence the local state is canonical
SELECT version, name, array_length(statements, 1) AS stmt_count
FROM supabase_migrations.schema_migrations
WHERE version = '001';
-- version: '001'
-- name: 'initial_schema'
-- stmt_count: 21
All 136 rows have name populated and statements as a non-null text[] of parsed SQL.
$ supabase migration list --linked
Local | Remote | Time (UTC)
----------------|----------------|---------------------
001 | 001 | 001
…
20260363000000 | 20260363000000 | 20260363000000
20260364000000 | 20260364000000 | 20260364000000
$ supabase db push --linked --dry-run
DRY RUN: migrations will *not* be pushed to the database.
Remote database is up to date.
What we've tried
- Manual SQL canonicalize.
DELETE FROM schema_migrations followed by INSERT (version, name, statements) VALUES ('001', 'initial_schema', NULL), … for every local file. Hosted CLI failed with the same INSERT collision. (We suspected statements=NULL triggered re-apply.)
supabase migration repair --linked --status applied <version> for all 136 versions. This populated statements correctly. Local CLI reports in sync. Hosted CLI still fails with the same INSERT collision. ← current state
- Empty commit re-push to retrigger the check. No change.
Prior to canonicalize, the same project was failing with Remote migration versions not found in local migrations directory — different error pattern from when the tracking table had legacy mixed-format rows (suffixed-only version='019_rls_policies' instead of canonical version='019', name='rls_policies'). The repair fixed that error but uncovered this second one.
Ask
- Confirm the exact command sequence the GitHub App runs on push to a production branch (so we can reproduce locally).
- Identify what about our
schema_migrations content the hosted CLI considers "needs re-apply" despite migration repair having marked everything applied.
- Provide a documented procedure for "I have manually rebuilt schema_migrations and need the hosted CLI to accept it as ground truth" — currently the local CLI honours
migration repair while the hosted one does not.
If this is a known issue, a workaround we can apply repo-side (config flag, file content marker, etc.) would also unblock us.
Environment
- Local OS: Windows 11 Pro 26200, bash via Git Bash
- Node 20.x, pnpm
- Supabase CLI:
npx supabase@2.95.2
- Not using Supabase Preview Branches (single-DB-per-project); failing check fires on direct production-branch push.
Summary
There is a behavioural divergence between local
supabase db push --linked(CLI 2.95.2) and the hosted Supabase Branching CLI run by theSupabaseGitHub App (slugsupabase, client IDIv1.b91a6d8eaa272168) on push to a production branch.After fully canonicalizing
supabase_migrations.schema_migrationson the remote project usingsupabase migration repair --linked --status applied <version>for every local migration version:supabase migration list --linkedshows every row with Local + Remote matched.supabase db push --linked --dry-runprintsRemote database is up to date.schema_migrationstable holds 136 rows, each withversion,name, and a non-nullstatementsarray (5–36 statements per row depending on migration).…the hosted check still fails with:
i.e. the hosted CLI is attempting to
INSERTa tracking row for migration version001despite the canonical row already existing — it is not skipping already-applied migrations the way the local CLI does.Reproduction
supabase/migrations/:001_*.sql…081_*.sql20260301000000_*.sql…20260364000000_*.sqlsupabase migration repair --linked --status applied <every version>.$ supabase migration list --linked # All rows show Local | Remote populated. $ supabase db push --linked --dry-run Remote database is up to date.Expected
Hosted CLI consults
schema_migrationsand skips already-applied versions exactly like localdb pushdoes. Aftermigration repair --status applied, no further apply work should be needed.Actual
Hosted CLI appears to either (a) bypass the "is this version already applied?" check, or (b) interpret the row's
statementscolumn or some other field differently than the local CLI. It then attempts a plainINSERT(noON CONFLICT) forversion = '001'and aborts on the duplicate-key error.Evidence the local state is canonical
All 136 rows have
namepopulated andstatementsas a non-nulltext[]of parsed SQL.What we've tried
DELETE FROM schema_migrationsfollowed byINSERT (version, name, statements) VALUES ('001', 'initial_schema', NULL), …for every local file. Hosted CLI failed with the same INSERT collision. (We suspectedstatements=NULLtriggered re-apply.)supabase migration repair --linked --status applied <version>for all 136 versions. This populatedstatementscorrectly. Local CLI reports in sync. Hosted CLI still fails with the same INSERT collision. ← current statePrior to canonicalize, the same project was failing with
Remote migration versions not found in local migrations directory— different error pattern from when the tracking table had legacy mixed-format rows (suffixed-onlyversion='019_rls_policies'instead of canonicalversion='019', name='rls_policies'). The repair fixed that error but uncovered this second one.Ask
schema_migrationscontent the hosted CLI considers "needs re-apply" despitemigration repairhaving marked everything applied.migration repairwhile the hosted one does not.If this is a known issue, a workaround we can apply repo-side (config flag, file content marker, etc.) would also unblock us.
Environment
npx supabase@2.95.2