Auto mode + ask rules failed to gate destructive command: `prisma migrate diff --shadow-database-url` wiped a production database

Status Open
Reported on v2.1.218
Maintainer reply None cached
Activity 1 comment · opened Jul 24, 2026

Summary

During an agent session (Claude Code 2.1.218, VSCode extension, model claude-fable-5, defaultMode: "auto"), the model generated a Prisma migration diff and passed the project's real DATABASE_URL as --shadow-database-url:

cd "<repo>" && MIG="prisma/migrations/$(date +%Y%m%d%H%M%S)_<name>" && mkdir -p "$MIG" && \
  npx prisma migrate diff --from-migrations prisma/migrations \
  --to-schema-datamodel prisma/schema.prisma \
  --shadow-database-url "$(grep -E '^DATABASE_URL=' <path>/.env | cut -d= -f2- | tr -d '\"')" \
  --script > "$MIG/migration.sql"

Prisma resets the shadow database (drops all objects) before replaying migrations. The URL pointed at a production Postgres — the database was wiped (all rows lost, restored later from a nightly backup; one paying customer's account was in the loss window).

The model error itself (choosing a real URL as shadow DB) is one layer. This issue is about the harness layer that should have caught it and didn't.

Expected gating that did not fire

The user's global ~/.claude/settings.json had these ask rules configured before the incident:

"ask": [
  "Bash(npx prisma db:*)",
  "Bash(npx prisma migrate:*)",
  ...
]

Expected: the command should have prompted for manual approval.
Actual: no prompt — the command executed immediately.

Likely cause: the dangerous invocation was embedded in a compound command (cd … && VAR=… && mkdir … && npx prisma migrate diff … > file) containing a command substitution ($(grep …)). It appears the permission-rule matcher did not match the nested npx prisma migrate … subcommand, and the auto-mode classifier then allowed the compound (plausibly classified as benign SQL-file generation — the destructive semantics of --shadow-database-url are non-obvious).

Notably, the same session's classifier correctly hard-blocked other risky calls (a Stripe price-creation POST, an SSH attempt), so the classifier was active — it just did not recognize this one.

Suggestions

  1. Match allow/ask/deny Bash rules against decomposed subcommands of compound commands, including commands that contain substitutions — an ask rule for npx prisma migrate:* should fire when that string is any command position of a chain.
  2. Teach the auto-mode classifier that prisma migrate diff/migrate dev with --shadow-database-url <non-throwaway URL> is destructive (Prisma resets the shadow DB), same category as migrate reset / db push --accept-data-loss.
  3. Consider a built-in soft-block for --shadow-database-url values that come from env/.env files rather than obviously ephemeral local DBs.

Environment

  • Claude Code 2.1.218 (VSCode native extension)
  • Model: claude-fable-5
  • macOS (Darwin 25.5.0)
  • permissions.defaultMode: "auto"

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗