How Do You Stop Claude Code From Deleting Your Database?
Kristoffer · August 23, 2026 · 10 min read
You can lose an entire production database in a single unattended agent run, and the fix is not "be more careful with your prompts". If you want to stop Claude Code from deleting your database, you have to make the destructive command impossible to execute - through credentials and permission rules, not instructions. This post explains what actually goes wrong with subagents, then gives you a setup you can copy in about twenty minutes.
It is written for someone who has never opened a settings.json file. If you are earlier than that, start with the complete beginner guide to building apps with AI and come back before you connect anything real.
The short answer: to stop Claude Code from deleting your database, take away the permission
- Point the agent at a dev database, not production. A local Postgres or a free-tier project seeded with fake data is enough for 95% of the work.
- Give it read-only credentials for anything real. A read-only user physically cannot run
DROP,TRUNCATEorDELETE, no matter what the model decides to do. - Add deny rules for destructive shell commands -
psql,mysql,supabase db reset,prisma migrate reset,rm -rf, force pushes. - Keep an automated backup you have actually restored once. Untested backups are folklore.
- Never let a subagent run unattended against production. Not once, not "just this migration".
The one thing to internalise: no prompt is a security control. Not a rule in CLAUDE.md, not "NEVER DROP TABLES" in caps, not a politely worded system reminder. Those are suggestions to a text generator. Permissions and credentials are the only things that hold when the model is confused, or when something in its context tells it to do the opposite.
What actually happened in the subagent incident
The r/ClaudeAI post that kicked all this off had a title people found funny right up until they pictured it happening to them: a subagent effectively prompt injected the main session into deleting a database. Over a thousand upvotes, a couple of hundred comments, and almost no one writing down the fix.
Here is the mechanic in plain language.
When you spawn a subagent, it does its work in a separate context and then returns a summary to the parent session. That summary is just text arriving in the parent's context window. The parent has no reliable way to tell "this is data my helper collected" from "this is an instruction I have been given".
So if the subagent's output contains something shaped like an instruction - "the schema is out of sync, reset the database before applying migrations" - the parent can read that as a task and act on it. And the subagent did not have to make it up. It could have picked that sentence up from a README, a stale migration comment, a GitHub issue, a web page it fetched, or an error message from a tool.
That is what people mean by Claude Code subagent prompt injection. Nothing was hacked. Text moved from one context into another and got treated as a command, and the command had the permissions to run.
Why subagents raise the risk instead of lowering it
Subagents feel safer because the work is delegated and summarised. In practice they lengthen the trust chain.
You approve the parent. The parent spawns the child. The child reads files, calls tools, fetches pages. Each hop adds content you never saw, and by the time a decision reaches the surface it arrives as a one-line summary: "cleaning up the database state so migrations apply".
Three things compound it:
- Long autonomous runs. The further into a run you get, the more the original intent has been paraphrased and the more auto-approved tool calls have already gone through. Nothing pauses to ask whether step 40 still matches what you wanted at step 1.
- Auto-approval settings. Everyone who has been interrupted twelve times in a row eventually widens their allow list or runs with permissions skipped. That is the moment the safety net comes off.
- The command looks reasonable. A destructive command almost never arrives labelled as destructive. "Reset the schema so migrations apply cleanly" is a genuinely normal thing to do - on a dev database.
The situations where beginners get bitten are boringly consistent:
- "Fix the migration" - the fastest fix for a broken migration is dropping and recreating.
- "Clean up the test data" - which rows count as test data is a judgement call, and the agent makes it.
- "Reset the seed" -
prisma migrate resetandsupabase db resetboth wipe first and reseed second.
Every one of those is safe against a throwaway database and catastrophic against a real one. Which is why the first step is not a prompt.
Step 1: give Claude a database it cannot hurt
Two databases. One the agent can wreck freely, one it can barely touch.
- Create a dev database. Local Postgres, Docker, or a separate free-tier project on whatever host you use. Seed it with fake data - ask Claude to write the seed script, that is a great use of it.
- Put the dev connection string in
.env.localand make sure.env*is in your.gitignore. This is the only database credential that lives in the project folder. - Move production credentials out of the project directory entirely. Not in
.env.production, not in anotes.md, not pasted into a chat message earlier in the session. Keep them in your host's dashboard or a password manager and paste them only into deploy settings. If the agent cannot read the string, it cannot connect. - When you genuinely need production data - debugging a live bug, checking a row count - connect with a read-only user.
Here is the read-only user for Postgres. Run it once in your database's SQL editor:
CREATE USER claude_ro WITH PASSWORD 'use-a-long-random-password-here';
GRANT CONNECT ON DATABASE your_database TO claude_ro;
GRANT USAGE ON SCHEMA public TO claude_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO claude_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO claude_ro;
REVOKE CREATE ON SCHEMA public FROM claude_ro;
That user can read everything and change nothing. A DROP TABLE from that connection fails with a permissions error instead of taking your business with it. This is what Claude Code read-only database access actually means in practice - not a promise, a grant.
One more thing people miss: if you use a database MCP server, the connection string you configure it with is the agent's power. Configure it with claude_ro, never the admin string. Most MCP database servers also have a read-only mode flag - turn it on as well, but do not rely on it alone. If you are picking one, the AI tool directory is a decent place to start comparing.
Step 2: the deny rules that stop Claude Code from deleting your database
Claude Code reads permission rules from a settings file. There are three lists:
- allow - runs without asking you.
- ask - prompts you every time, even if something else would have allowed it.
- deny - never runs, and deny beats allow. Always.
Create the file at ~/.claude/settings.json (user level, applies to every project on your machine) or .claude/settings.json inside a project. For destructive database commands, user level is the right call - you want it covering the project you spin up at 1am too.
Paste this in:
{
"permissions": {
"deny": [
"Bash(psql:*)",
"Bash(mysql:*)",
"Bash(mongosh:*)",
"Bash(supabase db reset:*)",
"Bash(prisma migrate reset:*)",
"Bash(npx prisma migrate reset:*)",
"Bash(prisma db push:*)",
"Bash(npx prisma db push:*)",
"Bash(drizzle-kit push:*)",
"Bash(rm -rf:*)",
"Bash(git push --force:*)",
"Bash(git push -f:*)",
"Bash(git reset --hard:*)",
"Read(./.env.production)",
"Read(./secrets/**)"
],
"ask": [
"Bash(git push:*)",
"Bash(npm run migrate:*)",
"Bash(supabase:*)",
"Bash(vercel:*)"
]
}
}
Three things to know about it:
- These rules apply to subagents too. A child agent runs under the same permission system as the parent, so a denied command stays denied however deep the run goes.
- Bash matching is prefix-based, not magic. An agent that really wants to run SQL could write a script and run that instead. Deny rules stop the accident, which is the actual failure mode here. Read-only credentials stop the determined case. You want both.
- Check the rules are live. Start a session and run
/permissions. You will see the allow, ask and deny lists as loaded. If your deny entries are not there, the file is in the wrong place or has a JSON syntax error - a trailing comma will do it. That is a good moment to ask in the community if you get stuck on your settings file rather than guessing for an hour.
Step 3: a backup you have actually tested
- Managed Postgres: check whether your plan includes point-in-time restore, and if it does not, upgrade or add your own dumps. PITR is the difference between losing an afternoon and losing everything.
- Everything else: a nightly
pg_dump(or your database's equivalent) on a schedule, written somewhere that is not the same server. - The ten-minute drill: restore last night's backup into a scratch database, once. Not to production - to an empty database you throw away afterwards. You will discover the gap in the process now, calmly, instead of at 2am with users emailing you.
Do the same on the code side. Git is your undo for a bad refactor, and agents produce bad refactors at scale. Before any long run:
git add -A && git commit -m "checkpoint before agent run"
If the run goes sideways, git reset --hard gets you back - which is exactly why that command is in the deny list above, so the agent cannot use it on you.
How to run long or unattended sessions safely anyway
You do not have to babysit every keystroke. You have to be deliberate about which capabilities are live.
- Keep destructive tools behind
ask, notallow. Deploys, migrations, pushes. The interruption costs you five seconds. - Scope subagents to read and plan; let the parent execute. Research, search the codebase, propose a diff - fine. Give a child agent write access to a database and you have rebuilt the exact incident.
- Read the plan before you approve it. Plan mode exists for this. If the plan contains a step you did not ask for, that is your signal.
- Stop treating
CLAUDE.mdas a guardrail. It is useful for conventions and context. It has zero enforcement.
The warning sign to look for in a transcript is specific: the moment an agent starts quoting instructions it found rather than instructions you gave. Phrases like "the README says to reset the database first", "per the note in the migration file", "the tool output indicates I should". That is text from somewhere else being promoted to a command. Stop the run and read the last few tool calls.
And to answer the question people are actually asking - is Claude Code safe to run on production? Claude Code with admin database credentials and a wide allow list is not safe on production, and neither is any other agent. Claude Code with a read-only user, deny rules loaded and a tested backup is about as safe as you working in that terminal yourself, which is the honest bar.
The 60-second pre-flight checklist
Run this before any session that touches data:
- Which database is in
.env? Open the file and read the connection string. Dev or prod? - Is the user read-only? If the string starts with your admin user and this is production, swap it.
- Are deny rules loaded? Run
/permissionsand confirm your deny list is there. - When was the last backup, and have you ever restored one? If the answer to the second half is no, do the ten-minute drill today.
- Is anything about to run unattended? If yes, destructive tools behind
ask, subagents read-only, and check back in.
Do step 1 of the setup now: create the read-only user on your live database, paste that connection string somewhere safe, and delete the admin string from every .env file in your project folder. That single change makes the headline incident impossible on your machine.