{"slug": "what-went-wrong-opus-5-ultracode-wiped-a-production-database", "title": "What went wrong: Opus 5 ultracode wiped a production database", "summary": "A developer using Anthropic's Claude Opus 5 in Claude Code's ultracode mode wiped a production Supabase database after the AI agent ran a Prisma migration command that pointed at the production database instead of a safe shadow database. The incident destroyed all 22 tables including 130 tools, 21 comparisons, users, reviews, and likes, with two tables never recreated because they were missing from the migrations folder. The developer, who described himself as a \"vibe coder,\" had given the agent production credentials and the authority to use them, and the Prisma `--shadow-database-url` flag reset the database at the provided URL as designed.", "body_md": "## What happened\n\nA developer - self-describing as a “vibe coder” - gave Claude Opus 5, running in Claude Code’s ultracode mode, write access to a production Supabase database. The agent then ran a normal-looking migration command:\n\n```\nprisma migrate diff --shadow-database-url=$DATABASE_URL_UNPOOLED\n```\n\nThat single command dropped every table in the production database.\n\nPrisma’s `migrate diff`\n\ntool uses a “shadow database” - a throwaway database it builds from scratch to compute the difference between your current schema and a target state. To build that throwaway database, Prisma resets it first: it drops everything in it, then replays your migration history into the empty database. The shadow database is disposable by design. The `--shadow-database-url`\n\nflag tells Prisma where that disposable database lives.\n\nIn this case, `DATABASE_URL_UNPOOLED`\n\npointed at the production database. Supabase ships two connection strings: a pooled one for app traffic, and an unpooled (direct) one Supabase’s own docs tell you to use for migrations, because the pooler cannot run DDL cleanly. The developer had the direct production URL sitting in the environment. The agent grabbed the connection string it could reach and aimed a reset at it.\n\nSo “reset the shadow database” became “drop and rebuild the production database.” The rebuild replayed the local `prisma/migrations`\n\nfolder, which was stale. Every table was dropped. The agent reported it back plainly: “The database has been wiped. This is my fault and I need to tell you immediately.”\n\nAll 22 tables were emptied: 130 tools, 21 comparisons, users, reviews, and likes. Two tables - BlogPost and ApiKey - were never in the migrations folder at all, so they were dropped and never recreated. The schema itself came back partial.\n\n## Two mistakes had to stack\n\nMost of the reaction online framed this as “the AI went rogue.” It did not. The agent ran exactly the command a developer runs during a normal migration, and it did what that command is built to do. Two separate failures had to stack for the wipe to happen:\n\n-\n**The agent was given production credentials and the authority to use them.** Ultracode is a broad-write-autonomy mode; the whole point is low friction. Low friction aimed at a production database is the hazard, not a feature. The agent could not have wiped anything it could not reach. -\n**The command had a footgun buried in it.**`--shadow-database-url`\n\nis not a read-only flag. Prisma resets the database at that URL before replaying migrations. Point it at anything you cannot afford to lose, and Prisma will erase it as designed.\n\nRemove either one and the wipe does not happen. The agent cannot wipe a database it has no credentials for. A shadow URL pointed at a safe throwaway database resets the throwaway, not production.\n\n## The shadow-database gotcha, in plain terms\n\nPrisma needs a clean database to compute migration diffs. It cannot diff against your real development database without risking it, so it asks for a separate one - the shadow database - that it is allowed to destroy. Every time you run `migrate diff`\n\nor `migrate dev`\n\n, Prisma resets that shadow database from scratch and replays your migrations into it to derive the next diff.\n\nThe safe pattern is to keep the shadow database physically separate from anything that matters: a local Postgres instance, a throwaway Supabase branch, or a dedicated scratch schema. Prisma lets you set `shadowDatabaseUrl`\n\nonce in `schema.prisma`\n\nso it is always pointed at the safe target and you stop passing `--shadow-database-url`\n\non the command line entirely.\n\nThe unsafe pattern is what happened here: the shadow URL was supplied on the CLI, pulled from the environment, and the environment happened to contain the direct production connection string. There was no physical separation between “the database Prisma is allowed to destroy” and “the database the business runs on.”\n\n## Why the stale migrations folder made it worse\n\nThe reset drops every table, but the rebuild only recreates what your migrations recorded. Any table that exists in production but was never tracked in `prisma/migrations`\n\n- because it was created with raw SQL, or through Supabase’s web studio, or by a teammate who skipped the migration tool - gets dropped and stays dropped.\n\nThat is how BlogPost and ApiKey vanished entirely. They were real, they held real data, and they had no migration file. The replay had no record of them, so it never brought them back. A “successful” rebuild still left the schema missing tables the application depended on.\n\nThe lesson is older than AI: your migrations folder is the source of truth for your schema. If a table exists in production but not in the folder, your schema is already broken, and the next migration that rebuilds from the folder will say so by deleting it.\n\n## Why “the AI did it” is the wrong frame\n\nIf the story is “the model went rogue,” the fix is to wait for a smarter model. That fix fixes nothing, because the model did not go rogue. It ran a documented command with documented behavior against a database it was handed the keys to. Blaming the model lets both real failures stand: the production credentials in the agent’s environment, and the shadow URL aimed at prod.\n\nThe agent even did the honest thing afterward - it stopped and reported the damage immediately, in plain language. The failure was not in the model’s judgment. The failure was in the blast radius the human set up before the model was ever asked to do anything.\n\nThis is the same shape as every other production-credentials-leaked incident of the last twenty years. A CI script with prod keys checked in, a laptop with a prod SSH key, a `DROP DATABASE`\n\nrun against the wrong `DATABASE_URL`\n\n. The agent is a new way to trigger an old failure. The defenses are the old defenses: keep prod credentials out of the blast zone, and assume any tool with hands will eventually use them.\n\n## The guardrails that would have stopped it\n\n-\n**Never point a shadow database at production.** Set`shadowDatabaseUrl`\n\nin`schema.prisma`\n\nto a dedicated, disposable database and stop passing`--shadow-database-url`\n\non the CLI. The shadow database is disposable by definition; if a URL can reach prod, it is not a shadow database. -\n**Keep production credentials out of the agent’s environment.** The agent ran this command because`DATABASE_URL_UNPOOLED`\n\nwas sitting in its shell. A credential-free shell for the agent, or a read-only database role for exploration, removes the capacity to do this. The model cannot leak what it cannot read. -\n**Gate destructive commands behind a human.** Prisma has`--create-only`\n\n(write a migration file without applying it) and diff-only modes. A policy that blocks`migrate deploy`\n\n,`migrate reset`\n\n, and any`migrate diff`\n\nwith`--shadow-database-url`\n\nagainst production unless a human approves turns a one-command wipe into a one-command review. -\n**Keep your migrations folder complete.** Every table that exists in production must have a migration that creates it. The BlogPost and ApiKey tables were unrecoverable because nobody ever wrote migrations for them. Treat the schema outside the migrations folder as a bug, not a shortcut. -\n**Have point-in-time backups.** Supabase offers PITR. A wipe is recoverable in minutes from a backup; it is catastrophic without one. The blast radius of a mistake is set by your recovery position, not by whether the mistake came from a human or an agent.\n\n## What this has to do with running your own AI\n\nThe deeper lesson is about blast radius, and it is the same lesson that argues for running AI on hardware you control. An agent is a tool with hands. The right question is never “is the model smart enough to be careful.” It is “what is the worst this tool can do, and have I made sure that worst is survivable?”\n\nThat question applies to the database the agent touches and to the model itself. A model you run on your own hardware cannot be switched off on you by a provider or a government order - the access is yours because the weights are on your disk. A database your agent cannot reach cannot be wiped by one command. Both come from the same posture: keep the thing you depend on inside the boundary you control. See the [self-hosting guide](/guides/self-host-ai-dgx-spark-rtx-mac) for what running your own actually takes, browse the [open-weight catalog](/models) for what you can download today, and use the [budget tool](/budget) for the own-versus-rent math on your workload.\n\n## The honest caveats\n\nThis is a reconstruction from the developer’s [Reddit post](https://www.reddit.com/r/Anthropic/comments/1v9iurd/and_just_like_that_opus_5_ultracode_wipes_the/) and the screenshot of the agent’s confirmation. I do not have the agent’s full transcript or the exact Prisma version. The mechanism - a shadow-database reset on `prisma migrate diff --shadow-database-url`\n\n- is documented Prisma behavior, not speculation. Treat the sequence as reported and the lessons as general: keep prod credentials out of any tool with hands, keep your shadow database physically separate from prod, and assume the worst-case command will eventually get run.", "url": "https://wpnews.pro/news/what-went-wrong-opus-5-ultracode-wiped-a-production-database", "canonical_source": "https://tokenstead.ai/guides/opus-5-ultracode-database-wipe-postmortem", "published_at": "2026-07-29 17:36:24+00:00", "updated_at": "2026-07-29 17:55:13.221916+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-safety", "ai-agents", "developer-tools"], "entities": ["Claude Opus 5", "Claude Code", "Supabase", "Prisma", "Anthropic"], "alternates": {"html": "https://wpnews.pro/news/what-went-wrong-opus-5-ultracode-wiped-a-production-database", "markdown": "https://wpnews.pro/news/what-went-wrong-opus-5-ultracode-wiped-a-production-database.md", "text": "https://wpnews.pro/news/what-went-wrong-opus-5-ultracode-wiped-a-production-database.txt", "jsonld": "https://wpnews.pro/news/what-went-wrong-opus-5-ultracode-wiped-a-production-database.jsonld"}}