# Vibe Coding Without Losing Your Data: Database Precautions

*Published 2026-09-24 · Vibe Coding, Databases, Data, AI*

> When you vibe code, the agent runs migrations, UPDATEs, and DELETEs with no backup and no WHERE guard. How to keep an AI agent from quietly destroying the data it's supposed to protect.

Source: https://www.yashthakur.com/blog/vibe-coding-data-precautions

---

**The short answer:** the quiet danger of vibe coding a data layer is that the agent will happily run a destructive migration, an un-`WHERE`d `UPDATE`, or a `DELETE` with no backup behind it — and the demo still looks fine right up until the data is gone. Protect yourself before you let it touch the database: a backup plus verified point-in-time recovery before any migration, forward-only migrations a human reads first, no destructive operation in production without a guard, transactions around every multi-step write, and a deliberate plan for PII. The code is reversible. The data usually isn't.

Choosing *which* database to use is a separate decision — I covered that in [how to choose a backend you won't regret](/blog/how-to-choose-a-backend). This post is about the thing that's easier to get catastrophically wrong: not losing or corrupting the data once it's in there. When you vibe code, the agent writes migrations and data operations the same confident way it writes a component. The difference is that a bad component throws an error you can see, and a bad `DELETE` returns `200 OK` on an empty table.

## Back up *before* the migration, and prove you can restore

The single highest-leverage habit: a backup and a verified restore path *before* any schema change, not as something you'll "set up later." An agent asked to add a column will cheerfully rewrite a table, and if that migration goes sideways on real data, your only way back is a backup that already existed.

"We have backups" is not the same as "we can restore." Point-in-time recovery — the ability to roll the database back to a specific second before the bad migration ran — is what actually saves you, and the only way to know it works is to have restored from it at least once. Postgres gives you this (`pg_dump` for snapshots, WAL archiving for PITR); most managed providers hand it to you with a toggle. Turn it on, then test a restore into a throwaway database so you find the gaps while it's cheap.

## Migrations: forward-only, reviewed, and never silently destructive

Let the agent generate migrations, but treat each one as a change a human reads before it runs. Two rules keep them safe:

- **Forward-only.** Don't rely on a "down" migration to undo a mistake in production — once data has changed shape, the reverse migration often can't reconstruct what was lost. Roll *forward* with a new corrective migration, and lean on your backup for true disaster recovery.
- **Separate the destructive step.** Adding a column is safe. Dropping one, dropping a table, or rewriting a type is not. Keep those in their own reviewed migration, ship them after the code that stopped using the old column, and never let "add a field" and "drop three others" ride in the same agent-generated change.

## Never run a destructive operation in prod without a guard

This is the one that bites hardest, because the agent's version looks almost identical to the safe version — it's just missing the line that matters.

```SQL
-- ❌ what the agent writes when you say "mark the orders as shipped"
-- no WHERE: every row in the table is now 'shipped'
-- no transaction: nothing to roll back when you notice
UPDATE orders SET status = 'shipped';

DELETE FROM users;  -- "clean up the test users" → the whole table is gone

-- ✅ scoped, wrapped, and verified before it commits
BEGIN;

  -- SELECT first and read the count — does it match what you expect?
  SELECT count(*) FROM orders WHERE id = ANY($1);

  UPDATE orders
    SET status = 'shipped'
    WHERE id = ANY($1);         -- scoped to the exact rows

  DELETE FROM users
    WHERE created_at < now() - interval '1 day'
      AND is_test = true;       -- scoped, not "everyone"

-- rows look right? COMMIT.  wrong count? ROLLBACK and nothing happened.
COMMIT;
```

The habits that make this safe are boring and non-negotiable: scope every `UPDATE`/`DELETE` with a `WHERE`, `SELECT` the affected rows first and read the count, and wrap the whole thing in a transaction so a surprise is a `ROLLBACK` instead of an incident. In application code, require a `WHERE` at the query-builder level and block raw destructive SQL in the production path entirely.

## Transactions, or partial writes will corrupt your state

Any operation that touches more than one row or table has to be all-or-nothing. The classic vibe-coding bug is a "transfer" that debits one account, then the process dies before it credits the other — now the money has evaporated and the database is internally inconsistent with no error anywhere. An agent optimizing for "it works in the happy path" routinely skips the transaction, because the happy path never fails.

Wrap multi-step writes in a single transaction so they commit together or not at all. If the agent split a logical write across two calls on two connections, that's not atomic no matter how close together they run — thread one transaction through the whole operation and commit once.

## Handle PII like it's radioactive

The data model is also where privacy mistakes ossify. An agent will log a full request body (password and email included), store personal data it was never asked to collect, and keep it forever — because none of that breaks a test. Decide this deliberately:

- **Don't log PII.** Redact emails, tokens, and payment details before anything hits your logs or your error tracker. Logs get shipped to third parties and kept for months.
- **Encrypt at rest** and minimize what you collect — the safest field is the one you never stored.
- **Give it a retention policy.** Delete personal data when you no longer need it; "keep everything forever" is a liability, not a feature.

## Review the schema before it ossifies

Your schema is the hardest thing in the whole stack to change later, so it's worth a human read *before* real data accumulates behind it. The agent tends to flatten a genuinely relational model into one wide table with JSON blobs because that's the fastest thing to generate — and it's fine until you need to query inside those blobs or keep them consistent. Get the relational shape right early, while the only thing a migration has to move is test data.

## Use agents and skills deliberately

- **A backend-architecture / backend-patterns skill** to review the data model and migration strategy before the first real row lands — ask it specifically "what in this schema will be painful to change once we have production data?"
- **A security-and-hardening skill** pointed at how PII flows through the app: what gets logged, what's encrypted, what's retained, and where real data might leak into fixtures or traces.
- **A coding-standards skill** to enforce the non-negotiables in the query layer — transactions around multi-step writes, a required `WHERE` on destructive statements, no raw SQL in the prod path.

The move that de-risks the data layer is the same one that de-risks the rest: have a specialized agent audit the destructive and privacy-sensitive paths the generalist agent generated, because those are exactly the mistakes that pass every test and only show up when the data is already gone.

Your data outlives your code, your framework, and probably the whole first version of the app — which is why it deserves more caution than anything else the agent touches. Back it up and prove the restore, keep migrations forward-only and reviewed, guard every destructive operation, wrap multi-step writes in transactions, and treat PII as a liability. This is one of the vibe-coding deep-dives — start from [the vibe-coding precautions overview](/blog/vibe-coding-precautions), and read it alongside [how to choose a backend](/blog/how-to-choose-a-backend) and [security and networking precautions](/blog/security-networking-precautions).
