The short answer
Add DB::prohibitDestructiveCommands($this->app->isProduction()); to your AppServiceProvider’s boot method. In production that stops db:wipe, migrate:fresh, migrate:refresh, migrate:reset and migrate:rollback from running, and no command-line flag turns it off. Let the agent run php artisan migrate --pretend and migrate:status; a person runs php artisan migrate --force on production after reading the SQL.
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
The prompt an agent learns to skip
Laravel already protects production. Some migration operations are destructive, so against a production database you’re asked to confirm before they run. To run them without the prompt, you add --force.1
That design assumes a person at the keyboard. A coding agent hitting a confirmation it can’t answer will try again with --force, because that’s what the docs, and every deploy script it has seen, say to do. The prompt does its job for people and almost nothing for agents.
migrate:fresh is the command to worry about most. It drops all tables and then runs migrate.1 An agent with a migration history that won’t apply cleanly sees it as the fastest way back to a working schema.
Prohibit destructive commands in code
Laravel has a stronger control, and it lives in your application instead of on the command line. The DB facade’s prohibitDestructiveCommands method prohibits db:wipe, migrate:fresh, migrate:refresh, migrate:reset and migrate:rollback.2 A prohibited command prints a warning and stops:3
Code What the screen or file shows, copied as is.
This command is prohibited from running in this environment.
Wire it to the environment so development is unaffected:
Code What the screen or file shows, copied as is.
// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\DB;
public function boot(): void
{
DB::prohibitDestructiveCommands($this->app->isProduction());
}
Why this beats the prompt: there’s no flag to add. --force doesn’t help, and neither does an environment variable, unlike Rails’ DISABLE_DATABASE_ENVIRONMENT_CHECK (our Rails post covers that one). To get past it, the agent would have to edit the service provider, which shows up in a diff you review.
Two things it doesn’t cover. Plain php artisan migrate --force still runs, which is what you want, since forward migrations are how production gets its schema. And migrate:rollback is on the list, so a one-step rollback on production needs a person to change the code or the environment, which some teams will find too strict. Adjust to taste, but start strict.
A workflow: the agent writes, a person applies
- The agent writes the migration with
php artisan make:migration. - The agent shows the SQL.
php artisan migrate --pretendprints the SQL the pending migrations would run, without running them;migrate:rollback --pretenddoes the same for a rollback.1 - The agent shows what’s pending.
php artisan migrate:statuslists which migrations have run and which haven’t.1 - A person reads the SQL. Look for
drop table,drop column, type changes on large tables, and newnot nullcolumns without defaults. - A person runs
php artisan migrate --forceon production.
Steps 2 and 3 are read-only, so an agent can run them against a copy of production to give you real output.
What a command check sees
A pre-command hook in the coding agent sees each artisan call before it runs. We ran Deedglass’s shell guard from 0.13.0 on 2026-10-27:4
| Command | Answer |
|---|---|
php artisan migrate:fresh --force |
asks |
php artisan db:wipe --force |
asks |
php artisan migrate --force |
nothing |
php artisan migrate:rollback |
nothing |
Code What the screen or file shows, copied as is.
$ npx deedglass@0.13.0 guard shell --no-files --command 'php artisan migrate:fresh --force'
Deedglass asks first: artisan migrate:fresh drops every table in the database, then migrates, so their rows are gone.
laravel-migrate-fresh artisan migrate:fresh drops every table in the database, then migrates, so their rows are gone
The catalog also covers artisan migrate:reset and migrate:refresh, which roll back every migration, each row with its source in Laravel’s docs or framework code.4 --force doesn’t change the answer: the guard reads which command runs, not the prompt.
Why have both the hook and prohibitDestructiveCommands? They cover different places. The prohibit call only acts where the app’s environment says production. The hook acts on the developer’s machine, where .env might point at a shared staging database, or at production by mistake, while APP_ENV says local. npx deedglass protect installs the hook in Claude Code, Cursor, Codex CLI, Gemini CLI and Copilot CLI.4
Where both stop
Neither control reads code that runs inside the app. php artisan tinker --execute="DB::table('orders')->truncate();" empties a table as surely as migrate:fresh empties the database. The guard reads text, so code an interpreter runs passes untouched,4 and if the hook can’t run at all, the command goes ahead unchecked.4
So keep the last line of defence at the database:
- A database user for the agent without
DROPorTRUNCATE. Point a separate connection inconfig/database.phpat it for anything the agent runs. - No production credentials in the
.envthe agent reads. Use a local database for development and seed data. - Backups you’ve restored. Test the restore, not just the backup.
Questions people ask
Does –force skip Laravel’s production confirmation?
Yes. Destructive migration operations ask for confirmation against a production database, and --force runs them without the prompt.1
How do I stop migrate:fresh running in production?
Call DB::prohibitDestructiveCommands($this->app->isProduction()) in a service provider’s boot method. It prohibits db:wipe, migrate:fresh, migrate:refresh, migrate:reset and migrate:rollback.2
How can I see the SQL a Laravel migration will run?
php artisan migrate --pretend prints the SQL without running it.1
Does prohibitDestructiveCommands stop migrate –force?
No. It prohibits db:wipe, migrate:fresh, migrate:refresh, migrate:reset and migrate:rollback.2 Plain migrate --force still runs forward migrations, which is what a deploy needs.
For other frameworks, see the table of migration commands that delete data, Django migrations with an agent and Rails migrations with an agent. The general approach is in letting an agent write migrations safely, and installing the hook everywhere is in the protect walkthrough.
To check a command yourself, run npx deedglass guard shell --no-files --command 'php artisan db:wipe --force'; to grade what an agent can reach, run npx deedglass scan (what the grade means), or watch the checks in the playground.
Written with AI assistance and checked against our claims register.