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
A database change, checked and put back
-
1The agent’s change
Change order 42
UPDATE orders … WHERE id = 42
The agent: “1 row updated” -
2What the database really did
301 rows
A hidden trigger changed 300 more
Doesn’t match -
3Put back
301 rows restored
Signed off by a person
Matches
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
fullNametonamein the schema, and a diff may plan to dropfullNameand add an emptyname, 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:
- 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? - Does any
ALTERchange a type? Narrowing a type, or changing it to one that can’t hold the old values, can fail or lose data. - 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:
- Separate connection strings per environment, and never keep production’s in the
.envthe agent can read. - Use a database branch or a restored copy to try a migration first. The database branches comparison covers the options.
- 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
- Stop further pushes. Revoke the agent’s database credentials if you’re unsure what else it will run.
- Check what ran.
--verboseoutput or the agent’s transcript shows the statements. - Restore. From a point-in-time backup, a branch, or a restored copy, then copy back the lost columns or tables.
- 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.