afterbuild/ops
ERR-336/Prisma · Database
ERR-336
Error: P3009 migrate found failed migrations in the target database

appears when:When a previous prisma migrate deploy failed mid-way, schema drift exists, or the shadow DB user lacks create privileges

Prisma migration failed in production

A partial migration is worse than no migration. Prisma refuses to proceed until you mark the stuck one as applied or rolled back — no amount of retries will fix it alone.

Last updated 17 April 2026 · 7 min read · By Hyder Shah
Direct answer
Run prisma migrate status against the production DATABASE_URL. If it shows a failed migration, either finish the DDL manually and mark it --applied, or revert the partial DDL and mark it --rolled-back. Never re-run migrate deploy on a stuck migration without resolving first — it fails on the same step every time.

Quick fix for Prisma migration failed in production

scripts/resolve-migration.sh
bash
01# Resolve a stuck migration in production (read-only diagnosis first)02npx prisma migrate status03 04# If status shows a migration in the "failed" state:05# 1. Manually finish or revert the DDL in a transaction06psql "$DATABASE_URL" -c "BEGIN; -- inspect the partial state; ROLLBACK;"07 08# 2. Mark the failed migration as resolved, not re-applied09npx prisma migrate resolve --applied "20260410_add_index_users_email"10# or if the DDL was rolled back manually:11npx prisma migrate resolve --rolled-back "20260410_add_index_users_email"12 13# 3. Now safe to run remaining migrations14npx prisma migrate deploy15 16# For drift (prod has schema not in migrations):17# Option A: baseline from prod18npx prisma db pull19# review the diff, commit as an empty migration marked applied20npx prisma migrate resolve --applied "0_init"
The three-step recovery: status, manual finish or revert, resolve — then continue deploying

Deeper fixes when the quick fix fails

01 · Provide an explicit shadow database for managed Postgres

.env
bash
01# .env — pointing at a separate empty DB the Prisma user owns02DATABASE_URL="postgres://user:pw@neon-main.example.com/appdb"03PRISMA_MIGRATE_SHADOW_DATABASE_URL="postgres://user:pw@neon-shadow.example.com/shadow"04 05# On Neon: create a second branch called "shadow" and use its connection string06# On Supabase: create a second project for migrations-only use07# On Railway/Fly: spin up a throwaway Postgres and wire only in dev
Managed Postgres users rarely have CREATE DATABASE — give Prisma a dedicated shadow

02 · Baseline an existing production database

scripts/baseline.sh
bash
01# When prod has schema that is not in any migration (drift)02# 1. Pull the current schema into schema.prisma03npx prisma db pull04 05# 2. Create an initial migration from the current schema06mkdir -p prisma/migrations/0_init07npx prisma migrate diff \08  --from-empty \09  --to-schema-datamodel prisma/schema.prisma \10  --script > prisma/migrations/0_init/migration.sql11 12# 3. Mark it applied without running it13npx prisma migrate resolve --applied 0_init14 15# Now future migrations build on this baseline
Accept current prod as the new baseline — safest path when history is unknown

03 · CI check that catches drift before deploy

.github/workflows/migrations.yml
yaml
01# .github/workflows/migrations.yml02name: Check migrations03on: pull_request04jobs:05  check:06    runs-on: ubuntu-latest07    steps:08      - uses: actions/checkout@v409      - uses: actions/setup-node@v410        with: { node-version: 20 }11      - run: npm ci12      - name: Migration status vs production13        run: npx prisma migrate status14        env:15          DATABASE_URL: ${{ secrets.PROD_READONLY_DATABASE_URL }}16      - name: Migration diff must be empty or additive17        run: |18          npx prisma migrate diff \19            --from-url "${{ secrets.PROD_READONLY_DATABASE_URL }}" \20            --to-schema-datamodel prisma/schema.prisma \21            --exit-code
Block any PR that would produce drift or a destructive migration against production

Why AI-built apps hit Prisma migration failed in production

Prisma tracks every migration in a meta table called _prisma_migrations. Each row records the migration name, the SQL applied, the checksum, and a status. When you run prisma migrate deploy, Prisma reads the folder of migration files, compares against the rows in _prisma_migrations, and applies anything missing. If one step of a migration fails — say, a CREATE INDEX CONCURRENTLY that hit a lock timeout — Prisma writes a row with finished_at = null and marks it failed. On the next deploy, Prisma sees the stuck row and refuses to continue. The reasoning is correct: re-running a migration that partly succeeded could corrupt the schema.

AI-generated code adds three failure modes on top. First, scaffolds frequently ship with destructive changes in migrations (column drops, type narrowing) that Prisma refuses by default on production databases. You get Data loss warning: this migration will... and the deploy aborts. Second, Lovable and Bolt tend to create a fresh schema.prisma in every session, overwriting your migration history. Third, when working against Neon branches, the branch baseline may diverge from main, so the migration files reference a schema state that does not exist on main.

The dev-only variant is the shadow database error. prisma migrate dev creates a throwaway database to verify the migration applies cleanly to a blank slate. If your Postgres user does not have CREATE DATABASE privileges — as is standard on managed providers like Supabase and Neon — the shadow step fails with permission denied to create database. The fix is to provide a dedicated shadow database via PRISMA_MIGRATE_SHADOW_DATABASE_URL, pointing at a separate empty database the user can drop and recreate.

The last failure is drift: production has tables or columns that are not in any migration. That happens when someone runs raw SQL against prod, or when a previous deployment created schema via prisma db push instead of a tracked migration. migrate deploy detects drift, refuses to proceed, and asks you to baseline. Resolving drift is manual: either db pull the current prod schema into an initial migration and mark it applied, or revert the drifted changes to match the migration history.

Prisma migration failed in production by AI builder

How often each AI builder ships this error and the pattern that produces it.

AI builder × Prisma migration failed in production
BuilderFrequencyPattern
LovableEvery schema rewriteRegenerates schema.prisma from scratch, overwrites migration history
Bolt.newCommonUses prisma db push in dev, then tries migrate deploy in prod — drift
CursorCommonSuggests destructive column renames without a data-preserving intermediate step
Base44SometimesHard-codes shadow DB URL to main DATABASE_URL — corrupts prod
Replit AgentRareSkips migrations entirely, ships with raw SQL on startup

Related errors we fix

Stop Prisma migration failed in production recurring in AI-built apps

Still stuck with Prisma migration failed in production?

Emergency triage · $299 · 48h turnaround
We restore service and write the root-cause report.
start the triage →

Prisma migration failed in production questions

What is Prisma schema drift and why does it block migrations?+
Schema drift is when your production database has tables, columns, or indexes that are not in the Prisma migration history. Drift usually comes from someone running raw SQL against prod without a migration, or switching branches that had different migrations applied. Prisma detects drift when you run migrate deploy, refuses to proceed, and tells you to investigate. The fix is either prisma db pull + baseline, or prisma migrate resolve --applied for each known-good migration.
Why does Prisma need a shadow database?+
Prisma creates a throwaway shadow database during prisma migrate dev to verify that your migration, when applied to a clean copy of the schema, produces the exact model your schema.prisma defines. It is a safety net against handwritten SQL that drifts from the model. In production (migrate deploy) the shadow DB is not used — so shadow DB errors almost always come from dev, not prod. Use the PRISMA_MIGRATE_SHADOW_DATABASE_URL env var to point at a separate database the dev user can create and drop.
How do I resolve a failed migration in production?+
Run prisma migrate status to see which migration is stuck. If the partial DDL is safe to keep, manually finish applying it with raw SQL, then run prisma migrate resolve --applied <migration_name> to mark it complete in the _prisma_migrations table. If the partial changes need to be reverted, roll them back manually, then prisma migrate resolve --rolled-back <name>. Never re-run migrate deploy on a failed migration without first resolving it — it will fail on the same step again.
Why does my migration work on Neon branch but fail on main?+
Neon branches are copy-on-write clones. If you developed against a branch that was created before a previous migration ran on main, your local history is out of sync with main's history. When Prisma compares _prisma_migrations on main vs your migration folder, names do not line up. Use prisma migrate status against the main branch URL to see the real state, then either apply missing migrations in order or reset the branch from main before continuing.
How much does an Afterbuild Labs Prisma audit cost?+
Our Auth, Database and Integrations service covers Prisma migration recovery. For a drifted production database, diagnosis and resolution takes 1-3 hours depending on how far prod has drifted from the migration history. Fixed-fee. Deliverables: clean migration state, a documented runbook, and a CI check that runs prisma migrate diff against prod on every PR to catch future drift before it lands.
Next step

Ship the fix. Keep the fix.

Emergency Triage restores service in 48 hours. Break the Fix Loop rebuilds CI so this error cannot ship again.

About the author

Hyder Shah leads Afterbuild Labs, shipping production rescues for apps built in Lovable, Bolt.new, Cursor, Replit, v0, and Base44. our rescue methodology.

Sources