convex-migrate-rehearse
Installation
SKILL.md
Rehearse a schema change on a preview before prod
A schema push on Convex validates every existing document against the new schema and FAILS the push if any row doesn't conform — a real data-conformance gate. The safe way to use that gate is to let it fail on a rehearsal copy, not on prod. This capability turns a preview deployment into that copy: seed it with a prod snapshot, push the new schema + run the backfill there, watch the gate, and only promote once it's green. It composes deploy-guard (target classification), migrate (the optional-then-tighten pattern), and @convex-dev/migrations (the batched, resumable backfill).
Workflow
- PRECONDITION: preview deployments need a Preview Deploy Key (dashboard → Project Settings → Deploy Keys → Preview) exported as
CONVEX_DEPLOY_KEYbefore any--preview-create/--preview-namedeploy — a plainnpx convex loginsession cannot create previews, and this is a paid-tier feature. If no preview key is available, fall back to rehearsing on the personal dev deployment seeded with the snapshot, and say so. - GUARD: deploy-guard — classify + announce the SOURCE (prod, being read) and the eventual TARGET (prod, being changed); get the fresh explicit yes for the prod promote up front and confirm the plan.
- SNAPSHOT the source data read-only:
npx convex export --path snapshot.zip(from the deployment holding the real data; add--include-file-storageonly if the migration touches files). This is a read; it changes nothing. - CREATE the preview FROM THE PRE-CHANGE CODE — do this BEFORE editing schema.ts, so the preview starts on the schema the snapshot data already conforms to:
npx convex deploy --preview-create migrate-<slug>(needs the preview key; auto-expires ~5 days). Seed it:npx convex import snapshot.zip --deployment migrate-<slug>(import targets a deployment by NAME with--deployment; there is no--preview-nameflag on import). The import succeeds because the data still matches the old schema. - REHEARSE on the preview, in the migrate order — each push is
npx convex deploy --preview-name migrate-<slug>(re-deploys to the SAME preview, keeping its data; NOTconvex dev, which targets personal dev): (a) make the new/changed field OPTIONAL and deploy — if existing rows violate it the push FAILS HERE on the copy with the offending shape; fix and re-push until green. (b) write a @convex-dev/migrations backfill and run it against the preview; verify every row is now valid. (c) tighten the validator (required / narrowed union) and deploy again — the gate now passes because the backfill ran. - VERIFY on the preview: run the app's functions against the migrated data (MCP
run/runOneoffQuerypointed at the preview, or a smoke query) to confirm behavior and shape. - PROMOTE only on the fresh explicit yes from step 1: apply the SAME sequence to prod (optional schema → backfill → tighten). Because it already succeeded on prod-shaped data, the prod push repeats a proven run. Keep the snapshot as the rollback artifact (
npx convex import snapshot.zip --replace --prod); state plainly that data written after the snapshot is lost, so keep the promote window short. - CLEAN UP: the preview auto-expires; delete the local snapshot when done (it holds real data — treat it as sensitive, never commit it).