MIGRATION_LANES.md · ADR-021 · design docs/superpowers/specs/2026-06-09-migration-collision-proofing-and-declarative-pilot-design.md
Read this before researching a collision. The collision-proofing machinery is already built and layered. When you hit a version collision, you do not need to review Supabase docs or design a bespoke fix — run the one blessed command below. The recurring pain was N parallel agents each independently re-researching and proposing different resolutions; this runbook is the one resolution.
The problem in one paragraph
Migrations are namedYYYYMMDDHHMMSS_*.sql. That 14-digit prefix is the PRIMARY KEY of
supabase_migrations.schema_migrations, so it is a global ordering key, not a real
date (the tree even holds invalid hour-24/25 prefixes — treat versions as opaque
integers, never date math). supabase db diff -f and supabase migration new both
stamp the current second, and each worktree only sees its own migrations — so N parallel
agents branched off development reliably mint colliding or out-of-order prefixes. A
fresh collision hard-fails db push; a shadowed collision (one version already
recorded on a remote) silently skips the second file → real schema drift.
The layered defense (where each catch happens)
Author/pre-commit/pre-push/PR stages are best-effort — two branches in flight at the
same time are invisible to each other. The deploy boundary is the actual guarantee:
no version collision ever reaches a remote as silent drift.
Prevent (do this every time you author a migration)
Hand-authored / caveat (DML, policy, grant) migration — mint the version, don’t let the CLI pick wall-clock:supabase db diff -f) or supabase migration new — the CLI controls
the timestamp, so reversion it immediately after generating:
db:migration:reversion is idempotent — a no-op when your migration already sorts after
origin/development. It does a best-effort git fetch origin development first; pass
--no-fetch offline, or a filename to target one file explicitly.
Resolve (you already have a collision)
You will land here from a red pre-push,migration-guard, or a deploy auto-PR.
1. The blessed command (handles the safe majority)
2. Shadowed / REFUSED (human action — rare)
Ifdb:migration-versions:fix prints REFUSED, the later file’s version is already
recorded on the remote and the file is not re-apply-safe (raw CREATE TABLE / ADD COLUMN / CREATE POLICY with no IF NOT EXISTS / DROP … IF EXISTS). Do not force it.
Per ADR-021 Addendum (2026-06-09):
- Make the file idempotent (add the
IF NOT EXISTS/DROP POLICY IF EXISTSguards), or - Rename it to the suggested next free version by hand and confirm it applies cleanly fresh.
3. Local recovery after a bot/auto rename
A version you already applied locally that gets renamed: recover the local ledger withsupabase migration repair --status reverted <old> --status applied <new>. Never
supabase db reset on the shared local stack (.claude/rules/database.md).
Do NOT
- ❌ Hand-pick a “probably free” timestamp, or copy another migration’s prefix and
+1by eye. - ❌ Edit an applied migration’s SQL to dodge a collision (ADR-021 — the only blessed edit is the automated version-prefix rename of an unapplied file).
- ❌ Research Supabase docs to design a new resolution. The resolution is the command above.
References
- Scripts:
scripts/database/next-migration-version.ts,reversion-migration.ts,ensure-unique-migration-versions.ts,check-migration-version-collisions.mjs,migration-version-lib.ts(pure helpers). - npm:
db:migration:next-version,db:migration:reversion[:check],db:migration-versions:check|fix,db:check:migration-collisions. MIGRATION_LANES.md§5.8 (author-time allocator), ADR-021 (collision-edit exception), the 2026-06-09 collision-proofing design.