drizzle-kit push --force deleted data: why it happens and how to stop it

drizzle-kit push compares your schema with the database and applies the difference. With --force it accepts every statement that loses data, without asking. Here is what that means and a safer flow.

The short answer

drizzle-kit push reads your Drizzle schema, introspects the database, works out the difference and applies the SQL that closes it, without writing migration files.1 The --force flag tells it to “auto-accept all data-loss statements”.1 So if the difference includes dropping a column or a table, push --force drops it, and the data in it is gone.

To avoid that: run drizzle-kit push --explain first, which prints the planned SQL without applying it.1 For any database other people depend on, use generate to write migration files you can read, and migrate to apply them.2 And don’t let an agent add --force without a person seeing the plan.

Deedglass reads what Postgres actually committed, not what the agent reported. In our demo a hidden trigger turned 1 row into 301; a person countersigned the undo and all 301 rows came back.

What push does

Drizzle Kit offers two workflows.2

Workflow Commands What you review
Push drizzle-kit push The diff, at run time
Generate and migrate drizzle-kit generate, then drizzle-kit migrate SQL files, before they run

Drizzle calls push the best approach for rapid prototyping, and notes that some teams use it as their main flow in production.1 It’s fast because there’s no file to write or review. That is also its risk: the SQL that runs is computed at the moment you run it, against whatever the database looks like then.

Two flags help you see that SQL. --explain prints the planned changes without applying them, as a dry run, and --verbose prints every statement before executing it.1

What a diff can drop

A schema diff doesn’t know your intent. It only sees that the schema file and the database differ. Common ways a harmless-looking edit turns into a destructive statement:

  • A rename looks like a drop and an add. Rename fullName to name in the schema, and a diff may plan to drop fullName and add an empty name, unless the tool asks you whether it was a rename.
  • A removed table in the schema file. Deleting a model from the file, perhaps by a bad merge, plans a DROP TABLE.
  • A type change that can’t convert. Some changes need the column recreated.
  • Pointing at the wrong database. The schema is right for your branch database; the connection string is production.

Without --force, those are exactly the cases where you’d want to be asked. With it, nobody is.

Why agents reach for the force flag

A coding agent runs commands in a shell where an interactive prompt can’t easily be answered. When push stops to ask about a data-loss statement, the command appears to hang or fail. An agent trying to finish its task will often look for a way past the prompt, and --force is the documented way.

That is the pattern to design against. The agent isn’t malicious; it’s removing an obstacle. The obstacle was the safety check.

It isn’t unique to Drizzle. Other tools have one-flag equivalents. Alembic’s downgrade base reverts every revision back to an empty schema; Rails’s db:reset drops and recreates the database; Knex’s migrate:rollback --all rolls back every completed migration; Sequelize’s db:migrate:undo:all reverts every migration.1 Our post on Prisma’s migrate reset covers the Prisma side.

How to read a push plan

When you run push --explain, read the output the way a reviewer reads a migration. Three questions catch most problems:

  1. Is any statement a DROP? If so, is that table or column really meant to go, or is it a rename the diff didn’t recognise?
  2. Does any ALTER change a type? Narrowing a type, or changing it to one that can’t hold the old values, can fail or lose data.
  3. Which database is this? Check the host in the connection string before you look at anything else.

If the answer to any of these is unclear, don’t push. Generate a migration file instead, and let a second person read it.

A safer workflow

For local prototyping on a throwaway database, push is fine. For anything shared, move to files:

Code Commands typed in a terminal. The text around it says what happens.

# 1. See what push would do, without doing it
npx drizzle-kit push --explain

# 2. For shared databases: write the SQL to a file instead
npx drizzle-kit generate

# 3. Read the file. Look for DROP, and for drops followed by adds that should be renames
grep -nE "DROP (TABLE|COLUMN)" drizzle/*.sql

# 4. Apply it, after review
npx drizzle-kit migrate

The grep in step 3 is crude but useful. Any DROP in a migration file deserves a second pair of eyes, and a pull request is the natural place for it.

Three more habits:

  1. Separate connection strings per environment, and never keep production’s in the .env the agent can read.
  2. Use a database branch or a restored copy to try a migration first. The database branches comparison covers the options.
  3. Back up before applying, and know how long a restore takes.

Guardrails for agents

If an agent can run drizzle-kit at all, add a check that sees the command before it runs.

Permission rules. In Claude Code, an ask rule makes a matching command wait for your yes, and ask and deny rules apply when any part of a compound command matches.4 For example, ask on Bash(npx drizzle-kit push*), and allow only Bash(npx drizzle-kit push --explain*). Remember that a script in package.json can run the same command under another name.

A hook. A PreToolUse hook can read the full command and ask whenever it contains push with --force. Our Claude Code hook post has a tested script you can extend.

A ready-made guard. deedglass guard shell has a catalog row for exactly this: drizzle-kit push --force applies every schema change without asking, including the ones Drizzle warns delete data.3 It asks first, with one sentence naming the reason, and it never answers allow.3 Its limits are the same as any text check: a value known only at run time, such as a Makefile target or a variable, passes untouched.3

The role. The strongest guard is a database user that can’t drop tables. If the agent’s connection can’t run DROP, no flag changes that. The Postgres write access guide shows how.

If it already happened

  1. Stop further pushes. Revoke the agent’s database credentials if you’re unsure what else it will run.
  2. Check what ran. --verbose output or the agent’s transcript shows the statements.
  3. Restore. From a point-in-time backup, a branch, or a restored copy, then copy back the lost columns or tables.
  4. Write the guardrail before you let the agent near migrations again.

The checklist before an agent touches production puts these controls in order. To see a gate refuse destructive SQL on a real database in your browser, try the playground; to find which destructive commands an agent could run without a prompt in your repository, run npx deedglass scan (the grade, explained).5

Written with AI assistance and checked against our claims register.

The takeaway

drizzle-kit push --force accepts every data-loss statement; run push --explain first and use generate and migrate for shared databases.

Read nextA Claude Code hook that asks before DROP TABLE, migrate reset and rm -rf Try it: install in 7 s

Related on this site

More posts

Sources

  1. 1Drizzle docs: drizzle-kit push · orm.drizzle.team · checked 26 October 2026
  2. 2Drizzle docs: Drizzle Kit overview · orm.drizzle.team · checked 26 October 2026
  3. 3Deedglass docs: the shell guard · docs.deedglass.com · checked 26 October 2026
  4. 4Claude Code docs: Permissions · code.claude.com · checked 26 October 2026
  5. 5Deedglass docs: the scan · docs.deedglass.com · checked 26 October 2026

Published 26 October 2026, sources last checked 26 October 2026. If something here is out of date, write to hello@deedglass.com.

Try it on your own setup

npx deedglass scan

Grades only your own setup. Nothing is uploaded.

Pricing