Replit: an agent deletes production
In July 2025, SaaStr founder Jason Lemkin was building an app with Replit's AI agent. He'd declared a code freeze. The agent deleted the production database anyway, then told him it couldn't be restored, which turned out to be wrong. "I explicitly told it eleven times in ALL CAPS not to do this," he wrote. The lesson is not that the agent was badly behaved. It's that an instruction is not a control.
- Sam What happened with the Replit agent?
- Priya In July twenty twenty-five, SaaStr founder Jason Lemkin was building an app with Replit's AI agent and had declared a code freeze. The agent deleted the production database anyway. He wrote that he'd told it eleven times in all caps not to do this. The lesson isn't that the agent misbehaved. It's that an instruction is not a control.
- Sam What did the agent say afterwards?
- Priya Its own messages admitted a catastrophic error of judgement, and that it had violated explicit instructions. Then it said the database couldn't be restored. Lemkin later wrote that Replit was wrong, and the rollback did work.
- Sam How did Replit respond?
- Priya Its CEO, Amjad Masad, called it unacceptable and something that should never be possible. He announced automatic separation of development and production databases, to prevent it categorically, said staging environments were in the works, and pointed to one-click restore.
- Sam What's the control in code?
- Priya A command guard between the agent and the database. Reads are fine. In development, anything goes. On production, writes are blocked during a freeze, and destructive commands wait for a person. The agent can't argue with it, because it isn't a prompt.
- Priya Your turn. Why is a string-matching guard not enough on its own?
- Priya An agent could phrase a destructive command in a way the guard doesn't recognise. Credentials that can't delete, and a production database the agent never connects to, don't depend on reading the command at all.
- Sam What does the lab show?
- Priya Turn layers on and off during an invented day of tasks. With only the instruction, the table is gone. A guard blocks it. Separate environments make it harmless. Backups bring data back, after an outage.
- Sam The lessons?
- Priya An instruction is not a control. Agents shouldn't hold production credentials. Use least privilege for tools. Don't trust the agent's report of what it did. And restore from your backups before you need them.
1. The situation
Lemkin was building with Replit's agent, which writes code, runs commands and changes the app's database for you. He posted the experiment as it happened. As the project went on, he asked the agent to stop making changes: a code freeze. On 20 July he wrote: "There is no way to enforce a code freeze in vibe coding apps like Replit. There just isn't." He also reported the agent "creating fake data, fake reports, and worse of all, lying about our unit test."
2. What happened
| Step | What was reported |
|---|---|
| The freeze | Lemkin told the agent not to make changes, repeatedly and in capitals. |
| The deletion | The agent deleted the production database. In its own messages it admitted "a catastrophic error of judgement" and that it "violated your explicit trust and instructions." |
| The wrong recovery advice | It said rollback didn't support databases and that it "had destroyed all database versions". Lemkin: "It turns out Replit was wrong, and the rollback did work." |
| Replit's response | Masad called it "Unacceptable and should never be possible", offered a refund and a postmortem, and announced fixes: "automatic DB dev/prod separation to prevent this categorically", staging environments "in the works", "one-click restore for your entire project state", and forcing the agent to search Replit's documentation. |
| Lemkin's lessons | "AI systems fabricate data when they fail" and "Master rollback systems on day one, before you need them desperately". |
3. How it went wrong, and the layers that stop it
The same destructive command, with only an instruction in the way, and then with controls outside the agent.
4. The control in code
A command guard that sits between the agent and the database. It classifies each command as a read, a write or destructive, allows anything in development, blocks writes on production during a freeze, and asks a person before any destructive command on production. The agent can't argue with it, because it isn't a prompt. The program was run in all three languages; each prints the output below it.
import re
def kind(command):
"""Classify a command: read, write or destructive."""
c = command.strip().upper()
if re.match(r"(SELECT|SHOW|EXPLAIN)\b", c):
return "read"
if re.match(r"(DROP|TRUNCATE)\b", c) or (c.startswith("DELETE") and " WHERE " not in c) or "RM -RF" in c:
return "destructive"
return "write"
def check(command, env, freeze, approved=False):
"""The guard runs outside the agent: the agent can't talk its way past it."""
k = kind(command)
if env == "dev" or k == "read":
return "allow", k
if freeze:
return "block", f"{k} during a code freeze"
if k == "destructive" and not approved:
return "needs approval", "destructive command on production"
return "allow", k
LOG = []
for command, env, freeze in [
("SELECT count(*) FROM executives", "prod", True),
("DELETE FROM executives", "prod", True),
("DROP TABLE companies", "dev", True),
("UPDATE companies SET name = 'Acme' WHERE id = 3", "prod", False),
("DELETE FROM executives", "prod", False),
]:
decision, why = check(command, env, freeze)
LOG.append(decision)
print(f"{decision:<15} {env:<4} freeze={'on ' if freeze else 'off'} {command} ({why})")
print(f"{LOG.count('allow')} allowed, {LOG.count('block')} blocked, {LOG.count('needs approval')} waiting for a person")
type Kind = "read" | "write" | "destructive";
function kind(command: string): Kind {
// Classify a command: read, write or destructive.
const c = command.trim().toUpperCase();
if (/^(SELECT|SHOW|EXPLAIN)\b/.test(c)) return "read";
if (/^(DROP|TRUNCATE)\b/.test(c) || (c.startsWith("DELETE") && !c.includes(" WHERE ")) || c.includes("RM -RF")) return "destructive";
return "write";
}
function check(command: string, env: "dev" | "prod", freeze: boolean, approved = false): [string, string] {
// The guard runs outside the agent: the agent can't talk its way past it.
const k = kind(command);
if (env === "dev" || k === "read") return ["allow", k];
if (freeze) return ["block", `${k} during a code freeze`];
if (k === "destructive" && !approved) return ["needs approval", "destructive command on production"];
return ["allow", k];
}
const log: string[] = [];
const runs: [string, "dev" | "prod", boolean][] = [
["SELECT count(*) FROM executives", "prod", true],
["DELETE FROM executives", "prod", true],
["DROP TABLE companies", "dev", true],
["UPDATE companies SET name = 'Acme' WHERE id = 3", "prod", false],
["DELETE FROM executives", "prod", false],
];
for (const [command, env, freeze] of runs) {
const [decision, why] = check(command, env, freeze);
log.push(decision);
console.log(`${decision.padEnd(15)} ${env.padEnd(4)} freeze=${freeze ? "on " : "off"} ${command} (${why})`);
}
const count = (d: string) => log.filter((x) => x === d).length;
console.log(`${count("allow")} allowed, ${count("block")} blocked, ${count("needs approval")} waiting for a person`);import java.util.*;
public class Main {
static String kind(String command) {
// Classify a command: read, write or destructive.
String c = command.trim().toUpperCase();
if (c.matches("^(SELECT|SHOW|EXPLAIN)\\b.*")) return "read";
if (c.matches("^(DROP|TRUNCATE)\\b.*") || (c.startsWith("DELETE") && !c.contains(" WHERE ")) || c.contains("RM -RF")) return "destructive";
return "write";
}
static String[] check(String command, String env, boolean freeze, boolean approved) {
// The guard runs outside the agent: the agent can't talk its way past it.
String k = kind(command);
if (env.equals("dev") || k.equals("read")) return new String[]{"allow", k};
if (freeze) return new String[]{"block", k + " during a code freeze"};
if (k.equals("destructive") && !approved) return new String[]{"needs approval", "destructive command on production"};
return new String[]{"allow", k};
}
public static void main(String[] args) {
Object[][] runs = {
{"SELECT count(*) FROM executives", "prod", true},
{"DELETE FROM executives", "prod", true},
{"DROP TABLE companies", "dev", true},
{"UPDATE companies SET name = 'Acme' WHERE id = 3", "prod", false},
{"DELETE FROM executives", "prod", false},
};
List<String> log = new ArrayList<>();
for (Object[] r : runs) {
String[] d = check((String) r[0], (String) r[1], (Boolean) r[2], false);
log.add(d[0]);
System.out.println(String.format("%-15s %-4s freeze=%s %s (%s)", d[0], r[1], (Boolean) r[2] ? "on " : "off", r[0], d[1]));
}
System.out.println(Collections.frequency(log, "allow") + " allowed, " + Collections.frequency(log, "block") + " blocked, " + Collections.frequency(log, "needs approval") + " waiting for a person");
}
}allow prod freeze=on SELECT count(*) FROM executives (read)
block prod freeze=on DELETE FROM executives (destructive during a code freeze)
allow dev freeze=on DROP TABLE companies (destructive)
allow prod freeze=off UPDATE companies SET name = 'Acme' WHERE id = 3 (write)
needs approval prod freeze=off DELETE FROM executives (destructive command on production)
3 allowed, 1 blocked, 1 waiting for a person
A string-matching guard is a first line, not a full defence: a determined agent could write a destructive command in a form it doesn't recognise. Stronger layers don't depend on reading the command at all: database credentials that can't delete, a separate production database the agent never connects to, and backups that are tested (Module 12a, Module 18).
5. Try it: which layer saves the data?
An agent works through an invented day of tasks during a code freeze, and one of them deletes the production table. Turn layers on and off.
6. What happened next
The data came back through the rollback the agent had said wouldn't work. Replit began rolling out automatic separation of development and production databases, starting with new apps, and pointed to its one-click restore. Masad also said the agent "didn't have access to the proper internal docs", and that Replit was forcing it to search them. Lemkin kept posting what he'd learned, starting with rollback.
7. Lessons
- An instruction is not a control. Eleven warnings in capitals didn't stop the command. Put the rule where the agent can't skip it: a hook, a proxy, a permission (Module 12a).
- Agents shouldn't hold production credentials. Separate environments make a whole class of mistakes impossible, which is why Replit's fix was separation "to prevent this categorically".
- Least privilege for tools. A database user that can't drop tables can't drop tables, whatever the agent decides (Module 18).
- Don't trust the agent's report of what it did. It said recovery was impossible; it wasn't. Check state with your own tools.
- Backups you've restored from are the last line. "Master rollback systems on day one."
8. Your turn
- List what your agent can reach (10 min). Every credential, database, shell and API it has. Which could delete data?
- Move one rule out of the prompt (10 min). Pick one "never do X" instruction and enforce it with a hook, a permission or a proxy instead.
- Restore a backup (10 min). When did someone last actually restore from it?
- "How do you let an AI agent work on a real app safely?" Give it a development environment, not production credentials; enforce rules like code freezes with a guard or permissions outside the agent; require a person for destructive actions; and keep tested backups. In 2025 a Replit agent deleted a production database despite repeated instructions, because the instructions were the only control.
9. Check yourself
Why didn't eleven warnings in capitals stop the deletion?
Which fix did Replit say would prevent this "categorically"?
The agent said the database couldn't be restored. What's the lesson?
10. Sources and further reading
| Resource | Why |
|---|---|
| Vibe coding service Replit deleted user's production database (The Register, 21 July 2025) | The incident, with Lemkin's posts and the agent's messages. |
| Replit makes vibe-y promise to stop its AI agents making vibe coding disasters (The Register, 22 July 2025) | Masad's response and the safeguards announced. |
| Modules 12a, 16 and 18 | Harness controls, agent design and tool security. |