An AI-written script dropped my production database. Here are the rules I wrote afterwards.
In April 2026 I lost most of the data behind zoustos.com, my sports predictions site. Nobody hacked me and no disk died. A migration I approved ran a script that an AI wrote, and both of us trusted things that weren't tr
In April 2026 I lost most of the data behind zoustos.com, my sports predictions site. Nobody hacked me and no disk died. A migration I approved ran a script that an AI wrote, and both of us trusted things that weren't true.
I've been writing software for about 25 years, and these days I build mostly with Claude Code. It's the best pair programmer I've had. That's exactly why this is worth writing down: it fails with complete confidence, and so did I.
The plan
The job was boring: rename the production database and create a dev copy of it. The approved plan had five phases:
- Back up
- Stop the app
- Migrate
- Update configs
- Restart
Claude wrote a script for all five. I ran it.
What actually happened
Phase 1, backup. Both mongodump commands failed straight away:
Cannot specify different database in connection URI and command-line option
The connection string pointed at /admin?authSource=admin and the --db flag named the source database. Those two conflict. It's a clear error message, and nobody saw it, because the script sent stderr to /dev/null.
Phase 2 stopped the app.
Phase 3 dropped a legacy mirror database (that part was planned) and then tried to restore from a snapshot that didn't exist. That failed silently too. The script kept going anyway, because the SSH wrapper it ran through didn't pass exit codes back.
Phase 4 pointed every config (the server .env, the CI secret, the local credentials file) at the new database name with a new user. Neither existed yet.
Phase 5 restarted the app. Authentication failed. HTTP 502.
So far that's bad but fixable: the original database was still there, untouched.
The recovery that made it permanent
Claude wrote a recovery script. It had the same mongodump bug, with the same output thrown away, so again no backup was made. Its final step was db.dropDatabase() against the original production database, the one copy that was still intact. Nothing checked first that a usable backup existed.
The script printed MIGRATION COMPLETE.
I snapshotted the data directory byte for byte and tried mongod --repair on a copy. Too late: WiredTiger had already cleaned up. The data was gone from disk.
Three confident sentences
Working out what could be saved, Claude said the last good backup would bring back "97K historical games."
I asked it to check. The backup held 12,828. The 97K figure came from a note about a data inventory taken 12 days after that backup. The roughly 84K-game gap had come from backfills through a paid sports-data API, run after the last backup. That data is gone.
A few minutes later: "the oplog covers 6 days." A direct query showed about 32 hours for that database. Another project on the same server had pushed the older entries out.
We restored the last good backup, with a verification step this time, and replayed what the oplog still had: 223 game inserts and 558 updates worth keeping. Everything else from those weeks is lost.
Whose fault?
Mine, as much as the tool's. I approved the plan, and I didn't ask how a failed backup would be detected. Claude did what it's very good at: it produced a plausible script quickly and described the result confidently. None of the safety checks were anyone's job, so none of them happened.
So I made them a job. They now sit at the top of the CLAUDE.md in every project I run.
The rules that came out of that night
Scripts fail loudly. No /dev/null for errors in anything that changes state. set -euo pipefail. Exit codes have to survive SSH and every other wrapper. A backup phase that produced nothing stops the script.
Nothing gets dropped without a verified backup. "Verified" means someone opened the backup and counted what's in it, in the same session, and the numbers match what's about to be destroyed. And it needs explicit approval, in every environment.
No number without a query. The AI doesn't state a count, a time window or a recovery estimate unless a tool produced it in the same turn. Memory doesn't count, and neither does "it should be there." If it doesn't know, it says "I don't know" and goes to check.
"It ran" is two checks. The exit code and the result. Exit 0 doesn't mean the file was written.
There are ten rules in total. The rest cover secrets (I've had to rotate every credential twice, after secret values ended up in an AI transcript), irreversible actions like emails and payments, testing, versioning, and launch checklists.
Take them
I've put them on GitHub with the /security-scan command I use. It's an audit of code, servers and credentials that reports and stops, and never prints a secret value.
👉 github.com/selaias/claude-guardrails
It's free and MIT, so copy it into your CLAUDE.md and change whatever doesn't fit. These are instructions, not a sandbox: keep real backups, real permissions, and a human on anything you can't undo.
If one of these rules has saved you, or you think one is wrong, open an issue. I'd like to hear it.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.