Prisma Next — Migration Review (Deployment + Concurrency)
Edit your data contract. Prisma handles the rest.
This skill is about reviewing migrations, not authoring them. It covers the questions that come up at deploy time and when multiple developers are landing migrations concurrently.
The skill teaches the system's mental model — what a ref is, what a marker is, what the migration graph is — and shows how to ask the system for its state. It does not prescribe rigid step-by-step procedures: most "review" questions are answered by understanding the model and querying the right thing. Rigid procedures are reserved for the rare case where there's literally one safe path.
When to Use
- User asks "what migrations will run when I merge this?" or "what's about to run on deploy?".
- User hit a concurrent-migration conflict ( advanced while their branch was open).
- User wants to wire up a / ref so CI can deploy against it.
- User wants to run a migration against an environment that isn't the local dev DB.
- User asks about CI integration for migrations.
When Not to Use
- User wants to author a migration → .
- User wants to fix a hash-mismatch / drift in a single env → (re-plan path) or (envelope-driven).
- User wants to edit the contract → .
Key Concepts — the navigation model
Every migration question is a navigation from an origin to a destination. Once you have this model, the rest of the skill is just "which command asks the system about which navigation."
Origin
The
origin is the database's
current contract hash. The database carries a row in PN's marker table that records
"this database is at hash X". When the CLI runs online (a
is provided, or
is set in
), PN reads the marker and that hash is the origin. Offline (no DB connection), the origin is unknown — many commands degrade to listing the on-disk migrations and skip the per-edge applied/pending status.
A live DB is therefore the authoritative source of origin. The "recorded marker" in any other artifact (refs, local cache, your assumptions) is a working copy that can drift; the live DB never does.
Destination
The destination is the contract hash you want the database to be at. Two ways to name a destination:
- A — a named pointer to a hash, stored under
migrations/app/refs/<name>
. Refs are named after environments by convention (, ) to communicate "this is where production is expected to be". The ref itself is just a hash + an optional set of required invariants; it has nothing to do with which database you connect to.
- The current contract head — implicit when no is passed. This is the hash of the current on disk.
does
not mean "connect to the staging database." It means "navigate the database I connected to (via
or config) toward whatever hash this ref points at." Database selection is orthogonal: pass
--db $STAGING_DATABASE_URL
to actually point at staging.
The migration graph
The on-disk migrations form a directed graph:
nodes are contract hashes; edges are migrations. Each migration declares a
hash and a
hash. A migration applies only when the database's current marker matches its
hash; running it advances the marker to its
hash.
queries the graph for the path from origin to destination and reports per-edge status:
- applied — on the path from to the marker (history).
- pending — on the path from the marker to the destination (what would run).
- unreachable — on the path from to the destination, but the marker is on a different branch and won't reach it without first re-routing.
Diagnostic codes
emits structured diagnostics on the result envelope (
) so the agent can branch on the code rather than parsing the prose summary. Each diagnostic also carries
(
or
), a human
, and
— the same hints the CLI prints under the summary line.
| Code | Severity | Meaning in the navigation model | Next move |
|---|
| info | Marker = destination; no edges to walk. | Nothing to do. |
MIGRATION.DATABASE_BEHIND
| info | Marker is an ancestor of the destination; N pending edges in between. | migrate --to <name> --db $URL
. |
MIGRATION.INVARIANTS_PENDING
| info | Marker reached destination structurally but missing required invariants the ref declares. | migrate --to <name> --db $URL
to take a path that covers them. |
| warn | Online, but the database has no marker row — never initialised. | (first apply writes the marker). |
MIGRATION.MARKER_NOT_IN_HISTORY
| warn | Online; marker hash is not a node in the graph. The database was changed outside the migration system. | Decide which side is truth: (accept DB as truth), (push contract to DB), (re-derive contract from DB), or (inspect first). |
| warn | Multiple valid leaves; the destination is ambiguous. | Pass , or to create one. |
| warn | Contract head is not in the graph — the contract was edited without re-planning. | to extend the graph. |
| warn | couldn't be read. | to regenerate it. |
A CI gate should read
from
output and decide based on
plus
; see
Workflow — CI below for the structure.
Workflow — "What's about to run on deploy?"
The user asks: "I'm about to merge this PR. What migrations are going to run when I deploy to staging?"
This is the navigation question:
origin = staging's live marker;
destination = the ref
(or the contract head if you haven't set one). Ask the system:
bash
pnpm prisma-next migration status --to staging --db "$STAGING_DATABASE_URL"
The command:
- Reads the staging DB's marker (the origin).
- Resolves to a contract hash (the destination).
- Renders the path between them as an ordered list of migrations, with per-edge / / status, and an explicit summary line of the form "N migration(s) behind ref 'staging'".
- Prints a header that names the config, migrations directory, the active ref, and the database connection (masked) — so the framing is visible in the output.
If you omit
, the command runs offline: it lists the migrations on disk but cannot tell you what's applied, because it has no origin. That's fine for
"what's on this branch?"; it's not fine for
"what's about to run on staging?" — for that you need staging's live marker.
If you omit
, the destination defaults to the contract head — which answers
"is this branch's contract reachable from the database, and how?", not
"what runs on deploy". Pass the ref explicitly when the question is about a specific environment.
summarises each pending migration's operations by class (
,
,
,
) and reports a destructive-op count when destructive operations are present. Surface that count to the user before they merge or deploy — destructive operations are the class that warrants manual review.
Workflow — "What state is each environment at?"
Just
migration status --db $URL
for each environment's DB. The marker (origin) comes back from the DB itself; the summary line tells you whether the environment is at the contract head, at a named ref, ahead of head, or on a divergent branch.
Concept — concurrent migrations on the same branch point
This used to be called diamond convergence in some PN docs; the situation is the same regardless of the label.
What's happening. Two topic branches each authored a migration off the same parent contract hash. The first branch merges to
; the destination ref (e.g.
) advances to that branch's
hash. Your branch's migration still has its
hash pointing at the
old parent. The migration graph, after rebase, no longer has a clean path through your migration:
- Your migration's is no longer an ancestor of the new head.
- Or your migration's is reachable, but the path through your migration arrives at a hash that's not the union of both branches' changes.
Either way, the on-disk plan is stale.
Resolution. The on-disk plan is stale because its
hash is no longer the head of the graph; apply the cluster's standard
edit → plan → apply loop to the post-rebase state and the planner produces a fresh migration whose
matches the new head.
The one thing the planner can't do for you is port custom data-transform logic from the abandoned
into the new one — schema deltas are derived from the contract, but any hand-written
operations are yours to carry across before applying. There is no separate "revalidate" step, no special "diamond apply" flow.
Workflow — set, list, get, delete refs
Refs are small artifacts. There's no per-environment lifecycle; you just point a name at a hash.
bash
pnpm prisma-next ref set production <contract-hash>
pnpm prisma-next ref list
# `ref get` was removed — use `ref list` and filter by name
pnpm prisma-next ref list | grep production
pnpm prisma-next ref delete production
writes a file at
migrations/app/refs/<name>
carrying the hash and any required invariants. Refs are commit-friendly artifacts — keep them in git; the team agrees on what
points at the same way they agree on what
is.
Workflow — apply a migration against an environment
bash
pnpm prisma-next migrate --to production --db "$PRODUCTION_DATABASE_URL"
The destination is the ref's hash; the origin is the production DB's live marker. The command computes the path between them and applies each pending migration in order, advancing the marker.
is the environment selection knob.
is the destination-hash knob. They're independent.
Concept — ref-mismatch on CI / deploy
CI reports:
"the recorded ref is at hash X; the live DB is at hash Y."
The mismatch is a fact about two pieces of state that disagree. The investigation is the same regardless of which piece is wrong:
- DB ahead of the ref. Someone applied a migration outside CI without updating the ref in git. Re-record the ref with
prisma-next ref set <ref-name> <db-marker-hash>
(commit + push); then audit how the out-of-band apply happened.
- DB behind the ref. A previous deploy was rolled back, or the DB was restored from an older backup. Either re-apply forward with
prisma-next migrate --to <ref-name> --db $URL
, or re-route the ref backward to match what's actually deployed with prisma-next ref set <ref-name> <db-marker-hash>
. The choice is the user's — name both options.
- DB on a different branch. An out-of-band schema change (manual SQL, ad-hoc migration) wrote something the migration graph doesn't model. Run to inspect the drift, then either
prisma-next contract infer
to re-derive the contract from the database, or edit the contract and run prisma-next migration plan
so the database is the eventual destination.
to silently align the ref with whatever the DB happens to be at is almost never the right move. It papers over drift that you'll pay for later.
Workflow — CI: verify a branch can advance the target environment
The gate is
migration status --to <env> --db $URL
: it computes the path from the live marker to the ref and reports it, without mutating anything. There is no
flag on
; the inspect / gate step is
.
yaml
- name: Verify staging is reachable
run: |
pnpm prisma-next migration status \
--to staging --db "$STAGING_DATABASE_URL" --json > status.json
node -e '
const s = JSON.parse(require("fs").readFileSync("status.json", "utf8"));
const warns = (s.diagnostics ?? []).filter(d => d.severity === "warn");
if (warns.length) {
console.error("Blocking diagnostics:", warns);
process.exit(1);
}
'
- name: Apply
run: pnpm prisma-next migrate --to staging --db "$STAGING_DATABASE_URL"
exits non-zero only on hard errors (unreadable migrations directory, unsatisfiable invariants, unreconstructable history). Diagnostics like
MIGRATION.MARKER_NOT_IN_HISTORY
,
,
, and
are reported on the result envelope with
but the process exits
— the agent (or a CI gate) must inspect
and fail the build itself. Use
so the gate parses a structured shape rather than the human summary.
is interactive-free and has no destructive-op confirmation prompt — the safety rails that prompt for destructive changes live on
(see the
skill). Whatever the planner put in the migration graph is what
runs; review happens at
and at
time, before the apply step.
Common Pitfalls
- Reading without for a deploy question. That asks "can this branch's contract reach the head?", not "what's about to run on staging?". Always pass the ref when the question is about a specific environment.
- Reading without for a deploy question. Without a live DB, you have no origin. The output lists what's on disk; it can't say what's applied on the environment. Pass for any high-stakes question.
- Confusing the ref with a DB connection. selects the destination hash, not the database. Pass both and explicitly.
- Treating diamond convergence as a special procedure. It's not. It's the normal edit → plan → apply loop applied to the post-rebase state. The only extra step is "port any data-transform logic from your old over."
- Running to silence a CI mismatch without understanding the cause. That can mask out-of-band changes or rollback drift. Investigate first.
What Prisma Next doesn't do yet
- Per-environment migration ordering beyond the default chain. If you need staging to skip a migration that production requires (or vice versa), the supported path is to author the per-env divergence as separate migrations and gate them in your deploy script. If you want first-class per-env routing, file a feature request via the skill.
- A built-in side-by-side "branch diff" view. There is a full-graph render () that shows branches, but no -style comparison between two branches' migration sets. Workaround: run on each branch and the output. If you want a built-in branch-comparison view, file a feature request via the skill.
Reference Files
This skill is intentionally body-only; the underlying CLI reference (
prisma-next migration status --help
,
,
) is the authoritative surface for flag-level detail. When in doubt, run
and read the actual command's description rather than guessing from this skill.
Checklist