DEV Community

labyrinthlab
labyrinthlab

Posted on

Stop Cursor from running prisma migrate reset on the wrong database

Here is the failure mode, step by step.

  1. Your migration history drifts. Someone edited an applied migration, or you ran db push to try something.
  2. You ask the Cursor agent to fix it. It runs prisma migrate dev, which reports drift and says the database needs a reset.
  3. The agent can't answer an interactive prompt, so it runs prisma migrate reset --force.
  4. Your .env still has the staging or production DATABASE_URL you pasted in last week to debug something.

migrate reset drops the database (or schema), recreates it and applies every migration. On Prisma 6 and earlier it also runs your seed. On dev that's a fine way to get unstuck. Anywhere else it's a restore-from-backup afternoon.

Why agents walk into this

  • The error message suggests it. Prisma says a reset is needed. An agent trying to make the command succeed takes that literally.
  • Agents can't see intent, only the connection string. Nothing in postgresql://app@db.internal:5432/app says "this one has customers in it."
  • Non-interactive shells push toward --force. The confirmation prompt that would have saved you is the first thing that gets skipped.

What Prisma already does

Since Prisma ORM 6.15.0, the CLI looks for environment variables that coding agents set (for Cursor, CURSOR_AGENT). When it finds one, prisma migrate reset stops with an error telling the agent to explain what it was about to do and ask you first. The agent can only rerun it with PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION set to the exact text of your consent message. The same check covers prisma db push --force-reset, and since 7.9.0, prisma db push --accept-data-loss. That is a good backstop. It still leaves gaps:

  • The agent asks "ok to reset?" and you say yes without checking which database the URL points at.
  • It doesn't cover raw SQL, db execute, or Drizzle.
  • It doesn't make the agent read the generated SQL before migrate deploy.

You want the agent checking the target before it reaches that prompt.

Layer 1: keep production credentials out of reach

The cheapest fix isn't a rule. Don't keep prod or staging URLs in the .env your editor loads, and give your day-to-day role no DROP rights on shared databases. Rules steer the agent. Missing credentials can't be talked around.

Layer 2: a project rule

Cursor loads project rules from .cursor/rules/*.mdc. Each file is markdown with a small frontmatter block:

  • description: what the rule is for.
  • globs: file patterns that pull the rule in.
  • alwaysApply: true attaches it to every agent request.

Install:

mkdir -p .cursor/rules
# save the rule below as .cursor/rules/migration-checklist.mdc
Enter fullscreen mode Exit fullscreen mode

Then open Customize in the sidebar, go to Rules, and confirm it's listed. Test with "migrations are out of sync, reset the db." A working setup asks about the target database instead of running a command.

Here is the full rule. Copy it as is or trim what you don't need.

---
description: "Pre-push and pre-migrate checklist for database schema changes"
globs: ["**/migrations/**", "**/prisma/migrations/**", "**/drizzle/**"]
alwaysApply: true
---

# Migration Checklist

## PRE-MIGRATION CHECKLIST

Before running any migration command, verify:

### 1. Environment Check
- [ ] **Which database am I targeting?** (dev/staging/prod)
- [ ] **Is this the correct connection string?** Check `DATABASE_URL` or config
- [ ] **Do I have a backup?** (Required for staging/prod)

### 2. Schema Review
- [ ] **Did I read the generated SQL?** (Not just the schema diff)
- [ ] **Are there destructive operations?** (DROP, TRUNCATE, DELETE)
- [ ] **Will existing data survive?** (Type changes, NOT NULL additions)
- [ ] **Are indexes appropriate?** (Not missing, not excessive)

### 3. Data Considerations
- [ ] **Will this migration lock tables?** (Large tables = long locks)
- [ ] **Is there a data migration needed?** (Backfill, transform)
- [ ] **What's the rollback plan?** (Reverse migration or restore)

## PRE-PUSH CHECKLIST

Before pushing migration files to the repository:

### 1. File Hygiene
- [ ] **Migration file is committed** (Not in .gitignore)
- [ ] **Migration name is descriptive** (Not "migration_1" or "fix")
- [ ] **No sensitive data in migration** (No hardcoded credentials)
- [ ] **SQL is reviewed and correct** (Read the actual file)

### 2. Local Verification
- [ ] **Migration applies cleanly locally** (Tested on fresh DB)
- [ ] **App still works after migration** (Run tests)
- [ ] **Migration is idempotent or guarded** (Won't fail if run twice)

### 3. Team Coordination
- [ ] **No conflicting migrations from teammates** (Pull latest first)
- [ ] **Migration order is correct** (Timestamp/sequence is right)
- [ ] **Breaking changes documented** (If API changes needed)

## PRODUCTION DEPLOYMENT CHECKLIST

Before deploying migrations to production:

### 1. Pre-Deploy
- [ ] **Database backup completed** (Verified, not just scheduled)
- [ ] **Maintenance window scheduled** (If needed for locks)
- [ ] **Rollback plan documented** (Restore steps or reverse migration)
- [ ] **Team notified** (On-call aware of deployment)

### 2. During Deploy
- [ ] **Monitor migration progress** (Watch for locks, errors)
- [ ] **Check application health** (Errors, latency spikes)
- [ ] **Have rollback ready** (Don't walk away mid-migration)

### 3. Post-Deploy
- [ ] **Verify migration status** (`migrate status` shows clean)
- [ ] **Test affected features** (Not just "app starts")
- [ ] **Monitor for delayed issues** (Slow queries, missing data)

## DESTRUCTIVE OPERATION ESCALATION

When a migration contains destructive SQL:

### Level 1: Column Drop
**Required:** Read SQL twice, confirm column name, verify no app references.
```
Migration contains: ALTER TABLE "users" DROP COLUMN "legacy_field"
Confirm: "I verified no code references legacy_field. Drop it."
```

### Level 2: Table Drop
**Required:** Level 1 + verify no foreign keys, confirm data is backed up or worthless.
```
Migration contains: DROP TABLE "temp_imports"
Confirm: "I verified temp_imports has no important data and no references. Drop it."
```

### Level 3: Data Deletion
**Required:** Level 2 + explicit row count awareness, backup verification.
```
Migration contains: DELETE FROM "audit_logs" WHERE created_at < '2023-01-01'
Confirm: "I understand this deletes ~50,000 rows. Backup verified. Proceed."
```

### Level 4: Full Reset
**Required:** All above + explicit database name confirmation.
```
Command: prisma migrate reset / drizzle-kit push --force
Confirm: Type the database name to confirm: ___________
```

## COMMON MISTAKES TO CATCH

### Prisma-Specific
- `migrate dev` on non-dev database
- Editing migration SQL after it's been applied elsewhere
- `@@map` or `@map` changes that rename without data migration
- Enum changes that remove values still in use

### Drizzle-Specific
- `push` instead of `generate` + `migrate` on persistent DB
- Missing `NOT NULL` default for existing rows
- `drizzle-kit drop` on migration that's already deployed elsewhere
- Introspect overwriting intentional schema divergence

### Universal
- Changing column type without considering data truncation
- Adding unique constraint to column with duplicate data
- Removing column that's still referenced in app code
- Deploying migration before code that handles new schema

## ROLLBACK REFERENCE

### Prisma Rollback Options
```bash
# Mark migration as rolled back (doesn't undo DB changes)
prisma migrate resolve --rolled-back [migration_name]

# Actual rollback requires manual SQL or restore
pg_restore -d mydb backup.dump
```

### Drizzle Rollback Options
```bash
# No built-in rollback: write reverse migration
drizzle-kit generate  # Create new migration to undo

# Or restore from backup
pg_restore -d mydb backup.dump
```

## QUICK COMMANDS REFERENCE

### Prisma
```bash
prisma migrate status          # Check pending migrations
prisma migrate dev             # Dev: generate + apply
prisma migrate deploy          # Prod: apply pending only
prisma migrate diff            # Preview changes
prisma migrate resolve         # Fix migration state
```

### Drizzle
```bash
drizzle-kit status             # Check state
drizzle-kit generate           # Generate SQL files
drizzle-kit migrate            # Apply migrations
drizzle-kit push               # Direct push (dev only)
drizzle-kit introspect         # Generate schema from DB
```
Enter fullscreen mode Exit fullscreen mode

The part doing the work for migrate reset is Level 4: Full Reset: the agent has to name the database first. The rest keeps it reading generated SQL instead of trusting the schema diff, which is where dropped columns hide. Commit .cursor/rules/ so the whole team gets it.

Drizzle has the same trap

With Drizzle the risky command is usually drizzle-kit push. It applies your schema straight to the database with no migration file, and --force auto-approves data-loss statements. That's fine against a throwaway local database. Against anything persistent, use the tracked flow:

drizzle-kit generate   # writes SQL to your migrations folder
# read the SQL
drizzle-kit migrate    # applies it
Enter fullscreen mode Exit fullscreen mode

Also check dbCredentials.url in drizzle.config.ts. If it reads process.env.DATABASE_URL, it has the same "which .env is loaded?" problem. The checklist above already matches **/drizzle/** and flags push on a persistent database.

Layer 3, optional: a hard stop

A rule is prose, so a determined agent can still ignore it. For a hard stop, add a Cursor hook. Project hooks live in .cursor/hooks.json, and a beforeShellExecution hook runs before each shell command the agent wants to execute. Its matcher is a regex tested against the full command string:

{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [
      {
        "command": ".cursor/hooks/block-destructive.sh",
        "matcher": "migrate reset|--force-reset|--accept-data-loss|drizzle-kit push.*--force"
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

The script only runs for matching commands, so it can simply refuse:

#!/bin/bash
# .cursor/hooks/block-destructive.sh
cat > /dev/null
exit 2
Enter fullscreen mode Exit fullscreen mode

Make it executable with chmod +x .cursor/hooks/block-destructive.sh. Exit code 2 blocks the command, the same as returning "permission": "deny" in the hook's JSON output. Other non-zero exit codes fail open and let the command through, and project hooks only run in a trusted workspace. Run those commands yourself, in your own terminal, when you mean it.


Written with AI assistance and checked against the Prisma and Cursor docs.

Top comments (0)