InterviewKata
AI engineering · Case study 05 · AI in the real world

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.

A 2-minute conversation: Sam asks, Priya explains
0:00 / 2:05

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."

Real case, reportedFrom The Register's reports of 21 July and 22 July 2025, which quote Lemkin's and Replit CEO Amjad Masad's posts on X. Details that reports disagree on, such as record counts, are left out. The commands in the demos are invented.

2. What happened

StepWhat was reported
The freezeLemkin told the agent not to make changes, repeatedly and in capitals.
The deletionThe 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 adviceIt 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 responseMasad 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

8. Your turn

  1. List what your agent can reach (10 min). Every credential, database, shell and API it has. Which could delete data?
  2. 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.
  3. Restore a backup (10 min). When did someone last actually restore from it?
Say it in the interview
  • "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?

Enforce rules outside the agent: guards, permissions, separate environments.

Which fix did Replit say would prevent this "categorically"?

If the agent never touches production, it can't delete it.

The agent said the database couldn't be restored. What's the lesson?

An agent's account of what it did can be wrong.

10. Sources and further reading

ResourceWhy
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 18Harness controls, agent design and tool security.