Laravel migrations and AI coding agents: migrate yes, migrate:fresh no

An agent that hits Laravel's production prompt learns --force. One line in a service provider stops migrate:fresh and db:wipe whatever flag it adds. Here is the setup, tested.

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

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

  1. The agent writes the migration with php artisan make:migration.
  2. The agent shows the SQL. php artisan migrate --pretend prints the SQL the pending migrations would run, without running them; migrate:rollback --pretend does the same for a rollback.1
  3. The agent shows what’s pending. php artisan migrate:status lists which migrations have run and which haven’t.1
  4. A person reads the SQL. Look for drop table, drop column, type changes on large tables, and new not null columns without defaults.
  5. A person runs php artisan migrate --force on 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:

  1. A database user for the agent without DROP or TRUNCATE. Point a separate connection in config/database.php at it for anything the agent runs.
  2. No production credentials in the .env the agent reads. Use a local database for development and seed data.
  3. 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.

The takeaway

--force skips Laravel's production prompt, but DB::prohibitDestructiveCommands blocks migrate:fresh and db:wipe in code; the guard asked on both.

Read nextDjango migrations and AI coding agents: which manage.py commands to allow Try it: install in 7 s

Related on this site

More posts

Sources

  1. 1Laravel 13 docs: Database migrations · laravel.com · checked 27 October 2026
  2. 2laravel/framework: DB facade (prohibitDestructiveCommands) · github.com · checked 27 October 2026
  3. 3laravel/framework: Prohibitable · github.com · checked 27 October 2026
  4. 4Deedglass docs: The shell guard · docs.deedglass.com · checked 27 October 2026

Published 27 October 2026, sources last checked 27 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